Remove multiple lines of text from a JS file

Hello

I’m struggling to find a solution to removing multiple lines from a js file thousands of rows long.
I can’t use markers for blockinfile since after a version update, the function may be somewhere else and the markers will be gone then and I can’t use a fixed position either, since this could also change then.

I have tried lineinfile and replace modules and the regex that would work in regex101.com in Python flavor will either error out or show it’s already ok, nothing was changed.

I have tried complex patterns to make absolutely sure I won’t delete anything by accident, but I’m at the moment at this very simple, yet more dangerous pattern and Ansible says it’s already ok.

- name: Remove the subscription box from the Datacenter Summary page.
  ansible.builtin.lineinfile:
    path: /root/test.js
    regexp: '^\s+?{\n\s+title: gettext\(''Subscriptions''\),.*(\n.+){17}'
    state: absent

A more strict, but more complex pattern that matches every line in the block was:

^\s+?{\n\s+?title: gettext\(''Subscriptions''\),\n(\s+?(height|items).+?\n){2}\s+?{\n(\s+?(xtype|itemId|userCls|listeners|element|click).+?\n){6}\s+?if\s\(.+?\n\s+?window\.open.+?\n\s+?}\n.+?\n(\s+?},\n){2}\s+?],\n\s+?},

Wrapped regex around double quotes instead of single quotes and the pattern would look like this, but it still says everything is ok:

^\\s+?{\\n\\s+?title: gettext\\('Subscriptions'\\),\\n(\\s+?(height|items).+?\\n){2}\\s+?{\\n(\\s+?(xtype|itemId|userCls|listeners|element|click).+?\\n){6}\\s+?if\\s\\(.+?\\n\\s+?window\\.open.+?\\n\\s+?}\\n.+?\\n(\\s+?},\\n){2}\\s+?],\\n\\s+?},

Is there even a way to do this with Ansible natively?

Basically I’m trying to remove the following lines from pvemanagerlib.js.

    {
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        {
            xtype: 'pveHealthWidget',
            itemId: 'subscriptions',
            userCls: 'pointer',
            listeners: {
            element: 'el',
            click: function() {
                if (this.component.userCls === 'pointer') {
                window.open('https://www.proxmox.com/en/proxmox-virtual-environment/pricing', '_blank');
                }
            },
            },
        },
        ],
    },

Don’t panic, I’m running Proxmox at home and their subscription is way too expensive for what I do with it and I started using it last month. I want to clean up the Datacenter Summary page.

1 Like

Create block marks first. For example, given the file /tmp/pvemanagerlib.js for testing

    {
        title: gettext('foo'),
        height: 220,
        items: [
        ],
    },
    {
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        ],
    },
    {
        title: gettext('bar'),
        height: 220,
        items: [
        ],
    },

The play

- hosts: localhost

  tasks:


    - name: Create block markers.
      vars:
        marker: Subscriptions
        regex1: |-
          (\s*)title: gettext\('Subscriptions'\),
        replace1: |-
          \1title: gettext('Subscriptions'),
        regex2: '    },'
      block:

        - name: Create begin marker.
          ansible.builtin.replace:
            path: /tmp/pvemanagerlib.js
            regexp: "{{ regex1 }}"
            replace: |-

              {{ '#' }} BEGIN ANSIBLE MANAGED BLOCK {{ marker }}{{ replace1 }}

        - name: Create end marker
          ansible.builtin.replace:
            path: /tmp/pvemanagerlib.js
            regexp: ({{ regex1 }}[\s\S]*?{{ regex2 }})
            replace: |-
              \1
              {{ '#' }} END ANSIBLE MANAGED BLOCK {{ marker }}

gives

    {
        title: gettext('foo'),
        height: 220,
        items: [
        ],
    },
    {
# BEGIN ANSIBLE MANAGED BLOCK Subscriptions
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        ],
    },
# END ANSIBLE MANAGED BLOCK Subscriptions
    {
        title: gettext('bar'),
        height: 220,
        items: [
        ],
    },

Then, the editing is trivial. The play is not idempotent.

2 Likes

First off, thanks a ton! Not only I got what I wanted I accidentally found out why my regex patterns didn’t work. I had no idea you had to enclose them in brackets (groups).

I had to play with it and change it to include the starting {, so the result would be:

# BEGIN ANSIBLE MANAGED BLOCK Subscriptions
    {
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        ],
    },
# END ANSIBLE MANAGED BLOCK Subscriptions

instead of

    {
# BEGIN ANSIBLE MANAGED BLOCK Subscriptions
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        ],
    },
# END ANSIBLE MANAGED BLOCK Subscriptions

Here are my tasks. I broke them up into individual tasks because ansible-lint complained something about having to use when: somewhere in or before the blocks and I didn’t feel like troubleshooting it.

- name: Find the BEGIN marker.
  ansible.builtin.find:
    name: /root
    patterns: 'test.js'
    file_type: file
    use_regex: true
    read_whole_file: true
    contains: "BEGIN ANSIBLE MANAGED BLOCK {{ regex_marker }}"
  register: blockcheck
  changed_when: false

- name: Create the BEGIN marker.
  ansible.builtin.replace:
    path: /root/test.js
    regexp: "{{ regex_begin_find }}"
    replace: |
      \n{{ '#' }} BEGIN ANSIBLE MANAGED BLOCK {{ regex_marker }}{{ regex_begin_replace }}
  when: blockcheck.matched == 0

- name: Create the END marker.
  ansible.builtin.replace:
    path: /root/test.js
    regexp: ({{ regex_end_blockstart }}[\s\S]*?{{ regex_end_blockend }})
    replace: |
      \1{{ '#' }} END ANSIBLE MANAGED BLOCK {{ regex_marker }}
  when: blockcheck.matched == 0

EDIT: Since this action is not idempotent, I added a check.

The host host_vars file contains the following patterns:

regex_begin_find: |
  (\s+?{\n)(?:\s+?title: gettext\('Subscriptions'\),)
regex_begin_replace: |
  \1\ttitle: gettext('Subscriptions'),
regex_end_blockend: |
  (\s+?],\n\s+?},)
regex_end_blockstart: |
  ((\s+?)title: gettext\('Subscriptions'\),)
regex_marker: Subscription

Outcome:

    {
        itemId: 'nodeview',
        xtype: 'pveDcNodeView',
        height: 250,
    },
# BEGIN ANSIBLE MANAGED BLOCK Subscription
    {
        title: gettext('Subscriptions'),
        height: 220,
        items: [
        {
            xtype: 'pveHealthWidget',
            itemId: 'subscriptions',
            userCls: 'pointer',
            listeners: {
            element: 'el',
            click: function() {
                if (this.component.userCls === 'pointer') {
                window.open('https://www.proxmox.com/en/proxmox-virtual-environment/pricing', '_blank');
                }
            },
            },
        },
        ],
    },
# END ANSIBLE MANAGED BLOCK Subscription
    ],

    listeners: {
    resize: function(panel) {
        Proxmox.Utils.updateColumns(panel);
    },
    },
2 Likes

Nice! You can create 2nd capturing group and avoid the hard-coded TAB

regex_begin_find: |
  (\s+?{\n)(\s+)title: gettext\('Subscriptions'\),
regex_begin_replace: |
  \1\2title: gettext('Subscriptions'),
1 Like

When I try that, Python complains that I’m trying to reference an invalid group.

Changed it back and it worked again.

I think it’s fine, the last task (not included in this thread) is going to delete the whole block anyway.

Wild guess, have you deleted the closing parenthesis ) ? (in the end of the line)

I accidentally deleted the whole playbook :face_with_symbols_on_mouth:.
All I have of it is what I copied here. :confounded_face:

EDIT: That was too close. I was able to restore the last state from the VS Code cache.


Regarding your last question. I now see that you have slightly changed the find pattern. I just changed \t to \2 in replace. I’ll try it in the evening.

Thanks. As suspected, I changed my regex_begin_find pattern and it worked. :+1:


oooh, I see. Each capturing group is represented with a numbered backslash in the replace part, unless the groups are named. Good to know.