Skip to content

Commit f5f0b60

Browse files
Create/implement separate hello.js for hello.html config (#728)
* Create/implement separate hello.js for hello.html config Currently configuration is mostly in a YAML file embedded in the lab's HTML. This means that the lab configuration cannot be shared between locales (translations), which is annoying. We can't easily load YAML or JSON from a separate file when running or testing locally. However, we *can* easily load a separate file if it's in JavaScript format. This switches the configuration of lab hello.html so it uses the configuration in hello.js. Signed-off-by: David A. Wheeler <[email protected]> * Simplify hello.js Signed-off-by: David A. Wheeler <[email protected]> * hello.js: Provide Japanese translation examples This does not *implement* translations, but at least this includes some translations (which we hope to use in a later change). Signed-off-by: David A. Wheeler <[email protected]> * Auto-report info and info2 differences To make it easier to move from YAML, automatically detect differences between info and info2. We can create a JavaScript file that looks clean but is *supposed* to be identical, and once done, eliminate the old version. This will help ensure we don't make mistakes during the transition. Signed-off-by: David A. Wheeler <[email protected]> * Note in markdown the beginning of transition from YAML Signed-off-by: David A. Wheeler <[email protected]> --------- Signed-off-by: David A. Wheeler <[email protected]>
1 parent 6bc30a6 commit f5f0b60

File tree

4 files changed

+199
-105
lines changed

4 files changed

+199
-105
lines changed

docs/labs/checker.js

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
let correctRe = []; // Array of compiled regex of correct answer
1010
let expected = []; // Array of an expected (correct) answer
1111
let info = {}; // General info
12+
let info2 = {}; // Transitional info - if it exists, compare to info
1213
let hints = []; // Array of hint objects
1314
let page_definitions = {}; // Definitions used when preprocessing regexes
1415

@@ -17,6 +18,9 @@ let user_gave_up = false; // True if user ever gave up before user solved it
1718

1819
let startTime = Date.now();
1920

21+
let BACKQUOTE = "`"; // Make it easy to use `${BACKQUOTE}`
22+
let DOLLAR = "$"; // Make it easy to use `${DOLLAR}`
23+
2024
// Current language
2125
let lang;
2226

@@ -91,7 +95,6 @@ function determine_locale() {
9195
return lang;
9296
}
9397

94-
9598
// This array contains the default pattern preprocessing commands, in order.
9699
// We process every pattern through these (in order) to create a final regex
97100
// to be used to match a pattern.
@@ -174,6 +177,38 @@ function setDifference(lhs, rhs) {
174177
return new Set(result);
175178
}
176179

180+
/* Return differences between two objects
181+
*/
182+
function objectDiff(obj1, obj2) {
183+
let diff = {};
184+
185+
function compare(obj1, obj2, path = '') {
186+
for (const key in obj1) {
187+
if (obj1.hasOwnProperty(key)) {
188+
const newPath = path ? `${path}.${key}` : key;
189+
190+
if (!obj2.hasOwnProperty(key)) {
191+
diff[newPath] = [obj1[key], undefined];
192+
} else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
193+
compare(obj1[key], obj2[key], newPath);
194+
} else if (obj1[key] !== obj2[key]) {
195+
diff[newPath] = [obj1[key], obj2[key]];
196+
}
197+
}
198+
}
199+
200+
for (const key in obj2) {
201+
if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) {
202+
const newPath = path ? `${path}.${key}` : key;
203+
diff[newPath] = [undefined, obj2[key]];
204+
}
205+
}
206+
}
207+
208+
compare(obj1, obj2);
209+
return diff;
210+
}
211+
177212
/*
178213
* Show debug output in debug region and maybe via alert box
179214
* @debugOutput - the debug information to show
@@ -546,10 +581,10 @@ function processHints(requestedHints) {
546581
return compiledHints;
547582
}
548583

549-
/** Set global values based on info.
584+
/** Load and parse YAML data, return result to be placed in "info".
550585
* @info: String with YAML (including JSON) data to use
551586
*/
552-
function processInfo(configurationInfo) {
587+
function processYamlToInfo(configurationInfo) {
553588
// This would only allow JSON, but then we don't need to load YAML lib:
554589
// let parsedJson = JSON.parse(configurationInfo);
555590

@@ -563,9 +598,15 @@ function processInfo(configurationInfo) {
563598
throw e; // Rethrow, so containing browser also gets exception
564599
}
565600

566-
// Set global variable
567-
info = parsedData;
601+
return parsedData;
602+
}
568603

604+
/** Set global values based on other than "correct" and "expected" values.
605+
* The correct and expected values may come from elsewhere, but we have to set up the
606+
* info-based values first, because info can change how those are interpreted.
607+
* @info: String with YAML (including JSON) data to use
608+
*/
609+
function processInfo(configurationInfo) {
569610
const allowedInfoFields = new Set([
570611
'hints', 'successes', 'failures', 'correct', 'expected',
571612
'definitions', 'preprocessing', 'preprocessingTests', 'debug']);
@@ -603,14 +644,14 @@ function processInfo(configurationInfo) {
603644
};
604645

605646
// Set up hints
606-
if (parsedData && parsedData.hints) {
607-
hints = processHints(parsedData.hints);
647+
if (info && info.hints) {
648+
hints = processHints(info.hints);
608649
};
609650
}
610651

611652
/**
612653
* Run a simple selftest.
613-
* Run loadData *before* calling this, to set up globals like correctRe.
654+
* Run setupInfo *before* calling this, to set up globals like correctRe.
614655
* This ensures that:
615656
* - the initial attempt is incorrect (as expected)
616657
* - the expected value is correct (as expected)
@@ -683,17 +724,36 @@ function runSelftest() {
683724
}
684725

685726
/**
686-
* Load data from HTML page and initialize our local variables from it.
727+
* Load "info" data and set up all other variables that depend on "info".
728+
* The "info" data includes the regex preprocessing steps, hints, etc.
687729
*/
688-
function loadData() {
689-
// If there is info (e.g., hints), load it & set up global variable hints.
730+
function setupInfo() {
690731
// We must load info *first*, because it can affect how other things
691732
// (like pattern preprocessing) is handled.
733+
734+
// Deprecated approach: Load embedded "info" data in YAML file.
735+
// If there is "info" data embedded in the HTML (e.g., hints),
736+
// load it & set up global variable hints.
692737
let infoElement = document.getElementById('info');
693738
if (infoElement) {
694-
processInfo(infoElement.textContent);
739+
let configurationYamlText = infoElement.textContent;
740+
// Set global variable "info"
741+
info = processYamlToInfo(configurationYamlText);
695742
};
696743

744+
// If an "info2" exists, report any differences between it and "info".
745+
// This makes it safer to change how info is recorded.
746+
if (Object.keys(info2).length > 0) {
747+
let differences = objectDiff(info, info2);
748+
if (Object.keys(differences).length > 0) {
749+
alert(`ERROR: info2 exists, but info and info2 differ: ${JSON.stringify(differences)}`);
750+
}
751+
};
752+
753+
754+
// Set global values *except* correct and expected arrays
755+
processInfo(info);
756+
697757
// Set global correct and expected arrays
698758
let current = 0;
699759
while (true) {
@@ -732,7 +792,8 @@ function loadData() {
732792
}
733793

734794
function initPage() {
735-
loadData();
795+
// Use configuration info to set up all relevant global values.
796+
setupInfo();
736797

737798
// Run a selftest on page load, to prevent later problems
738799
runSelftest();

docs/labs/create_checker.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ under the `docs/labs` directory.
9292
Simply fork the repository, add your proposed lab in the `docs/labs` directory,
9393
and create a pull request.
9494

95+
### Transitioning away from YAML
96+
97+
Configuration data was originally in an embedded YAML file.
98+
We are transitioning to using separate `.js` files to simplify
99+
translations and eliminate the need for the YAML library.
100+
E.g., `input1.html` will have a corresponding `input1.js`
101+
with configuration information that is shared between translations.
102+
That transition hasn't completed yet.
103+
104+
To help, you can create a JavaScript file that sets info2
105+
instead of info. The checker will automatically report
106+
any differences between the two values.
107+
95108
### Quick aside: script tag requirements
96109

97110
Data about the lab is embedded in the HTML in a
@@ -746,6 +759,25 @@ different markers for the text of various locales.
746759
E.g., `text` would be English, and `text_jp` would its Japanese translation.
747760
We'd love feedback on this idea.
748761

762+
## Conversion of YAML to JavaScript files
763+
764+
We have used embedded YAML in the HTML files for configuration.
765+
However, this creates a problem for translations: You want different
766+
HTML files for each translation (locale), yet the embedded YAML can't be shared.
767+
768+
We have decided to move away from YAML for configuration to
769+
a lab-specific JavaScript file. That file can be loaded when the HTML
770+
is loaded, even when the HTML is loaded locally.
771+
772+
You can start this conversion using the `yq` tool:
773+
774+
~~~~sh
775+
yq eval hello.yaml -o=json -P > hello.js
776+
~~~~
777+
778+
Prepend the result with `configurationInfo =` and suffix with `;`.
779+
Now load the JavaScript as a script (after the main library).
780+
749781
## Potential future directions
750782

751783
Below are notes about potential future directions.

docs/labs/hello.html

Lines changed: 1 addition & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<link rel="stylesheet" href="checker.css">
88
<script src="js-yaml.min.js"></script>
99
<script src="checker.js"></script>
10+
<script src="hello.js"></script>
1011
<link rel="license" href="https://creativecommons.org/licenses/by/4.0/">
1112

1213
<!-- See create_labs.md for how to create your own lab! -->
@@ -21,98 +22,6 @@
2122
\s* console \. log \( (["'`])Hello,\x20world!\1 \) ; \s*
2223
</script>
2324

24-
<script id="info" type="application/yaml">
25-
---
26-
hints:
27-
- absent: |-
28-
^ console \. log \(
29-
text: ' Please use the form console.log("...");'
30-
examples:
31-
- - ""
32-
- - "foo"
33-
- present: "Goodbye"
34-
text: "You need to change the text Goodbye to something else."
35-
examples:
36-
- - 'console.log("Goodbye.");'
37-
- present: "hello"
38-
text: "Please capitalize Hello."
39-
examples:
40-
- - 'console.log("hello.");'
41-
- present: "World"
42-
text: "Please lowercase world."
43-
examples:
44-
- - 'console.log("Hello, World!");'
45-
- present: "Hello[^,]"
46-
text: "Put a comma immediately after Hello."
47-
examples:
48-
- - 'console.log("Hello world.");'
49-
- present: "Hello"
50-
absent: "[Ww]orld"
51-
text: "There's a Hello, but you need to also mention the world."
52-
examples:
53-
- - 'console.log("Hello, ");'
54-
- present: |-
55-
world[^\!]
56-
text: "Put an exclamation point immediately after world."
57-
examples:
58-
- - 'console.log("Hello, world.");'
59-
- present: 'Hello,\s*world!'
60-
absent: 'Hello,\x20world!'
61-
text: "You need exactly one space between 'Hello,' and 'world!'"
62-
examples:
63-
- - 'console.log("Hello, world!");'
64-
- present: |-
65-
^ console \. log \( Hello
66-
text: |-
67-
You must quote constant strings using ", ', or `
68-
examples:
69-
- - 'console.log(Hello, world'
70-
- - 'console.log( Hello, world'
71-
- absent: " ; $"
72-
text: >
73-
Please end this statement with a semicolon. JavaScript does not
74-
require a semicolon in this case,
75-
but usually when modifying source code you should
76-
follow the style of the current code.
77-
examples:
78-
- - ' console.log("Hello, world!") '
79-
successes:
80-
- - ' console . log( "Hello, world!" ) ; '
81-
- - " console . log( 'Hello, world!' ) ; "
82-
- - " console . log( `Hello, world!` ) ; "
83-
failures:
84-
- - ' console . log( Hello, world! ) ; '
85-
- - ' console . log("hello, world!") ; '
86-
# ADVANCED use - define our own preprocessing commands.
87-
# I suggest using "|-" (stripping the trailing newlines)
88-
# preprocessing:
89-
# -
90-
# - |-
91-
# [\n\r]+
92-
# - ""
93-
# -
94-
# - |-
95-
# (\\s\*)?\s+(\\s\*)?
96-
# - "\\s*"
97-
# Here are tests for default preprocessing. You do not need this in
98-
# every lab, but it demonstrates how to do it.
99-
preprocessingTests:
100-
-
101-
- |-
102-
\s* console \. log \( (["'`])Hello,\x20world!\1 \) ; \s*
103-
- |-
104-
\s*console\s*\.\s*log\s*\(\s*(["'`])Hello,\x20world!\1\s*\)\s*;\s*
105-
-
106-
- |-
107-
\s* foo \s+ bar \\string\\ \s*
108-
- |-
109-
\s*foo\s+bar\s*\\string\\\s*
110-
# debug: true
111-
</script>
112-
<!--
113-
114-
-->
115-
11625
</head>
11726
<body>
11827
<!-- For GitHub Pages formatting: -->

0 commit comments

Comments
 (0)