Skip to content

Commit 442c032

Browse files
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]>
1 parent a04d174 commit 442c032

File tree

4 files changed

+160
-104
lines changed

4 files changed

+160
-104
lines changed

docs/labs/checker.js

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ let page_definitions = {}; // Definitions used when preprocessing regexes
1515
let user_solved = false; // True if user has *ever* solved it on this load
1616
let user_gave_up = false; // True if user ever gave up before user solved it
1717

18+
let BACKQUOTE = "`"; // Make it easy to use `${BACKQUOTE}`
19+
let DOLLAR = "$"; // Make it easy to use `${DOLLAR}`
20+
1821
// This array contains the default pattern preprocessing commands, in order.
1922
// We process every pattern through these (in order) to create a final regex
2023
// to be used to match a pattern.
@@ -456,10 +459,10 @@ function processHints(requestedHints) {
456459
return compiledHints;
457460
}
458461

459-
/** Set global values based on info.
462+
/** Load and parse YAML data, return result to be placed in "info".
460463
* @info: String with YAML (including JSON) data to use
461464
*/
462-
function processInfo(configurationInfo) {
465+
function processYamlToInfo(configurationInfo) {
463466
// This would only allow JSON, but then we don't need to load YAML lib:
464467
// let parsedJson = JSON.parse(configurationInfo);
465468

@@ -473,9 +476,15 @@ function processInfo(configurationInfo) {
473476
throw e; // Rethrow, so containing browser also gets exception
474477
}
475478

476-
// Set global variable
477-
info = parsedData;
479+
return parsedData;
480+
}
478481

482+
/** Set global values based on other than "correct" and "expected" values.
483+
* The correct and expected values may come from elsewhere, but we have to set up the
484+
* info-based values first, because info can change how those are interpreted.
485+
* @info: String with YAML (including JSON) data to use
486+
*/
487+
function processInfo(configurationInfo) {
479488
const allowedInfoFields = new Set([
480489
'hints', 'successes', 'failures', 'correct', 'expected',
481490
'definitions', 'preprocessing', 'preprocessingTests', 'debug']);
@@ -513,14 +522,14 @@ function processInfo(configurationInfo) {
513522
};
514523

515524
// Set up hints
516-
if (parsedData && parsedData.hints) {
517-
hints = processHints(parsedData.hints);
525+
if (info && info.hints) {
526+
hints = processHints(info.hints);
518527
};
519528
}
520529

521530
/**
522531
* Run a simple selftest.
523-
* Run loadData *before* calling this, to set up globals like correctRe.
532+
* Run setupInfo *before* calling this, to set up globals like correctRe.
524533
* This ensures that:
525534
* - the initial attempt is incorrect (as expected)
526535
* - the expected value is correct (as expected)
@@ -593,17 +602,26 @@ function runSelftest() {
593602
}
594603

595604
/**
596-
* Load data from HTML page and initialize our local variables from it.
605+
* Load "info" data and set up all other variables that depend on "info".
606+
* The "info" data includes the regex preprocessing steps, hints, etc.
597607
*/
598-
function loadData() {
599-
// If there is info (e.g., hints), load it & set up global variable hints.
608+
function setupInfo() {
600609
// We must load info *first*, because it can affect how other things
601610
// (like pattern preprocessing) is handled.
611+
612+
// Deprecated approach: Load embedded "info" data in YAML file.
613+
// If there is "info" data embedded in the HTML (e.g., hints),
614+
// load it & set up global variable hints.
602615
let infoElement = document.getElementById('info');
603616
if (infoElement) {
604-
processInfo(infoElement.textContent);
617+
let configurationYamlText = infoElement.textContent;
618+
// Set global variable "info"
619+
info = processYamlToInfo(configurationYamlText);
605620
};
606621

622+
// Set global values *except* correct and expected arrays
623+
processInfo(info);
624+
607625
// Set global correct and expected arrays
608626
let current = 0;
609627
while (true) {
@@ -642,7 +660,8 @@ function loadData() {
642660
}
643661

644662
function initPage() {
645-
loadData();
663+
// Use configuration info to set up all relevant global values.
664+
setupInfo();
646665

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

docs/labs/create_checker.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ Modify the `expected0` section to have a sample expected answer, and
7878
See [input1.html](input1.html) and [input2.html](input2.html)
7979
for examples.
8080

81+
*NOTE*: We are transitioning to using separate `.js` files to simplify
82+
translations. E.g., `input1.html` will have a corresponding `input1.js`
83+
with configuration information that is shared between translations.
84+
That transition hasn't completed yet.
85+
8186
Whenever a lab is loaded it automatically runs all embedded self-tests.
8287
At the least, it checks that the initial attempted answer does
8388
*not* satisfy the correct answer pattern, while the example expected answer
@@ -746,6 +751,25 @@ different markers for the text of various locales.
746751
E.g., `text` would be English, and `text_jp` would its Japanese translation.
747752
We'd love feedback on this idea.
748753

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

751775
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: -->

docs/labs/hello.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
info =
2+
{
3+
"hints": [
4+
{
5+
"absent": String.raw`^ console \. log \(`,
6+
"text": " Please use the form console.log(\"...\");",
7+
"examples": [
8+
[ "" ],
9+
[ "foo" ]
10+
]
11+
},
12+
{
13+
"present": "Goodbye",
14+
"text": "You need to change the text Goodbye to something else.",
15+
"examples": [
16+
[ "console.log(\"Goodbye.\");" ]
17+
]
18+
},
19+
{
20+
"present": "hello",
21+
"text": "Please capitalize Hello.",
22+
"examples": [
23+
[ "console.log(\"hello.\");" ]
24+
]
25+
},
26+
{
27+
"present": "World",
28+
"text": "Please lowercase world.",
29+
"examples": [
30+
[ "console.log(\"Hello, World!\");" ]
31+
]
32+
},
33+
{
34+
"present": "Hello[^,]",
35+
"text": "Put a comma immediately after Hello.",
36+
"examples": [
37+
[ "console.log(\"Hello world.\");" ]
38+
]
39+
},
40+
{
41+
"present": "Hello",
42+
"absent": "[Ww]orld",
43+
"text": "There's a Hello, but you need to also mention the world.",
44+
"examples": [
45+
[ "console.log(\"Hello, \");" ]
46+
]
47+
},
48+
{
49+
"present": String.raw`world[^\!]`,
50+
"text": "Put an exclamation point immediately after world.",
51+
"examples": [
52+
[ "console.log(\"Hello, world.\");" ]
53+
]
54+
},
55+
{
56+
"present": String.raw`Hello,\s*world!`,
57+
"absent": String.raw`Hello,\x20world!`,
58+
"text": "You need exactly one space between 'Hello,' and 'world!'",
59+
"examples": [
60+
[ "console.log(\"Hello, world!\");" ]
61+
]
62+
},
63+
{
64+
"present": String.raw`^ console \. log \( Hello`,
65+
"text": "You must quote constant strings using \", ', or `",
66+
"examples": [
67+
[ "console.log(Hello, world" ],
68+
[ "console.log( Hello, world" ]
69+
]
70+
},
71+
{
72+
"absent": String.raw` ; $`,
73+
"text": "Please end this statement with a semicolon. JavaScript does not require a semicolon in this case, but usually when modifying source code you should follow the style of the current code.\n",
74+
"examples": [
75+
[ " console.log(\"Hello, world!\") " ]
76+
]
77+
}
78+
],
79+
"successes": [
80+
[ " console . log( \"Hello, world!\" ) ; " ],
81+
[ " console . log( 'Hello, world!' ) ; " ],
82+
[ " console . log( `Hello, world!` ) ; " ]
83+
],
84+
"failures": [
85+
[ " console . log( Hello, world! ) ; " ],
86+
[ " console . log(\"hello, world!\") ; " ]
87+
],
88+
// All regexes are preprocessed by a set of rules. You can replace them with your
89+
// own set. You can completely eliminate them by providing an empty set of rules:
90+
// "preprocessing" : [ ],
91+
// Here are tests for default preprocessing. You do not need this in
92+
// every lab, but it demonstrates how to create tests for your preprocessing.
93+
"preprocessingTests": [
94+
[
95+
String.raw`\s* console \. log \( (["'${BACKQUOTE}])Hello,\x20world!\1 \) ; \s*`,
96+
String.raw`\s*console\s*\.\s*log\s*\(\s*(["'${BACKQUOTE}])Hello,\x20world!\1\s*\)\s*;\s*`
97+
],
98+
[
99+
String.raw`\s* foo \s+ bar \\string\\ \s*`,
100+
String.raw`\s*foo\s+bar\s*\\string\\\s*`
101+
]
102+
103+
]
104+
};

0 commit comments

Comments
 (0)