atcoder-refactor

Rewrites variable names in AtCoder problem statements.

15.06.2020 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         atcoder-refactor
// @namespace    https://github.com/yoshrc/atcoder-refactor
// @version      1.1
// @description  Rewrites variable names in AtCoder problem statements.
// @author       yoshrc
// @match        https://atcoder.jp/contests/*/tasks/*
// @grant        none
// ==/UserScript==

// TODO
// - Inline edit like IDE's multiselection instead of popup
// - Use Roman font for multiple-character variable names
// - Remove variable name setting ... realized by reset variable name when set empty

(function() {
    'use strict';

    const ID_ATTR = 'data-atcoder-refactor-id';

    const storage = (() => {
        const STORAGE_KEY_PREFIX = 'atcoder-refactor-';
        const contest = location.href.match(/^https:\/\/atcoder\.jp\/contests\/([^/?]+)/)[1];
        const task = location.href.match(/^https:\/\/atcoder\.jp\/contests\/[^/?]+\/tasks\/([^/?]+)/)[1];
        const key = STORAGE_KEY_PREFIX + contest + '-' + task;

        return {
            load: () => {
                const idToNameStr = localStorage[key];
                if (!idToNameStr) {
                    return null;
                } else {
                    return JSON.parse(idToNameStr);
                }
            },

            save: idToName => {
                localStorage[key] = JSON.stringify(idToName);
            }
        };
    })();

    // When handlers called, the following setups must be completed:
    // - for all varaible elements, ID_ATTR attribute must be set
    // - id-name mapping should be set in the storage
    const handlers = (() => {
        const forEachVariable = (id, operationOnElement) => {
            document.querySelectorAll(`[${ID_ATTR}=${id}]`).forEach(elem => {
                operationOnElement(elem);
            });
        };

        return {
            onclick: varElem => {
                const idToName = storage.load();
                const id = varElem.getAttribute(ID_ATTR);
                const oldName = idToName[id];
                const newName = prompt('Set variable name', oldName);
                if (!newName || newName === '') {
                    return;
                }

                forEachVariable(id, elem => {
                    elem.textContent = newName;
                });
                idToName[id] = newName;
                storage.save(idToName);
            }
        };
    })();

    MathJax.Hub.Register.StartupHook('End', () => {
        const isVariable = mathJaxCharElem => mathJaxCharElem.textContent.match(/^[A-Za-z]+$/);
        const forEachVariable = operationOnElement => {
            document.querySelectorAll('.mjx-char').forEach(elem => {
                if (!isVariable(elem)) {
                    return;
                }
                operationOnElement(elem);
            });
        };

        const setupStorage = () => {
            const idToName = {};
            forEachVariable(elem => {
                const id = elem.textContent;
                idToName[id] = id;
            });
            storage.save(idToName);
            return idToName;
        };

        // Storage data gets inconsistent if the problem statement is updated
        const fixInconsistentData = idToName => {
            let hasMissingId = false;
            const unnecessaryIds = new Set(Object.keys(idToName));
            forEachVariable(elem => {
                const id = elem.textContent;
                if (idToName[id]) {
                    unnecessaryIds.delete(id);
                } else {
                    idToName[id] = id;
                    hasMissingId = true;
                }
            });

            if (unnecessaryIds.size !== 0) {
                for (let id of unnecessaryIds) {
                    delete idToName[id];
                }
                return true;
            } else {
                return hasMissingId;
            }
        };

        let idToName = storage.load();
        if (!idToName) {
            idToName = setupStorage();
        } else if (fixInconsistentData(idToName)) {
            storage.save(idToName);
        }

        forEachVariable(elem => {
            const id = elem.textContent;
            elem.setAttribute(ID_ATTR, id);
            elem.textContent = idToName[id];
            elem.onclick = () => handlers.onclick(elem);
        });
    });
})();