diff --git a/.githooks/check b/.githooks/check index c70efed..af07895 100755 --- a/.githooks/check +++ b/.githooks/check @@ -2,6 +2,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +JS_DIR="$PROJECT_ROOT/js" if ! command -v antlr4 &> /dev/null && ! command -v antlr &> /dev/null; then echo "Error: ANTLR is not installed or not in PATH." >&2 @@ -13,58 +14,64 @@ echo "Cleaning up..." find "$PROJECT_ROOT/grammar" \ -type f \( -name "*.ts" -o -name "*.interp" -o -name "*.tokens" \) \ -delete -rm -f "$PROJECT_ROOT/web/bundles/parser.bundle.js" -rm -f "$PROJECT_ROOT/web/bundles/parser.bundle.min.js" -rm -f "$PROJECT_ROOT/web/bundles/parser.bundle.min.js.map" -rm -f "$PROJECT_ROOT/web/bundles/ui.bundle.js" -rm -f "$PROJECT_ROOT/web/bundles/ui.bundle.min.js" -rm -f "$PROJECT_ROOT/web/bundles/ui.bundle.min.js.map" -rm -rf "$PROJECT_ROOT/output" +rm -f "$JS_DIR/web/bundles/parser.bundle.js" +rm -f "$JS_DIR/web/bundles/parser.bundle.min.js" +rm -f "$JS_DIR/web/bundles/parser.bundle.min.js.map" +rm -f "$JS_DIR/web/bundles/ui.bundle.js" +rm -f "$JS_DIR/web/bundles/ui.bundle.min.js" +rm -f "$JS_DIR/web/bundles/ui.bundle.min.js.map" +rm -rf "$JS_DIR/output" echo "Generating grammar..." -"$PROJECT_ROOT/scripts/generate-grammar.sh" +"$JS_DIR/scripts/generate-grammar.sh" if [ ! -f "$PROJECT_ROOT/grammar/IconScriptLexer.ts" ]; then echo "Error: grammar was not created." >&2 exit 1 fi +cd "$JS_DIR" || exit 1 + +# Create grammar symlink if it doesn't exist. +if [ ! -L "$JS_DIR/grammar" ]; then + ln -s ../grammar "$JS_DIR/grammar" +fi + echo "Building parser..." npm run build:parser:min # Check that `parser.bundle.min.js` was created. -if [ ! -f "$PROJECT_ROOT/web/bundles/parser.bundle.min.js" ]; then +if [ ! -f "$JS_DIR/web/bundles/parser.bundle.min.js" ]; then echo "Error: parser.bundle.min.js was not created." >&2 exit 1 fi -echo "Creating SVG icons..." -mkdir -p "$PROJECT_ROOT/output" -mkdir -p "$PROJECT_ROOT/output/main" -npm run generate -- \ - "$PROJECT_ROOT/test/main.iconscript" \ - "$PROJECT_ROOT/output/main" &> /dev/null -if [ ! -d "$PROJECT_ROOT/output" ]; then - echo "Error: output directory was not created." >&2 +echo "Building command-line interface..." +rm -rf "$JS_DIR/dist" +npm run build:cli +# Check that CLI was created. +if [ ! -f "$JS_DIR/dist/cli/generate-svg.js" ]; then + echo "Error: dist/cli/generate-svg.js was not created." >&2 exit 1 fi -if [ ! -s "$PROJECT_ROOT/output" ]; then - echo "Error: output directory is empty." >&2 + +echo "Creating SVG icons (testing CLI)..." +mkdir -p "$JS_DIR/output" +mkdir -p "$JS_DIR/output/main" +if ! npm run generate -- \ + "$JS_DIR/test/main.iconscript" \ + "$JS_DIR/output/main"; then + echo "Error: SVG icon generation failed." >&2 exit 1 fi - -echo "Creating command-line interface..." -rm -rf "$PROJECT_ROOT/dist" -npm run build:cli -# Check that `iconscript` was created. -if [ ! -f "$PROJECT_ROOT/dist/cli/generate-svg.js" ]; then - echo "Error: iconscript was not created." >&2 +if [ ! -d "$JS_DIR/output/main" ] || [ -z "$(ls -A "$JS_DIR/output/main")" ]; then + echo "Error: output directory was not created or is empty." >&2 exit 1 fi echo "Building library..." -rm -rf "$PROJECT_ROOT/dist" +rm -rf "$JS_DIR/dist" npm run build:lib # Check that `dist/` was created. -if [ ! -f "$PROJECT_ROOT/dist/src/parser.js" ]; then +if [ ! -f "$JS_DIR/dist/src/parser.js" ]; then echo "Error: dist/src/parser.js was not created." >&2 exit 1 fi @@ -98,4 +105,4 @@ echo "Running Rust tests..." if ! cargo test --manifest-path "$PROJECT_ROOT/rust/Cargo.toml"; then echo "Error: Rust tests failed." >&2 exit 1 -fi \ No newline at end of file +fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed25ba5..f0dabe0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,11 @@ on: branches: [main, master] jobs: - check: + js: runs-on: ubuntu-latest + defaults: + run: + working-directory: js steps: - name: Checkout code @@ -19,6 +22,7 @@ jobs: with: node-version: "20" cache: "npm" + cache-dependency-path: js/package-lock.json - name: Setup Java uses: actions/setup-java@v4 @@ -27,6 +31,7 @@ jobs: java-version: "17" - name: Install ANTLR + working-directory: . run: | wget -q https://www.antlr.org/download/antlr-4.13.2-complete.jar -O /tmp/antlr.jar echo '#!/bin/bash' > /tmp/antlr4 @@ -37,12 +42,16 @@ jobs: - name: Install dependencies run: npm ci + - name: Create grammar symlink + run: ln -s ../grammar grammar + - name: Clean up generated files + working-directory: . run: | find grammar -type f \( -name "*.ts" -o -name "*.interp" -o -name "*.tokens" \) -delete || true - rm -f web/bundles/parser.bundle.js web/bundles/parser.bundle.min.js web/bundles/parser.bundle.min.js.map - rm -f web/bundles/ui.bundle.js web/bundles/ui.bundle.min.js web/bundles/ui.bundle.min.js.map - rm -rf output + rm -f js/web/bundles/parser.bundle.js js/web/bundles/parser.bundle.min.js js/web/bundles/parser.bundle.min.js.map + rm -f js/web/bundles/ui.bundle.js js/web/bundles/ui.bundle.min.js js/web/bundles/ui.bundle.min.js.map + rm -rf js/output - name: Generate grammar run: | @@ -50,6 +59,7 @@ jobs: ./scripts/generate-grammar.sh - name: Verify grammar generation + working-directory: . run: | if [ ! -f "grammar/IconScriptLexer.ts" ]; then echo "Error: grammar was not created." @@ -66,18 +76,6 @@ jobs: exit 1 fi - - name: Create SVG icons (test) - run: | - mkdir -p output/main - npm run generate -- test/main.iconscript output/main - - - name: Verify SVG icons creation - run: | - if [ ! -d "output" ] || [ -z "$(ls -A output)" ]; then - echo "Error: output directory was not created or is empty." - exit 1 - fi - - name: Build CLI run: | rm -rf dist @@ -90,6 +88,18 @@ jobs: exit 1 fi + - name: Create SVG icons (test) + run: | + mkdir -p output/main + npm run generate -- test/main.iconscript output/main + + - name: Verify SVG icons creation + run: | + if [ ! -d "output/main" ] || [ -z "$(ls -A output/main)" ]; then + echo "Error: output directory was not created or is empty." + exit 1 + fi + - name: Build library run: | rm -rf dist diff --git a/.gitignore b/.gitignore index c45cc7d..893fb46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,13 @@ *.egg-info/ -.npmrc +.env __pycache__/ grammar/.antlr/ -node_modules/ -# Output files. -dist/ -output/ +# JavaScript implementation. +js/.npmrc +js/node_modules/ +js/dist/ +js/output/ # Parsers, generated from `IconScript.g4` by ANTLR4. grammar/*.interp @@ -14,9 +15,9 @@ grammar/*.tokens grammar/*.ts # Bundled JavaScript files. -web/bundles/*.bundle.js -web/bundles/*.bundle.min.js -web/bundles/*.bundle.min.js.map +js/web/bundles/*.bundle.js +js/web/bundles/*.bundle.min.js +js/web/bundles/*.bundle.min.js.map # WIP. webassembly/ diff --git a/README.md b/README.md index 8a65d49..5545f2c 100644 --- a/README.md +++ b/README.md @@ -7,83 +7,42 @@ project. The grammar of the language is described in the ANTLR4 `grammar/IconScript.g4` file. -## Installation - -Install dependencies: - -```shell -npm install -``` - -## Development Setup - -Generate grammar files: - -```shell -npm run generate:grammar -``` - -Build all components (library, CLI, and web bundles): - -```shell -npm run build -``` - -Build individually: -- `npm run generate:grammar` — generate grammar files, -- `npm run build:lib` — build TypeScript library, -- `npm run build:cli` — build CLI tool, -- `npm run build:parser:min` — build parser bundle for web, -- `npm run build:ui:min` — build UI bundle for web. - -## Usage - -### Command-Line Interface - -```shell -npm run generate $INPUT_ICONSCRIPT_FILE $OUTPUT_DIR -``` - -### Web Interface - -1. Build the web bundles (if not already built): - -```shell -npm run build:parser:min -npm run build:ui:min -``` - -2. Start a local server (e.g., using `live-server`): - -```shell -npm install -g live-server -live-server web -``` - -Or use any other static file server pointing to the `web/` directory. +## SVG generation + +There are two implementations of iconscript for parsing and generating SVG +files. + - __Rust__: `cargo install iconscript`. Rust implementation is _faster_ and + _more reliable_. It uses + [`linesweeper`](https://docs.rs/linesweeper/latest/linesweeper/) library for + Boolean path operations and SVG optimizations. + - __JavaScript__ (TypeScript): `npm install iconscript`. JavaScript + implementation uses [Paper.js](http://paperjs.org/) library, that may produce + wrong outputs. ## Syntax +Syntax slightly resembles the syntax of path commands in SVG. + ### Global context - - __width__ — stroke width. - - __position__ — current position of the cursor. + - `width` — stroke width. + - `position` — current position of the cursor. ### Commands -`` is 2D coordinates in the form `,` or `+,` (`+` means +`` is 2D coordinates in the form `,` or `+,` (`+` means that the position is relative to the __position__). -| Command | Description | -|---|---| -| `subtract` | Set subtraction mode | -| `w ` | Set __width__ to a value | -| `m ` | Set __position__ to a value | -| `l []` | Draw lines between positions | -| `lf []` | Draw filled lines between positions | -| `e ` | Draw circle specified by center point and radius | -| `r ` | Draw rectangle specified by top left and bottom right points | -| `a ` | Draw arc specified by center point, radius, and two angles in radians | +| Command | Description | +| ------------------------------------ | --------------------------------------------------------------------------------- | +| `subtract` | Set subtraction mode | +| `w ` | Set `width` to a value | +| `m ` | Set `position` to a value | +| `l []` | Draw lines between positions | +| `lf []` | Draw filled lines between positions | +| `e ` | Draw circle specified by center point and radius | +| `r ` | Draw rectangle specified by top left and bottom right points | +| `a ` | Draw arc specified by center point, radius, start angle, and end angle in radians | ### Variables @@ -92,8 +51,8 @@ Variables can be defined with ` = []` and accessed with ### Scopes -Scopes group commands together using `{` and `}`. They can be nested and are used -to incapsulate context changes. +Scopes group commands together using `{` and `}`. They can be nested and are +used to incapsulate context changes. ### Example diff --git a/js/.prettierignore b/js/.prettierignore new file mode 100644 index 0000000..b967cb4 --- /dev/null +++ b/js/.prettierignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +web/bundles/ diff --git a/.prettierrc b/js/.prettierrc similarity index 100% rename from .prettierrc rename to js/.prettierrc diff --git a/.stylelintignore b/js/.stylelintignore similarity index 100% rename from .stylelintignore rename to js/.stylelintignore diff --git a/.stylelintrc.json b/js/.stylelintrc.json similarity index 100% rename from .stylelintrc.json rename to js/.stylelintrc.json diff --git a/js/README.md b/js/README.md new file mode 100644 index 0000000..00be9e0 --- /dev/null +++ b/js/README.md @@ -0,0 +1,54 @@ +## Installation + +Install dependencies: + +```shell +npm install +``` + +## Development Setup + +Generate grammar files: + +```shell +npm run generate:grammar +``` + +Build all components (library, CLI, and web bundles): + +```shell +npm run build +``` + +Build individually: +- `npm run generate:grammar` — generate grammar files, +- `npm run build:lib` — build TypeScript library, +- `npm run build:cli` — build CLI tool, +- `npm run build:parser:min` — build parser bundle for web, +- `npm run build:ui:min` — build UI bundle for web. + +## Usage + +### Command-Line Interface + +```shell +npm run generate $INPUT_ICONSCRIPT_FILE $OUTPUT_DIR +``` + +### Web Interface + +1. Build the web bundles (if not already built): + +```shell +npm run build:parser:min +npm run build:ui:min +``` + +2. Start a local server (e.g., using `live-server`): + +```shell +npm install -g live-server +live-server web +``` + +Or use any other static file server pointing to the `web/` directory. diff --git a/doc/diagram.md b/js/doc/diagram.md similarity index 100% rename from doc/diagram.md rename to js/doc/diagram.md diff --git a/doc/public.moi b/js/doc/public.moi similarity index 100% rename from doc/public.moi rename to js/doc/public.moi diff --git a/doc/syntax.moi b/js/doc/syntax.moi similarity index 100% rename from doc/syntax.moi rename to js/doc/syntax.moi diff --git a/eslint.config.mjs b/js/eslint.config.mjs similarity index 100% rename from eslint.config.mjs rename to js/eslint.config.mjs diff --git a/js/grammar b/js/grammar new file mode 120000 index 0000000..2744a25 --- /dev/null +++ b/js/grammar @@ -0,0 +1 @@ +../grammar \ No newline at end of file diff --git a/package-lock.json b/js/package-lock.json similarity index 100% rename from package-lock.json rename to js/package-lock.json diff --git a/package.json b/js/package.json similarity index 91% rename from package.json rename to js/package.json index 3e7eb4d..8ec98b6 100644 --- a/package.json +++ b/js/package.json @@ -3,12 +3,12 @@ "version": "0.2.0", "description": "iconscript, a language for generating SVG icons", "type": "module", - "main": "dist/parser.js", - "types": "dist/parser.d.ts", + "main": "dist/src/parser.js", + "types": "dist/src/parser.d.ts", "exports": { ".": { - "import": "./dist/parser.js", - "types": "./dist/parser.d.ts" + "import": "./dist/src/parser.js", + "types": "./dist/src/parser.d.ts" } }, "bin": { @@ -27,7 +27,7 @@ "lint:css": "stylelint \"**/*.css\"", "lint": "npm run lint:ts && npm run lint:html && npm run lint:css", "test": "tsx test-suite.js", - "generate": "tsx src/cli/generate-svg.ts", + "generate": "node dist/cli/generate-svg.js", "build:cli": "tsx scripts/build-cli.ts", "build:lib": "tsc", "build:parser": "tsx scripts/build-parser.ts", diff --git a/scripts/build-cli.ts b/js/scripts/build-cli.ts similarity index 88% rename from scripts/build-cli.ts rename to js/scripts/build-cli.ts index ade0a4b..ac2e89e 100644 --- a/scripts/build-cli.ts +++ b/js/scripts/build-cli.ts @@ -38,10 +38,11 @@ async function buildCLI(): Promise { banner: { js: "#!/usr/bin/env node", }, - // Mark paper and paperjs-offset as external since they have complex - // Node.js dependencies (jsdom) that are difficult to bundle. + // Mark dependencies as external since they have complex + // Node.js dependencies that are difficult to bundle. // These will need to be installed when using the CLI. - external: ["paper", "paperjs-offset"], + external: ["paper", "paperjs-offset", "antlr4"], + nodePaths: [join(projectRoot, "node_modules")], }); console.log(`Created: ${basename(outputFile)}.`); diff --git a/scripts/build-parser.ts b/js/scripts/build-parser.ts similarity index 95% rename from scripts/build-parser.ts rename to js/scripts/build-parser.ts index 75e3c2b..611728b 100644 --- a/scripts/build-parser.ts +++ b/js/scripts/build-parser.ts @@ -27,6 +27,7 @@ async function buildParser(): Promise { sourcemap: minify, target: "es2020", external: [], + nodePaths: [join(projectRoot, "node_modules")], }); console.log(`Created: ${basename(outputFile)}.`); } catch (error) { diff --git a/scripts/build-ui.ts b/js/scripts/build-ui.ts similarity index 100% rename from scripts/build-ui.ts rename to js/scripts/build-ui.ts diff --git a/scripts/generate-grammar.sh b/js/scripts/generate-grammar.sh similarity index 84% rename from scripts/generate-grammar.sh rename to js/scripts/generate-grammar.sh index 4af3f21..3fe0153 100755 --- a/scripts/generate-grammar.sh +++ b/js/scripts/generate-grammar.sh @@ -3,7 +3,8 @@ set -e -PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +JS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PROJECT_ROOT="$(cd "$JS_DIR/.." && pwd)" GRAMMAR_FILE="$PROJECT_ROOT/grammar/IconScript.g4" OUTPUT_DIR="$PROJECT_ROOT/grammar" diff --git a/src/cli/generate-svg-wrapper.cjs b/js/src/cli/generate-svg-wrapper.cjs similarity index 100% rename from src/cli/generate-svg-wrapper.cjs rename to js/src/cli/generate-svg-wrapper.cjs diff --git a/src/cli/generate-svg.ts b/js/src/cli/generate-svg.ts similarity index 100% rename from src/cli/generate-svg.ts rename to js/src/cli/generate-svg.ts diff --git a/src/grammar.d.ts b/js/src/grammar.d.ts similarity index 100% rename from src/grammar.d.ts rename to js/src/grammar.d.ts diff --git a/src/parser.ts b/js/src/parser.ts similarity index 100% rename from src/parser.ts rename to js/src/parser.ts diff --git a/src/types.d.ts b/js/src/types.d.ts similarity index 100% rename from src/types.d.ts rename to js/src/types.d.ts diff --git a/syntax/README.md b/js/syntax/README.md similarity index 100% rename from syntax/README.md rename to js/syntax/README.md diff --git a/syntax/iconscript-vscode/.vscodeignore b/js/syntax/iconscript-vscode/.vscodeignore similarity index 100% rename from syntax/iconscript-vscode/.vscodeignore rename to js/syntax/iconscript-vscode/.vscodeignore diff --git a/syntax/iconscript-vscode/language-configuration.json b/js/syntax/iconscript-vscode/language-configuration.json similarity index 100% rename from syntax/iconscript-vscode/language-configuration.json rename to js/syntax/iconscript-vscode/language-configuration.json diff --git a/syntax/iconscript-vscode/package.json b/js/syntax/iconscript-vscode/package.json similarity index 100% rename from syntax/iconscript-vscode/package.json rename to js/syntax/iconscript-vscode/package.json diff --git a/syntax/iconscript-vscode/syntax/iconscript.tmLanguage.json b/js/syntax/iconscript-vscode/syntax/iconscript.tmLanguage.json similarity index 100% rename from syntax/iconscript-vscode/syntax/iconscript.tmLanguage.json rename to js/syntax/iconscript-vscode/syntax/iconscript.tmLanguage.json diff --git a/syntax/iconscript.sublime-syntax b/js/syntax/iconscript.sublime-syntax similarity index 100% rename from syntax/iconscript.sublime-syntax rename to js/syntax/iconscript.sublime-syntax diff --git a/syntax/iconscript.vim b/js/syntax/iconscript.vim similarity index 100% rename from syntax/iconscript.vim rename to js/syntax/iconscript.vim diff --git a/test/flag.iconscript b/js/test/flag.iconscript similarity index 100% rename from test/flag.iconscript rename to js/test/flag.iconscript diff --git a/test/main.iconscript b/js/test/main.iconscript similarity index 100% rename from test/main.iconscript rename to js/test/main.iconscript diff --git a/tsconfig.json b/js/tsconfig.json similarity index 82% rename from tsconfig.json rename to js/tsconfig.json index c838439..706d5b3 100644 --- a/tsconfig.json +++ b/js/tsconfig.json @@ -11,7 +11,11 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, - "outDir": "./dist" + "outDir": "./dist", + "baseUrl": ".", + "paths": { + "antlr4": ["node_modules/antlr4"] + } }, "files": [ "src/parser.ts", @@ -21,5 +25,5 @@ "grammar/IconScriptParser.ts", "grammar/IconScriptListener.ts" ], - "exclude": ["node_modules", "dist", "**/*.js", "**/*.mjs", "scripts", "web", "grammar/*.interp", "grammar/*.tokens"] + "exclude": ["node_modules", "dist", "**/*.js", "**/*.mjs", "scripts", "web"] } \ No newline at end of file diff --git a/web/bundles/.gitkeep b/js/web/bundles/.gitkeep similarity index 100% rename from web/bundles/.gitkeep rename to js/web/bundles/.gitkeep diff --git a/web/index.html b/js/web/index.html similarity index 100% rename from web/index.html rename to js/web/index.html diff --git a/web/style.css b/js/web/style.css similarity index 100% rename from web/style.css rename to js/web/style.css diff --git a/web/ui.ts b/js/web/ui.ts similarity index 100% rename from web/ui.ts rename to js/web/ui.ts diff --git a/web/wrapper.css b/js/web/wrapper.css similarity index 100% rename from web/wrapper.css rename to js/web/wrapper.css diff --git a/rust/README.md b/rust/README.md index 0e8de9d..388aac4 100644 --- a/rust/README.md +++ b/rust/README.md @@ -17,16 +17,15 @@ The binary will be available at `target/release/iconscript`. ## Usage ```shell -./target/release/iconscript $OPTIONS $ICONSCRIPT_FILE +./target/release/iconscript $OPTIONS $ICONSCRIPT_FILE $OUTPUT_DIRECTORY ``` -| Option | Description | -| -------------------- | -------------------------------------------------- | -| `-o`, `--output` | Output directory for SVG files (default: `output`) | -| `-s`, `--sketch` | Output raw paths without combining | -| `--no-rounding` | Disable coordinate rounding | -| `--no-deduplication` | Disable duplicate point removal | -| `--no-collinear` | Disable collinear point simplification | +| Option | Description | +| -------------------- | -------------------------------------- | +| `-s`, `--sketch` | Output raw paths without combining | +| `--no-rounding` | Disable coordinate rounding | +| `--no-deduplication` | Disable duplicate point removal | +| `--no-collinear` | Disable collinear point simplification | ## Testing