Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1844c1c3 authored by Lucas Dupin's avatar Lucas Dupin Committed by Android (Google) Code Review
Browse files

Merge "Scripts for token migration" into tm-qpr-dev

parents 140cff15 f27a8e14
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
{
    "env": {
        "es2021": true,
        "node": true
    },
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": ["prettier", "@typescript-eslint", "eslint-plugin-simple-import-sort", "import"],
    "extends": ["prettier", "eslint:recommended", "plugin:@typescript-eslint/recommended"],
    "rules": {
        "prettier/prettier": ["error"],
        "no-unused-vars": "off",
        "@typescript-eslint/no-unused-vars": [
            "warn",
            {
                "argsIgnorePattern": "^_",
                "varsIgnorePattern": "^_",
                "caughtErrorsIgnorePattern": "^_"
            }
        ],
        "no-multiple-empty-lines": ["error", { "max": 2 }],
        "no-multi-spaces": "error",
        "simple-import-sort/imports": "error",
        "simple-import-sort/exports": "error",
        "import/first": "error",
        "import/newline-after-import": "error",
        "import/no-duplicates": "error"
    }
}
+2 −0
Original line number Diff line number Diff line
vscode
node_modules
 No newline at end of file
+9 −0
Original line number Diff line number Diff line
{
   "tabWidth": 4,
   "printWidth": 100,
   "semi": true,
   "singleQuote": true,
    "bracketSameLine": true,
    "bracketSpacing": true,
    "arrowParens": "always"
}
 No newline at end of file
+297 −0
Original line number Diff line number Diff line
// Copyright 2022 Google LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

type IElementComment =
    | { commentNode: undefined; textContent: undefined; hidden: undefined }
    | { commentNode: Node; textContent: string; hidden: boolean };

interface ITag {
    attrs?: Record<string, string | number>;
    tagName: string;
}

export interface INewTag extends ITag {
    content?: string | number;
    comment?: string;
}

export type IUpdateTag = Partial<Omit<INewTag, 'tagName'>>;

export default class DOM {
    static addEntry(containerElement: Element, tagOptions: INewTag) {
        const doc = containerElement.ownerDocument;
        const exists = this.alreadyHasEntry(containerElement, tagOptions);

        if (exists) {
            console.log('Ignored adding entry already available: ', exists.outerHTML);
            return;
        }

        let insertPoint: Node | null = containerElement.lastElementChild; //.childNodes[containerElement.childNodes.length - 1];

        if (!insertPoint) {
            console.log('Ignored adding entry in empity parent: ', containerElement.outerHTML);
            return;
        }

        const { attrs, comment, content, tagName } = tagOptions;

        if (comment) {
            const commentNode = doc.createComment(comment);
            this.insertAfterIdented(commentNode, insertPoint);
            insertPoint = commentNode;
        }

        const newEl = doc.createElement(tagName);
        if (content) newEl.innerHTML = content.toString();
        if (attrs)
            Object.entries(attrs).forEach(([attr, value]) =>
                newEl.setAttribute(attr, value.toString())
            );
        this.insertAfterIdented(newEl, insertPoint);

        return true;
    }

    static insertBeforeIndented(newNode: Node, referenceNode: Node) {
        const paddingNode = referenceNode.previousSibling;
        const ownerDoc = referenceNode.ownerDocument;
        const containerNode = referenceNode.parentNode;

        if (!paddingNode || !ownerDoc || !containerNode) return;

        const currentPadding = paddingNode.textContent || '';
        const textNode = referenceNode.ownerDocument.createTextNode(currentPadding);

        containerNode.insertBefore(newNode, referenceNode);
        containerNode.insertBefore(textNode, newNode);
    }

    static insertAfterIdented(newNode: Node, referenceNode: Node) {
        const paddingNode = referenceNode.previousSibling;
        const ownerDoc = referenceNode.ownerDocument;
        const containerNode = referenceNode.parentNode;

        if (!paddingNode || !ownerDoc || !containerNode) return;

        const currentPadding = paddingNode.textContent || '';
        const textNode = ownerDoc.createTextNode(currentPadding);

        containerNode.insertBefore(newNode, referenceNode.nextSibling);
        containerNode.insertBefore(textNode, newNode);
    }

    static getElementComment(el: Element): IElementComment {
        const commentNode = el.previousSibling?.previousSibling;

        const out = { commentNode: undefined, textContent: undefined, hidden: undefined };

        if (!commentNode) return out;

        const textContent = commentNode.textContent || '';
        const hidden = textContent.substring(textContent.length - 6) == '@hide ';

        if (!(commentNode && commentNode.nodeName == '#comment')) return out;

        return { commentNode, textContent, hidden: hidden };
    }

    static duplicateEntryWithChange(
        templateElement: Element,
        options: Omit<IUpdateTag, 'content'>
    ) {
        const exists = this.futureEntryAlreadyExist(templateElement, options);
        if (exists) {
            console.log('Ignored duplicating entry already available: ', exists.outerHTML);
            return;
        }

        const { commentNode } = this.getElementComment(templateElement);
        let insertPoint: Node = templateElement;

        if (commentNode) {
            const newComment = commentNode.cloneNode();
            this.insertAfterIdented(newComment, insertPoint);
            insertPoint = newComment;
        }

        const newEl = templateElement.cloneNode(true) as Element;
        this.insertAfterIdented(newEl, insertPoint);

        this.updateElement(newEl, options);
        return true;
    }

    static replaceStringInAttributeValueOnQueried(
        root: Element,
        query: string,
        attrArray: string[],
        replaceMap: Map<string, string>
    ): boolean {
        let updated = false;
        const queried = [...Array.from(root.querySelectorAll(query)), root];

        queried.forEach((el) => {
            attrArray.forEach((attr) => {
                if (el.hasAttribute(attr)) {
                    const currentAttrValue = el.getAttribute(attr);

                    if (!currentAttrValue) return;

                    [...replaceMap.entries()].some(([oldStr, newStr]) => {
                        if (
                            currentAttrValue.length >= oldStr.length &&
                            currentAttrValue.indexOf(oldStr) ==
                                currentAttrValue.length - oldStr.length
                        ) {
                            el.setAttribute(attr, currentAttrValue.replace(oldStr, newStr));
                            updated = true;
                            return true;
                        }
                        return false;
                    });
                }
            });
        });

        return updated;
    }

    static updateElement(el: Element, updateOptions: IUpdateTag) {
        const exists = this.futureEntryAlreadyExist(el, updateOptions);
        if (exists) {
            console.log('Ignored updating entry already available: ', exists.outerHTML);
            return;
        }

        const { comment, attrs, content } = updateOptions;

        if (comment) {
            const { commentNode } = this.getElementComment(el);
            if (commentNode) {
                commentNode.textContent = comment;
            }
        }

        if (attrs) {
            for (const attr in attrs) {
                const value = attrs[attr];

                if (value != undefined) {
                    el.setAttribute(attr, `${value}`);
                } else {
                    el.removeAttribute(attr);
                }
            }
        }

        if (content != undefined) {
            el.innerHTML = `${content}`;
        }

        return true;
    }

    static elementToOptions(el: Element): ITag {
        return {
            attrs: this.getAllElementAttributes(el),
            tagName: el.tagName,
        };
    }

    static getAllElementAttributes(el: Element): Record<string, string> {
        return el
            .getAttributeNames()
            .reduce(
                (acc, attr) => ({ ...acc, [attr]: el.getAttribute(attr) || '' }),
                {} as Record<string, string>
            );
    }

    static futureEntryAlreadyExist(el: Element, updateOptions: IUpdateTag) {
        const currentElOptions = this.elementToOptions(el);

        if (!el.parentElement) {
            console.log('Checked el has no parent');
            process.exit();
        }

        return this.alreadyHasEntry(el.parentElement, {
            ...currentElOptions,
            ...updateOptions,
            attrs: { ...currentElOptions.attrs, ...updateOptions.attrs },
        });
    }

    static alreadyHasEntry(
        containerElement: Element,
        { attrs, tagName }: Pick<INewTag, 'attrs' | 'tagName'>
    ) {
        const qAttrs = attrs
            ? Object.entries(attrs)
                  .map(([a, v]) => `[${a}="${v}"]`)
                  .join('')
            : '';

        return containerElement.querySelector(tagName + qAttrs);
    }

    static replaceContentTextOnQueried(
        root: Element,
        query: string,
        replacePairs: Array<[string, string]>
    ) {
        let updated = false;
        let queried = Array.from(root.querySelectorAll(query));

        if (queried.length == 0) queried = [...Array.from(root.querySelectorAll(query)), root];

        queried.forEach((el) => {
            replacePairs.forEach(([oldStr, newStr]) => {
                if (el.innerHTML == oldStr) {
                    el.innerHTML = newStr;
                    updated = true;
                }
            });
        });

        return updated;
    }

    static XMLDocToString(doc: XMLDocument) {
        let str = '';

        doc.childNodes.forEach((node) => {
            switch (node.nodeType) {
                case 8: // comment
                    str += `<!--${node.nodeValue}-->\n`;
                    break;

                case 3: // text
                    str += node.textContent;
                    break;

                case 1: // element
                    str += (node as Element).outerHTML;
                    break;

                default:
                    console.log('Unhandled node type: ' + node.nodeType);
                    break;
            }
        });

        return str;
    }
}
+112 −0
Original line number Diff line number Diff line
// Copyright 2022 Google LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.import { exec } from 'child_process';

import { exec } from 'child_process';
import { parse } from 'csv-parse';
import { promises as fs } from 'fs';
import jsdom from 'jsdom';

const DOMParser = new jsdom.JSDOM('').window.DOMParser as typeof window.DOMParser;

type TFileList = string[];

export type TCSVRecord = Array<string | boolean | number>;

class _FileIO {
    public parser = new DOMParser();
    public saved: string[] = [];

    public loadXML = async (path: string): Promise<XMLDocument> => {
        try {
            const src = await this.loadFileAsText(path);
            return this.parser.parseFromString(src, 'text/xml') as XMLDocument;
        } catch (error) {
            console.log(`Failed to parse XML file '${path}'.`, error);
            process.exit();
        }
    };

    public loadFileAsText = async (path: string): Promise<string> => {
        try {
            return await fs.readFile(path, { encoding: 'utf8' });
        } catch (error) {
            console.log(`Failed to read file '${path}'.`, error);
            process.exit();
        }
    };

    public saveFile = async (data: string, path: string) => {
        try {
            await fs.writeFile(path, data, { encoding: 'utf8' });
            this.saved.push(path);
        } catch (error) {
            console.log(error);
            console.log(`Failed to write file '${path}'.`);
            process.exit();
        }
    };

    public loadFileList = async (path: string): Promise<TFileList> => {
        const src = await this.loadFileAsText(path);

        try {
            return JSON.parse(src) as TFileList;
        } catch (error) {
            console.log(error);
            console.log(`Failed to parse JSON file '${path}'.`);
            process.exit();
        }
    };

    public loadCSV = (path: string): Promise<Array<TCSVRecord>> => {
        return new Promise((resolve, reject) => {
            this.loadFileAsText(path).then((src) => {
                parse(
                    src,
                    {
                        delimiter: '	',
                    },
                    (err, records) => {
                        if (err) {
                            reject(err);
                            return;
                        }

                        resolve(records);
                    }
                );
            });
        });
    };

    formatSaved = () => {
        const cmd = `idea format ${this.saved.join(' ')}`;

        exec(cmd, (error, out, stderr) => {
            if (error) {
                console.log(error.message);
                return;
            }

            if (stderr) {
                console.log(stderr);
                return;
            }

            console.log(out);
        });
    };
}

export const FileIO = new _FileIO();
Loading