atcoder-refactor

Rewrites variable names in AtCoder problem statements.

2020-06-15 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name atcoder-refactor
  3. // @namespace https://github.com/yoshrc/atcoder-refactor
  4. // @version 1.1
  5. // @description Rewrites variable names in AtCoder problem statements.
  6. // @author yoshrc
  7. // @match https://atcoder.jp/contests/*/tasks/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. // TODO
  12. // - Inline edit like IDE's multiselection instead of popup
  13. // - Use Roman font for multiple-character variable names
  14. // - Remove variable name setting ... realized by reset variable name when set empty
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. const ID_ATTR = 'data-atcoder-refactor-id';
  20.  
  21. const storage = (() => {
  22. const STORAGE_KEY_PREFIX = 'atcoder-refactor-';
  23. const contest = location.href.match(/^https:\/\/atcoder\.jp\/contests\/([^/?]+)/)[1];
  24. const task = location.href.match(/^https:\/\/atcoder\.jp\/contests\/[^/?]+\/tasks\/([^/?]+)/)[1];
  25. const key = STORAGE_KEY_PREFIX + contest + '-' + task;
  26.  
  27. return {
  28. load: () => {
  29. const idToNameStr = localStorage[key];
  30. if (!idToNameStr) {
  31. return null;
  32. } else {
  33. return JSON.parse(idToNameStr);
  34. }
  35. },
  36.  
  37. save: idToName => {
  38. localStorage[key] = JSON.stringify(idToName);
  39. }
  40. };
  41. })();
  42.  
  43. // When handlers called, the following setups must be completed:
  44. // - for all varaible elements, ID_ATTR attribute must be set
  45. // - id-name mapping should be set in the storage
  46. const handlers = (() => {
  47. const forEachVariable = (id, operationOnElement) => {
  48. document.querySelectorAll(`[${ID_ATTR}=${id}]`).forEach(elem => {
  49. operationOnElement(elem);
  50. });
  51. };
  52.  
  53. return {
  54. onclick: varElem => {
  55. const idToName = storage.load();
  56. const id = varElem.getAttribute(ID_ATTR);
  57. const oldName = idToName[id];
  58. const newName = prompt('Set variable name', oldName);
  59. if (!newName || newName === '') {
  60. return;
  61. }
  62.  
  63. forEachVariable(id, elem => {
  64. elem.textContent = newName;
  65. });
  66. idToName[id] = newName;
  67. storage.save(idToName);
  68. }
  69. };
  70. })();
  71.  
  72. MathJax.Hub.Register.StartupHook('End', () => {
  73. const isVariable = mathJaxCharElem => mathJaxCharElem.textContent.match(/^[A-Za-z]+$/);
  74. const forEachVariable = operationOnElement => {
  75. document.querySelectorAll('.mjx-char').forEach(elem => {
  76. if (!isVariable(elem)) {
  77. return;
  78. }
  79. operationOnElement(elem);
  80. });
  81. };
  82.  
  83. const setupStorage = () => {
  84. const idToName = {};
  85. forEachVariable(elem => {
  86. const id = elem.textContent;
  87. idToName[id] = id;
  88. });
  89. storage.save(idToName);
  90. return idToName;
  91. };
  92.  
  93. // Storage data gets inconsistent if the problem statement is updated
  94. const fixInconsistentData = idToName => {
  95. let hasMissingId = false;
  96. const unnecessaryIds = new Set(Object.keys(idToName));
  97. forEachVariable(elem => {
  98. const id = elem.textContent;
  99. if (idToName[id]) {
  100. unnecessaryIds.delete(id);
  101. } else {
  102. idToName[id] = id;
  103. hasMissingId = true;
  104. }
  105. });
  106.  
  107. if (unnecessaryIds.size !== 0) {
  108. for (let id of unnecessaryIds) {
  109. delete idToName[id];
  110. }
  111. return true;
  112. } else {
  113. return hasMissingId;
  114. }
  115. };
  116.  
  117. let idToName = storage.load();
  118. if (!idToName) {
  119. idToName = setupStorage();
  120. } else if (fixInconsistentData(idToName)) {
  121. storage.save(idToName);
  122. }
  123.  
  124. forEachVariable(elem => {
  125. const id = elem.textContent;
  126. elem.setAttribute(ID_ATTR, id);
  127. elem.textContent = idToName[id];
  128. elem.onclick = () => handlers.onclick(elem);
  129. });
  130. });
  131. })();