atcoder-refactor

Rewrites variable names in AtCoder problem statements.

As of 2020-06-15. See the latest version.

  1. // ==UserScript==
  2. // @name atcoder-refactor
  3. // @namespace https://github.com/yoshrc/atcoder-refactor
  4. // @version 1.0
  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 key = STORAGE_KEY_PREFIX + contest;
  25.  
  26. return {
  27. load: () => {
  28. const idToNameStr = localStorage[key];
  29. if (!idToNameStr) {
  30. return null;
  31. } else {
  32. return JSON.parse(idToNameStr);
  33. }
  34. },
  35.  
  36. save: idToName => {
  37. localStorage[key] = JSON.stringify(idToName);
  38. }
  39. };
  40. })();
  41.  
  42. // When handlers called, the following setups must be completed:
  43. // - for all varaible elements, ID_ATTR attribute must be set
  44. // - id-name mapping should be set in the storage
  45. const handlers = (() => {
  46. const forEachVariable = (id, operationOnElement) => {
  47. document.querySelectorAll(`[${ID_ATTR}=${id}]`).forEach(elem => {
  48. operationOnElement(elem);
  49. });
  50. };
  51.  
  52. return {
  53. onclick: varElem => {
  54. const idToName = storage.load();
  55. const id = varElem.getAttribute(ID_ATTR);
  56. const oldName = idToName[id];
  57. const newName = prompt('Set variable name', oldName);
  58. if (!newName || newName === '') {
  59. return;
  60. }
  61.  
  62. forEachVariable(id, elem => {
  63. elem.textContent = newName;
  64. });
  65. idToName[id] = newName;
  66. storage.save(idToName);
  67. }
  68. };
  69. })();
  70.  
  71. MathJax.Hub.Register.StartupHook('End', () => {
  72. const isVariable = mathJaxCharElem => mathJaxCharElem.textContent.match(/^[A-Za-z]+$/);
  73. const forEachVariable = operationOnElement => {
  74. document.querySelectorAll('.mjx-char').forEach(elem => {
  75. if (!isVariable(elem)) {
  76. return;
  77. }
  78. operationOnElement(elem);
  79. });
  80. };
  81.  
  82. const setupStorage = () => {
  83. const idToName = {};
  84. forEachVariable(elem => {
  85. const id = elem.textContent;
  86. idToName[id] = id;
  87. });
  88. storage.save(idToName);
  89. };
  90.  
  91. // Storage data gets inconsistent if the problem statement is updated
  92. const fixInconsistentData = idToName => {
  93. let hasMissingId = false;
  94. const unnecessaryIds = new Set(Object.keys(idToName));
  95. forEachVariable(elem => {
  96. const id = elem.textContent;
  97. if (idToName[id]) {
  98. unnecessaryIds.delete(id);
  99. } else {
  100. idToName[id] = id;
  101. hasMissingId = true;
  102. }
  103. });
  104.  
  105. if (unnecessaryIds.size !== 0) {
  106. for (let id of unnecessaryIds) {
  107. delete idToName[id];
  108. }
  109. return true;
  110. } else {
  111. return hasMissingId;
  112. }
  113. };
  114.  
  115. const rewriteVariables = idToName => {
  116. forEachVariable(elem => {
  117. const id = elem.textContent;
  118. elem.textContent = idToName[id];
  119. });
  120. };
  121.  
  122. forEachVariable(elem => {
  123. const id = elem.textContent;
  124. elem.setAttribute(ID_ATTR, id);
  125. });
  126.  
  127. const idToName = storage.load();
  128. if (!idToName) {
  129. setupStorage();
  130. } else {
  131. if (fixInconsistentData(idToName)) {
  132. storage.save(idToName);
  133. }
  134. rewriteVariables(idToName);
  135. }
  136.  
  137. forEachVariable(elem => {
  138. elem.onclick = () => handlers.onclick(elem);
  139. });
  140. });
  141. })();