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
20 changes: 19 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
- run: ./gradlew spotlessCheck

docs:
name: Docs (AqlHelp)
name: Docs (AqlHelp + AqlExamples)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -174,3 +174,21 @@ jobs:
name: cql-manual
path: help/
retention-days: 14

- name: Generate CQL examples site
run: ./gradlew generateExamples

- name: Verify examples generated properly
run: |
set -euo pipefail
test -d examples || { echo "::error::examples/ was not produced"; exit 1; }
test -s examples/index.html || { echo "::error::missing or empty examples/index.html"; exit 1; }
test -s examples/Employees.html || { echo "::error::missing or empty examples/Employees.html"; exit 1; }

- name: Upload generated examples
if: always()
uses: actions/upload-artifact@v4
with:
name: cql-examples
path: examples/
retention-days: 14
20 changes: 13 additions & 7 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Deploy docs to website

# Regenerate the CQL HTML manual and push it to the website repo's help/ directory.
# Only help/ is touched, so the hand-authored pages on the site are never affected.
# Regenerate the CQL HTML manual and the standalone examples site, then push them to the website
# repo's help/ and examples/ directories. Only those two directories are touched, so the
# hand-authored pages on the site are never affected.


on:
Expand All @@ -14,7 +15,7 @@ permissions:

jobs:
deploy:
name: Generate and publish help/
name: Generate and publish help/ and examples/
runs-on: ubuntu-latest
if: github.repository == 'CategoricalData/CQL'
steps:
Expand All @@ -29,25 +30,30 @@ jobs:
run: ./gradlew generateHelp
timeout-minutes: 20

- name: Generate the CQL examples site
run: ./gradlew generateExamples
timeout-minutes: 20

- name: Check out the website repo (via deploy key)
uses: actions/checkout@v4
with:
repository: CategoricalData/categoricaldata.github.io
ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
path: site

- name: Replace help/ and push if it changed
- name: Replace help/ and examples/ and push if they changed
run: |
set -euo pipefail
rm -rf site/help
rm -rf site/help site/examples
cp -r help site/help
cp -r examples site/examples
cd site
git config user.name "cql-docs-bot"
git config user.email "actions@users.noreply.github.com"
git add help
git add help examples
if git diff --cached --quiet; then
echo "No documentation changes — nothing to publish."
else
git commit -m "docs: refresh CQL manual from CQL@${GITHUB_SHA::7}"
git commit -m "docs: refresh CQL manual and examples from CQL@${GITHUB_SHA::7}"
git push
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ lib/com.microsoft.z3.jar
.classpath
.project
help/
# Anchored to the repo root so it ignores the generated examples/ site WITHOUT touching the
# source examples under resources/open/examples/.
/examples/
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,20 @@ tasks.register('generateHelp', JavaExec) {
jvmArgs '--add-opens', 'java.base/java.lang=ALL-UNNAMED'
}

// Runs AqlExamples.main, which generates the standalone examples site (one self-contained page
// per self-tester example, showing its source and executed output) into the examples/ directory
// (relative to the project root). The forked JVM calls System.exit(0) on success.
tasks.register('generateExamples', JavaExec) {
group = 'documentation'
description = 'Generates the standalone CQL examples site into the examples/ directory.'
dependsOn 'classes'
mainClass = 'catdata.cql.exp.AqlExamples'
classpath = sourceSets.main.runtimeClasspath
// Examples may touch AWT; force headless so it runs on CI with no display.
systemProperty 'java.awt.headless', 'true'
jvmArgs '--add-opens', 'java.base/java.lang=ALL-UNNAMED'
}

// Git pre-commit hook: runs spotlessCheck before each commit
tasks.register('installGitHook', Copy) {
from 'config/hooks/pre-commit'
Expand Down
121 changes: 64 additions & 57 deletions src/catdata/cql/AqlTester.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
package catdata.cql;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;

import catdata.Program;
import catdata.Util;
import catdata.cql.exp.AqlEnv;
Expand All @@ -24,55 +11,77 @@
import catdata.ide.Examples;
import catdata.ide.Language;
import gnu.trove.map.hash.THashMap;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;

public class AqlTester {

static String message = "This self-test will run all the built-in CQL examples and check for exceptions. This test cannot be interupted. This window will disappear for a while. Continue?";
static String message =
"This self-test will run all the built-in CQL examples and check for exceptions. This test cannot be interupted. This window will disappear for a while. Continue?";

/**
* Built-in CQL examples the self-test does NOT run (they need external resources, are too slow,
* or are not standalone programs). The docs/examples generators share this set so that what is
* published can never drift from what the self-test actually verifies.
*/
public static final Set<String> NON_RUNNABLE =
Set.of("TutorialTSP", "QuickSQL", "Stdlib", "Imports");

public static void doSelfTestGui() {
deleteFilesCreatedDuring(() -> {
int c = JOptionPane.showConfirmDialog(null, message, "Run Self-Test?", JOptionPane.YES_NO_OPTION);
if (c != JOptionPane.YES_OPTION) {
return null;
}
Map<String, Throwable> result = getTestResult();
if (result.isEmpty()) {
JOptionPane.showMessageDialog(null, "OK: Tests Passed");
return null;
}
JTabbedPane t = new JTabbedPane();
for (String k : result.keySet()) {
t.addTab(k, new CodeTextPanel("Error", result.get(k).getMessage()));
}
JOptionPane.showMessageDialog(null, t);
return null; // ignored
});
deleteFilesCreatedDuring(
() -> {
int c =
JOptionPane.showConfirmDialog(
null, message, "Run Self-Test?", JOptionPane.YES_NO_OPTION);
if (c != JOptionPane.YES_OPTION) {
return null;
}
Map<String, Throwable> result = getTestResult();
if (result.isEmpty()) {
JOptionPane.showMessageDialog(null, "OK: Tests Passed");
return null;
}
JTabbedPane t = new JTabbedPane();
for (String k : result.keySet()) {
t.addTab(k, new CodeTextPanel("Error", result.get(k).getMessage()));
}
JOptionPane.showMessageDialog(null, t);
return null; // ignored
});
}

/**
* Return a map of test name to error on that test, empty if all tests are successful.
*/
/** Return a map of test name to error on that test, empty if all tests are successful. */
public static Map<String, Throwable> doSelfTestSilent() {
return deleteFilesCreatedDuring(() -> {
// silence output
PrintStream out = System.out, err = System.err;
PrintStream nul = new PrintStream(new OutputStream() {
@Override
public void write(int arg0) throws IOException {
}
});
try {
System.setOut(nul);
System.setErr(nul);

// run tests
return getTestResult();
} finally {
// restore output
System.setOut(out);
System.setErr(nul);
}
});
return deleteFilesCreatedDuring(
() -> {
// silence output
PrintStream out = System.out, err = System.err;
PrintStream nul =
new PrintStream(
new OutputStream() {
@Override
public void write(int arg0) throws IOException {}
});
try {
System.setOut(nul);
System.setErr(nul);

// run tests
return getTestResult();
} finally {
// restore output
System.setOut(out);
System.setErr(nul);
}
});
}

public static <A> A deleteFilesCreatedDuring(java.util.function.Supplier<A> closure) {
Expand Down Expand Up @@ -109,8 +118,7 @@ private static Set<Path> walkPwd() throws IOException {
private static Map<String, Throwable> getTestResult() {
Map<String, String> progs = new THashMap<>();
for (Example e : Examples.getExamples(Language.CQL)) {
if (e.getName().equals("TutorialTSP") || e.getName().equals("QuickSQL")
|| e.getName().equals("Stdlib") || e.getName().equals("Imports")) {
if (NON_RUNNABLE.contains(e.getName())) {
continue;
}
progs.put(e.getName(), e.getText());
Expand All @@ -135,5 +143,4 @@ private static Map<String, Throwable> getTestResult() {
}
return result;
}

}
4 changes: 1 addition & 3 deletions src/catdata/cql/CqlExamplesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
class CqlExamplesTest {

/** Names to skip (require external resources or are too slow). */
private static final Set<String> SKIP = Set.of(
"TutorialTSP", "QuickSQL", "Stdlib", "Imports"
);
private static final Set<String> SKIP = AqlTester.NON_RUNNABLE;

@Test
@Tag("integration")
Expand Down
Loading
Loading