Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions docs/labs/checker.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
let correctRe = []; // Array of compiled regex of correct answer
let expected = []; // Array of an expected (correct) answer
let info = {}; // General info
let info2 = {}; // Transitional info - if it exists, compare to info
let hints = []; // Array of hint objects
let page_definitions = {}; // Definitions used when preprocessing regexes

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

let startTime = Date.now();

let BACKQUOTE = "`"; // Make it easy to use `${BACKQUOTE}`
let DOLLAR = "$"; // Make it easy to use `${DOLLAR}`

// Current language
let lang;

Expand Down Expand Up @@ -91,7 +95,6 @@ function determine_locale() {
return lang;
}


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

/* Return differences between two objects
*/
function objectDiff(obj1, obj2) {
let diff = {};

function compare(obj1, obj2, path = '') {
for (const key in obj1) {
if (obj1.hasOwnProperty(key)) {
const newPath = path ? `${path}.${key}` : key;

if (!obj2.hasOwnProperty(key)) {
diff[newPath] = [obj1[key], undefined];
} else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
compare(obj1[key], obj2[key], newPath);
} else if (obj1[key] !== obj2[key]) {
diff[newPath] = [obj1[key], obj2[key]];
}
}
}

for (const key in obj2) {
if (obj2.hasOwnProperty(key) && !obj1.hasOwnProperty(key)) {
const newPath = path ? `${path}.${key}` : key;
diff[newPath] = [undefined, obj2[key]];
}
}
}

compare(obj1, obj2);
return diff;
}

/*
* Show debug output in debug region and maybe via alert box
* @debugOutput - the debug information to show
Expand Down Expand Up @@ -546,10 +581,10 @@ function processHints(requestedHints) {
return compiledHints;
}

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

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

// Set global variable
info = parsedData;
return parsedData;
}

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

// Set up hints
if (parsedData && parsedData.hints) {
hints = processHints(parsedData.hints);
if (info && info.hints) {
hints = processHints(info.hints);
};
}

/**
* Run a simple selftest.
* Run loadData *before* calling this, to set up globals like correctRe.
* Run setupInfo *before* calling this, to set up globals like correctRe.
* This ensures that:
* - the initial attempt is incorrect (as expected)
* - the expected value is correct (as expected)
Expand Down Expand Up @@ -683,17 +724,36 @@ function runSelftest() {
}

/**
* Load data from HTML page and initialize our local variables from it.
* Load "info" data and set up all other variables that depend on "info".
* The "info" data includes the regex preprocessing steps, hints, etc.
*/
function loadData() {
// If there is info (e.g., hints), load it & set up global variable hints.
function setupInfo() {
// We must load info *first*, because it can affect how other things
// (like pattern preprocessing) is handled.

// Deprecated approach: Load embedded "info" data in YAML file.
// If there is "info" data embedded in the HTML (e.g., hints),
// load it & set up global variable hints.
let infoElement = document.getElementById('info');
if (infoElement) {
processInfo(infoElement.textContent);
let configurationYamlText = infoElement.textContent;
// Set global variable "info"
info = processYamlToInfo(configurationYamlText);
};

// If an "info2" exists, report any differences between it and "info".
// This makes it safer to change how info is recorded.
if (Object.keys(info2).length > 0) {
let differences = objectDiff(info, info2);
if (Object.keys(differences).length > 0) {
alert(`ERROR: info2 exists, but info and info2 differ: ${JSON.stringify(differences)}`);
}
};


// Set global values *except* correct and expected arrays
processInfo(info);

// Set global correct and expected arrays
let current = 0;
while (true) {
Expand Down Expand Up @@ -732,7 +792,8 @@ function loadData() {
}

function initPage() {
loadData();
// Use configuration info to set up all relevant global values.
setupInfo();

// Run a selftest on page load, to prevent later problems
runSelftest();
Expand Down
32 changes: 32 additions & 0 deletions docs/labs/create_checker.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ under the `docs/labs` directory.
Simply fork the repository, add your proposed lab in the `docs/labs` directory,
and create a pull request.

### Transitioning away from YAML

Configuration data was originally in an embedded YAML file.
We are transitioning to using separate `.js` files to simplify
translations and eliminate the need for the YAML library.
E.g., `input1.html` will have a corresponding `input1.js`
with configuration information that is shared between translations.
That transition hasn't completed yet.

To help, you can create a JavaScript file that sets info2
instead of info. The checker will automatically report
any differences between the two values.

### Quick aside: script tag requirements

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

## Conversion of YAML to JavaScript files

We have used embedded YAML in the HTML files for configuration.
However, this creates a problem for translations: You want different
HTML files for each translation (locale), yet the embedded YAML can't be shared.

We have decided to move away from YAML for configuration to
a lab-specific JavaScript file. That file can be loaded when the HTML
is loaded, even when the HTML is loaded locally.

You can start this conversion using the `yq` tool:

~~~~sh
yq eval hello.yaml -o=json -P > hello.js
~~~~

Prepend the result with `configurationInfo =` and suffix with `;`.
Now load the JavaScript as a script (after the main library).

## Potential future directions

Below are notes about potential future directions.
Expand Down
93 changes: 1 addition & 92 deletions docs/labs/hello.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link rel="stylesheet" href="checker.css">
<script src="js-yaml.min.js"></script>
<script src="checker.js"></script>
<script src="hello.js"></script>
<link rel="license" href="https://creativecommons.org/licenses/by/4.0/">

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

<script id="info" type="application/yaml">
---
hints:
- absent: |-
^ console \. log \(
text: ' Please use the form console.log("...");'
examples:
- - ""
- - "foo"
- present: "Goodbye"
text: "You need to change the text Goodbye to something else."
examples:
- - 'console.log("Goodbye.");'
- present: "hello"
text: "Please capitalize Hello."
examples:
- - 'console.log("hello.");'
- present: "World"
text: "Please lowercase world."
examples:
- - 'console.log("Hello, World!");'
- present: "Hello[^,]"
text: "Put a comma immediately after Hello."
examples:
- - 'console.log("Hello world.");'
- present: "Hello"
absent: "[Ww]orld"
text: "There's a Hello, but you need to also mention the world."
examples:
- - 'console.log("Hello, ");'
- present: |-
world[^\!]
text: "Put an exclamation point immediately after world."
examples:
- - 'console.log("Hello, world.");'
- present: 'Hello,\s*world!'
absent: 'Hello,\x20world!'
text: "You need exactly one space between 'Hello,' and 'world!'"
examples:
- - 'console.log("Hello, world!");'
- present: |-
^ console \. log \( Hello
text: |-
You must quote constant strings using ", ', or `
examples:
- - 'console.log(Hello, world'
- - 'console.log( Hello, world'
- absent: " ; $"
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.
examples:
- - ' console.log("Hello, world!") '
successes:
- - ' console . log( "Hello, world!" ) ; '
- - " console . log( 'Hello, world!' ) ; "
- - " console . log( `Hello, world!` ) ; "
failures:
- - ' console . log( Hello, world! ) ; '
- - ' console . log("hello, world!") ; '
# ADVANCED use - define our own preprocessing commands.
# I suggest using "|-" (stripping the trailing newlines)
# preprocessing:
# -
# - |-
# [\n\r]+
# - ""
# -
# - |-
# (\\s\*)?\s+(\\s\*)?
# - "\\s*"
# Here are tests for default preprocessing. You do not need this in
# every lab, but it demonstrates how to do it.
preprocessingTests:
-
- |-
\s* console \. log \( (["'`])Hello,\x20world!\1 \) ; \s*
- |-
\s*console\s*\.\s*log\s*\(\s*(["'`])Hello,\x20world!\1\s*\)\s*;\s*
-
- |-
\s* foo \s+ bar \\string\\ \s*
- |-
\s*foo\s+bar\s*\\string\\\s*
# debug: true
</script>
<!--

-->

</head>
<body>
<!-- For GitHub Pages formatting: -->
Expand Down
Loading