From 488e12de318ac94ac75c4b18d8fe30ff86043a7d Mon Sep 17 00:00:00 2001
From: Brandon Thomas
Date: Tue, 6 Oct 2026 01:39:08 -0400
Subject: [PATCH] Rename SheetCraft to GridCraft everywhere (crates, apps,
bundle ids, packaging, docs)
Co-Authored-By: Claude Opus 5.5
---
.github/workflows/freebsd.yml | 20 +-
.github/workflows/packaging-lint.yml | 12 +-
.github/workflows/release.yml | 40 +--
.github/workflows/windows-arm64.yml | 16 +-
AGENTS.md | 14 +-
ATTRIBUTION.md | 40 +--
CLAUDE.md | 14 +-
Cargo.lock | 340 +++++++++---------
Cargo.toml | 28 +-
LICENSE-MIT | 2 +-
NOTICE | 6 +-
README.md | 62 ++--
ROADMAP.md | 6 +-
.../Cargo.toml | 10 +-
.../src/main.rs | 70 ++--
.../tests/cli.rs | 14 +-
.../Cargo.toml | 10 +-
.../Trunk.toml | 2 +-
apps/gridcraft-web/index.html | 19 +
.../src/main.rs | 6 +-
.../src/web.rs | 10 +-
apps/{sheetcraft => gridcraft}/Cargo.toml | 12 +-
apps/gridcraft/build.rs | 30 ++
.../src/control_server.rs | 4 +-
apps/{sheetcraft => gridcraft}/src/main.rs | 36 +-
.../src/native_menu.rs | 8 +-
apps/sheetcraft-web/index.html | 19 -
apps/sheetcraft/build.rs | 30 --
assets/app-icon/LICENSE.txt | 8 +-
assets/app-icon/README.md | 34 +-
...sheetcraft-1024.png => gridcraft-1024.png} | Bin
...-macos-512.png => gridcraft-macos-512.png} | Bin
.../{sheetcraft.icns => gridcraft.icns} | Bin
.../{sheetcraft.ico => gridcraft.ico} | Bin
...oryteller.sheetcraft.svg => gridcraft.svg} | 2 +-
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
...craft.png => ai.storyteller.gridcraft.png} | Bin
.../apps/ai.storyteller.gridcraft.svg} | 2 +-
crates/calc/Cargo.toml | 14 +-
crates/calc/src/eval.rs | 32 +-
crates/calc/src/lib.rs | 2 +-
crates/calc/src/recalc.rs | 18 +-
crates/calc/src/tests.rs | 12 +-
crates/chart/Cargo.toml | 12 +-
crates/chart/src/axis.rs | 4 +-
crates/chart/src/lib.rs | 6 +-
crates/chart/src/render.rs | 2 +-
crates/chart/src/resolve.rs | 8 +-
crates/chart/src/sparkline.rs | 2 +-
crates/chart/src/tests.rs | 8 +-
crates/core/Cargo.toml | 4 +-
crates/core/src/lib.rs | 2 +-
crates/engine/Cargo.toml | 22 +-
crates/engine/examples/perf.rs | 4 +-
crates/engine/src/cf.rs | 28 +-
crates/engine/src/cmd/data.rs | 24 +-
crates/engine/src/cmd/draw.rs | 4 +-
crates/engine/src/cmd/edit.rs | 42 +--
crates/engine/src/cmd/extra.rs | 14 +-
crates/engine/src/cmd/format.rs | 12 +-
crates/engine/src/cmd/formulas.rs | 32 +-
crates/engine/src/cmd/insert.rs | 36 +-
crates/engine/src/cmd/inspect.rs | 2 +-
crates/engine/src/cmd/mod.rs | 24 +-
crates/engine/src/cmd/pivot.rs | 8 +-
crates/engine/src/cmd/print.rs | 74 ++--
crates/engine/src/cmd/review.rs | 2 +-
crates/engine/src/cmd/sheet.rs | 54 +--
crates/engine/src/cmd/spelling.rs | 4 +-
crates/engine/src/cmd/view.rs | 6 +-
crates/engine/src/cmd/whatif.rs | 40 +--
crates/engine/src/display.rs | 6 +-
crates/engine/src/fill.rs | 16 +-
crates/engine/src/io.rs | 36 +-
crates/engine/src/lib.rs | 28 +-
crates/engine/src/pivot.rs | 8 +-
crates/engine/src/sample.rs | 6 +-
crates/engine/src/selection.rs | 6 +-
crates/engine/src/tables.rs | 4 +-
crates/engine/src/tests.rs | 4 +-
crates/formula/Cargo.toml | 6 +-
crates/formula/src/adjust.rs | 2 +-
crates/formula/src/ast.rs | 2 +-
crates/formula/src/lexer.rs | 2 +-
crates/formula/src/parser.rs | 4 +-
crates/formula/src/printer.rs | 4 +-
crates/formula/src/tests.rs | 4 +-
crates/functions/Cargo.toml | 6 +-
crates/functions/src/criteria.rs | 4 +-
crates/functions/src/database.rs | 10 +-
crates/functions/src/date.rs | 10 +-
crates/functions/src/dynamic.rs | 4 +-
crates/functions/src/engineering.rs | 4 +-
crates/functions/src/financial.rs | 6 +-
crates/functions/src/info.rs | 6 +-
crates/functions/src/lib.rs | 10 +-
crates/functions/src/lift.rs | 6 +-
crates/functions/src/lookup.rs | 4 +-
crates/functions/src/math.rs | 2 +-
crates/functions/src/stat.rs | 6 +-
crates/functions/src/text.rs | 6 +-
crates/functions/src/util.rs | 4 +-
crates/mcp/Cargo.toml | 6 +-
crates/mcp/src/backend.rs | 6 +-
crates/mcp/src/headless.rs | 8 +-
crates/mcp/src/lib.rs | 8 +-
crates/mcp/src/server.rs | 10 +-
crates/mcp/src/tests.rs | 10 +-
crates/mcp/src/tools.rs | 6 +-
crates/model/Cargo.toml | 8 +-
crates/model/src/cell.rs | 14 +-
crates/model/src/features.rs | 4 +-
crates/model/src/lib.rs | 4 +-
crates/model/src/sheet.rs | 10 +-
crates/model/src/store.rs | 16 +-
crates/model/src/style.rs | 2 +-
crates/model/src/workbook.rs | 2 +-
crates/numfmt/Cargo.toml | 6 +-
crates/numfmt/src/lib.rs | 6 +-
crates/numfmt/src/render.rs | 4 +-
crates/pdf/Cargo.toml | 4 +-
crates/pdf/src/lib.rs | 6 +-
crates/ui-egui/Cargo.toml | 8 +-
crates/ui-egui/examples/snapshot.rs | 8 +-
crates/ui-egui/src/chartview.rs | 18 +-
crates/ui-egui/src/control.rs | 8 +-
crates/ui-egui/src/dialogs.rs | 49 ++-
crates/ui-egui/src/editor.rs | 12 +-
crates/ui-egui/src/formula_bar.rs | 26 +-
crates/ui-egui/src/grid.rs | 56 ++-
crates/ui-egui/src/icons.rs | 2 +-
crates/ui-egui/src/lib.rs | 38 +-
crates/ui-egui/src/panes.rs | 6 +-
crates/ui-egui/src/ribbon.rs | 34 +-
crates/ui-egui/src/tabs.rs | 16 +-
crates/ui-egui/src/widgets.rs | 4 +-
crates/ui-egui/tests/interaction.rs | 6 +-
crates/xlsx/Cargo.toml | 10 +-
crates/xlsx/src/chart.rs | 10 +-
crates/xlsx/src/csv.rs | 6 +-
crates/xlsx/src/drawing.rs | 6 +-
crates/xlsx/src/fmla.rs | 12 +-
crates/xlsx/src/lib.rs | 6 +-
crates/xlsx/src/pivot.rs | 10 +-
crates/xlsx/src/read.rs | 8 +-
crates/xlsx/src/sheet_read.rs | 14 +-
crates/xlsx/src/sheet_write.rs | 22 +-
crates/xlsx/src/styles.rs | 6 +-
crates/xlsx/src/tests/csv_tests.rs | 6 +-
crates/xlsx/src/tests/fixtures.rs | 16 +-
crates/xlsx/src/tests/malformed.rs | 6 +-
crates/xlsx/src/tests/pivot_tests.rs | 8 +-
crates/xlsx/src/tests/roundtrip.rs | 4 +-
crates/xlsx/src/theme.rs | 2 +-
crates/xlsx/src/write.rs | 12 +-
docs/brand/LICENSE-brand.txt | 12 +-
docs/cli.md | 28 +-
docs/control-protocol.md | 8 +-
docs/mcp.md | 24 +-
docs/parity.md | 10 +-
docs/release-playbook.md | 4 +-
docs/releasing.md | 94 ++---
packaging/env.sh | 16 +-
packaging/freebsd/package.sh | 24 +-
packaging/icons.sh | 20 +-
...sktop => ai.storyteller.gridcraft.desktop} | 10 +-
... ai.storyteller.gridcraft.metainfo.xml.in} | 24 +-
....xml => ai.storyteller.gridcraft.mime.xml} | 2 +-
...craft.yml => ai.storyteller.gridcraft.yml} | 26 +-
packaging/linux/nfpm.yaml | 16 +-
packaging/linux/package.sh | 38 +-
packaging/macos/Info.plist.in | 18 +-
packaging/macos/entitlements.plist | 2 +-
packaging/macos/import-cert.sh | 4 +-
packaging/macos/package.sh | 44 +--
packaging/web/README.md | 26 +-
packaging/web/package.sh | 10 +-
packaging/windows/app-build.rs.in | 34 +-
packaging/windows/gridcraft.wxs | 88 +++++
packaging/windows/package.ps1 | 38 +-
packaging/windows/sheetcraft.wxs | 88 -----
packaging/windows/sign.ps1 | 6 +-
xtask/Cargo.toml | 6 +-
xtask/src/ico.rs | 2 +-
xtask/src/layers.rs | 76 ++--
xtask/src/main.rs | 8 +-
xtask/src/parity.rs | 12 +-
193 files changed, 1557 insertions(+), 1566 deletions(-)
rename apps/{sheetcraft-cli => gridcraft-cli}/Cargo.toml (50%)
rename apps/{sheetcraft-cli => gridcraft-cli}/src/main.rs (89%)
rename apps/{sheetcraft-cli => gridcraft-cli}/tests/cli.rs (89%)
rename apps/{sheetcraft-web => gridcraft-web}/Cargo.toml (75%)
rename apps/{sheetcraft-web => gridcraft-web}/Trunk.toml (67%)
create mode 100644 apps/gridcraft-web/index.html
rename apps/{sheetcraft-web => gridcraft-web}/src/main.rs (74%)
rename apps/{sheetcraft-web => gridcraft-web}/src/web.rs (94%)
rename apps/{sheetcraft => gridcraft}/Cargo.toml (69%)
create mode 100644 apps/gridcraft/build.rs
rename apps/{sheetcraft => gridcraft}/src/control_server.rs (95%)
rename apps/{sheetcraft => gridcraft}/src/main.rs (86%)
rename apps/{sheetcraft => gridcraft}/src/native_menu.rs (97%)
delete mode 100644 apps/sheetcraft-web/index.html
delete mode 100644 apps/sheetcraft/build.rs
rename assets/app-icon/{sheetcraft-1024.png => gridcraft-1024.png} (100%)
rename assets/app-icon/{sheetcraft-macos-512.png => gridcraft-macos-512.png} (100%)
rename assets/app-icon/{sheetcraft.icns => gridcraft.icns} (100%)
rename assets/app-icon/{sheetcraft.ico => gridcraft.ico} (100%)
rename assets/app-icon/{hicolor/scalable/apps/ai.storyteller.sheetcraft.svg => gridcraft.svg} (94%)
rename assets/app-icon/hicolor/128x128/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/16x16/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/24x24/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/256x256/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/32x32/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/48x48/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/512x512/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/hicolor/64x64/apps/{ai.storyteller.sheetcraft.png => ai.storyteller.gridcraft.png} (100%)
rename assets/app-icon/{sheetcraft.svg => hicolor/scalable/apps/ai.storyteller.gridcraft.svg} (94%)
rename packaging/linux/{ai.storyteller.sheetcraft.desktop => ai.storyteller.gridcraft.desktop} (76%)
rename packaging/linux/{ai.storyteller.sheetcraft.metainfo.xml.in => ai.storyteller.gridcraft.metainfo.xml.in} (71%)
rename packaging/linux/{ai.storyteller.sheetcraft.mime.xml => ai.storyteller.gridcraft.mime.xml} (91%)
rename packaging/linux/flatpak/{ai.storyteller.sheetcraft.yml => ai.storyteller.gridcraft.yml} (66%)
create mode 100644 packaging/windows/gridcraft.wxs
delete mode 100644 packaging/windows/sheetcraft.wxs
diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml
index 9bdb2d7..615ef50 100644
--- a/.github/workflows/freebsd.yml
+++ b/.github/workflows/freebsd.yml
@@ -2,7 +2,7 @@
# (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter.
#
# With `package: true` (release.yml calls it that way, or a manual run) it instead builds the
-# release binaries and uploads `sheetcraft--freebsd-x86_64.tar.gz` as the
+# release binaries and uploads `gridcraft--freebsd-x86_64.tar.gz` as the
# `release-freebsd` artifact (packaging/freebsd/package.sh), which release.yml attaches to the
# draft GitHub Release.
name: FreeBSD
@@ -51,10 +51,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 300
env:
- SHEETCRAFT_VERSION: ${{ inputs.version }}
- SHEETCRAFT_VERSION_OVERRIDE: ${{ inputs.version_override && '1' || '' }}
- SHEETCRAFT_PACKAGE: ${{ inputs.package && '1' || '' }}
- SHEETCRAFT_BUILD_SHA: ${{ github.sha }}
+ GRIDCRAFT_VERSION: ${{ inputs.version }}
+ GRIDCRAFT_VERSION_OVERRIDE: ${{ inputs.version_override && '1' || '' }}
+ GRIDCRAFT_PACKAGE: ${{ inputs.package && '1' || '' }}
+ GRIDCRAFT_BUILD_SHA: ${{ github.sha }}
steps:
- uses: actions/checkout@v4
# Optional font input (docs: storytold/craft-fonts docs/integration.md); releases embed it.
@@ -71,7 +71,7 @@ jobs:
usesh: true
cpu: 4
mem: 12288
- envs: "SHEETCRAFT_VERSION SHEETCRAFT_VERSION_OVERRIDE SHEETCRAFT_PACKAGE SHEETCRAFT_BUILD_SHA"
+ envs: "GRIDCRAFT_VERSION GRIDCRAFT_VERSION_OVERRIDE GRIDCRAFT_PACKAGE GRIDCRAFT_BUILD_SHA"
# Only the packaging run brings files back (dist/release). The target dir lives outside
# the synced checkout, because copying it out of the VM fails on large builds.
copyback: ${{ inputs.package }}
@@ -82,11 +82,11 @@ jobs:
. "$HOME/.cargo/env"
# The checkout is synced in under another owner; let tests that call git read it.
git config --global --add safe.directory "*"
- export CARGO_TARGET_DIR=/var/tmp/sheetcraft-target CARGO_PROFILE_DEV_DEBUG=0
+ export CARGO_TARGET_DIR=/var/tmp/gridcraft-target CARGO_PROFILE_DEV_DEBUG=0
rustc --version
- if [ -n "$SHEETCRAFT_PACKAGE" ]; then
- if [ -n "$SHEETCRAFT_VERSION_OVERRIDE" ]; then
- cargo run -q -p xtask --no-default-features -- version set "$SHEETCRAFT_VERSION"
+ if [ -n "$GRIDCRAFT_PACKAGE" ]; then
+ if [ -n "$GRIDCRAFT_VERSION_OVERRIDE" ]; then
+ cargo run -q -p xtask --no-default-features -- version set "$GRIDCRAFT_VERSION"
fi
CRAFT_FONTS_DIR="$PWD/craft-fonts" CRAFT_FONTS_REQUIRED=1 bash packaging/freebsd/package.sh
else
diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml
index 6fb6a2f..ab3ad17 100644
--- a/.github/workflows/packaging-lint.yml
+++ b/.github/workflows/packaging-lint.yml
@@ -46,12 +46,12 @@ jobs:
}
- name: XML, desktop entry, AppStream, Flatpak manifest
run: |
- xmllint --noout packaging/windows/sheetcraft.wxs packaging/macos/*.plist* packaging/linux/*.xml*
+ xmllint --noout packaging/windows/gridcraft.wxs packaging/macos/*.plist* packaging/linux/*.xml*
# Windows Installer needs Icon ids ending in .ico or .exe, or shortcuts show a blank icon.
- if grep -oE '"$RUNNER_TEMP/ai.storyteller.sheetcraft.metainfo.xml"
- appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.sheetcraft.metainfo.xml"
- python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.sheetcraft.yml')); assert m['id'] == 'ai.storyteller.sheetcraft' and m['command'] == 'sheetcraft'"
+ desktop-file-validate packaging/linux/ai.storyteller.gridcraft.desktop
+ sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.gridcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.gridcraft.metainfo.xml"
+ appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.gridcraft.metainfo.xml"
+ python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.gridcraft.yml')); assert m['id'] == 'ai.storyteller.gridcraft' and m['command'] == 'gridcraft'"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 84d2105..d38cdf8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,5 +1,5 @@
# Release pipeline: every push to `release` builds signed installers for every platform and
-# creates or updates the draft GitHub Release "SheetCraft v". See docs/releasing.md.
+# creates or updates the draft GitHub Release "GridCraft v". See docs/releasing.md.
# Copied from DesignCraft (craftrules release/playbook.md); keep the two in step.
#
# All jobs run in the `release` environment (restricted to the `release` branch), which holds
@@ -29,7 +29,7 @@ env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
- SHEETCRAFT_BUILD_SHA: ${{ github.sha }}
+ GRIDCRAFT_BUILD_SHA: ${{ github.sha }}
INPUT_VERSION: ${{ inputs.version }}
jobs:
@@ -59,7 +59,7 @@ jobs:
echo "date=$(date -u +%Y-%m-%d)"
echo "prerelease=$pre"
} >>"$GITHUB_OUTPUT"
- echo "### SheetCraft v$v (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
+ echo "### GridCraft v$v (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
macos:
name: macOS universal
@@ -67,8 +67,8 @@ jobs:
runs-on: macos-15
environment: release
env:
- SHEETCRAFT_VERSION: ${{ needs.version.outputs.version }}
- SHEETCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ GRIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ GRIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
# Optional font input (docs: storytold/craft-fonts docs/integration.md); releases always embed it.
@@ -86,7 +86,7 @@ jobs:
key: release-macos-universal
- name: Apply version override
if: inputs.version != ''
- run: cargo run -q -p xtask --no-default-features -- version set "$SHEETCRAFT_VERSION"
+ run: cargo run -q -p xtask --no-default-features -- version set "$GRIDCRAFT_VERSION"
- name: Import signing certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -126,8 +126,8 @@ jobs:
# .github/workflows/windows-arm64.yml installs and runs it on ARM64 hardware.
- { arch: arm64, target: aarch64-pc-windows-msvc }
env:
- SHEETCRAFT_VERSION: ${{ needs.version.outputs.version }}
- SHEETCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ GRIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ GRIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
# Optional font input (docs: storytold/craft-fonts docs/integration.md); releases always embed it.
@@ -146,7 +146,7 @@ jobs:
- name: Apply version override
if: inputs.version != ''
shell: bash
- run: cargo run -q -p xtask --no-default-features -- version set "$SHEETCRAFT_VERSION"
+ run: cargo run -q -p xtask --no-default-features -- version set "$GRIDCRAFT_VERSION"
- name: Install WiX
shell: pwsh
run: dotnet tool install --global wix --version 5.0.2
@@ -184,8 +184,8 @@ jobs:
- { arch: x86_64, runner: ubuntu-22.04, deb: amd64 }
- { arch: aarch64, runner: ubuntu-22.04-arm, deb: arm64 }
env:
- SHEETCRAFT_VERSION: ${{ needs.version.outputs.version }}
- SHEETCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ GRIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ GRIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
NFPM_VERSION: 2.47.0
steps:
- uses: actions/checkout@v4
@@ -211,7 +211,7 @@ jobs:
key: release-linux-${{ matrix.arch }}
- name: Apply version override
if: inputs.version != ''
- run: cargo run -q -p xtask --no-default-features -- version set "$SHEETCRAFT_VERSION"
+ run: cargo run -q -p xtask --no-default-features -- version set "$GRIDCRAFT_VERSION"
- name: Build and package (AppImage, deb, rpm, tar.gz)
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
@@ -220,9 +220,9 @@ jobs:
- name: Check the packages
run: |
dpkg-deb --info dist/release/*.deb
- dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/sheetcraft$|metainfo|applications'
- ldd target/release/sheetcraft
- python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.sheetcraft.yml')); assert m['id'] == 'ai.storyteller.sheetcraft'"
+ dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/gridcraft$|metainfo|applications'
+ ldd target/release/gridcraft
+ python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.gridcraft.yml')); assert m['id'] == 'ai.storyteller.gridcraft'"
for appimage in dist/release/*.AppImage; do
chmod +x "$appimage"
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
@@ -239,8 +239,8 @@ jobs:
runs-on: ubuntu-latest
environment: release
env:
- SHEETCRAFT_VERSION: ${{ needs.version.outputs.version }}
- SHEETCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ GRIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ GRIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
TRUNK_VERSION: 0.21.14
steps:
- uses: actions/checkout@v4
@@ -264,7 +264,7 @@ jobs:
trunk --version
- name: Apply version override
if: inputs.version != ''
- run: cargo run -q -p xtask --no-default-features -- version set "$SHEETCRAFT_VERSION"
+ run: cargo run -q -p xtask --no-default-features -- version set "$GRIDCRAFT_VERSION"
- name: Build and zip
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
@@ -277,7 +277,7 @@ jobs:
if-no-files-found: error
# FreeBSD has no hosted runners: freebsd.yml builds in a FreeBSD VM and, with package: true,
- # uploads the release-freebsd artifact (sheetcraft--freebsd-x86_64.tar.gz). It needs no
+ # uploads the release-freebsd artifact (gridcraft--freebsd-x86_64.tar.gz). It needs no
# secrets, so the called workflow runs without the release environment.
freebsd:
name: FreeBSD
@@ -314,7 +314,7 @@ jobs:
working-directory: artifacts
run: |
tag="v$VERSION"
- title="SheetCraft v$VERSION"
+ title="GridCraft v$VERSION"
pre=()
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); fi
if draft="$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null)"; then
diff --git a/.github/workflows/windows-arm64.yml b/.github/workflows/windows-arm64.yml
index daad48c..81a4bfc 100644
--- a/.github/workflows/windows-arm64.yml
+++ b/.github/workflows/windows-arm64.yml
@@ -53,7 +53,7 @@ jobs:
run: ./packaging/windows/package.ps1 -Arch arm64
- uses: actions/upload-artifact@v4
with:
- name: sheetcraft-windows-arm64
+ name: gridcraft-windows-arm64
path: dist/arm64/*
if-no-files-found: error
retention-days: 7
@@ -66,7 +66,7 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
- name: sheetcraft-windows-arm64
+ name: gridcraft-windows-arm64
path: dist
- name: Install the MSI, check it, run it, uninstall it
shell: pwsh
@@ -75,8 +75,8 @@ jobs:
$msi = (Get-ChildItem dist/*.msi | Select-Object -First 1).FullName
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', 'install.log' -Wait -PassThru
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 60; throw "msiexec /i exited $($p.ExitCode)" }
- $dir = Join-Path $env:ProgramFiles 'SheetCraft'
- foreach ($exe in 'sheetcraft.exe', 'sheetcraft-cli.exe') {
+ $dir = Join-Path $env:ProgramFiles 'GridCraft'
+ foreach ($exe in 'gridcraft.exe', 'gridcraft-cli.exe') {
$path = Join-Path $dir $exe
if (-not (Test-Path $path)) { throw "$exe not installed in $dir" }
$bytes = [System.IO.File]::ReadAllBytes($path)
@@ -85,11 +85,11 @@ jobs:
Write-Output "ok $path (ARM64)"
}
# The installed CLI runs natively on ARM64.
- $cli = Join-Path $dir 'sheetcraft-cli.exe'
+ $cli = Join-Path $dir 'gridcraft-cli.exe'
$out = & $cli --version 2>&1 | Out-String
- if ($LASTEXITCODE -ne 0) { Write-Output $out; throw "sheetcraft-cli --version exited $LASTEXITCODE" }
- Write-Output "ok sheetcraft-cli --version: $($out.Trim())"
+ if ($LASTEXITCODE -ne 0) { Write-Output $out; throw "gridcraft-cli --version exited $LASTEXITCODE" }
+ Write-Output "ok gridcraft-cli --version: $($out.Trim())"
$p = Start-Process msiexec.exe -ArgumentList '/x', "`"$msi`"", '/qn' -Wait -PassThru
if ($p.ExitCode -ne 0) { throw "msiexec /x exited $($p.ExitCode)" }
- if (Test-Path (Join-Path $dir 'sheetcraft.exe')) { throw 'uninstall left sheetcraft.exe behind' }
+ if (Test-Path (Join-Path $dir 'gridcraft.exe')) { throw 'uninstall left gridcraft.exe behind' }
Write-Output 'ok uninstalled'
diff --git a/AGENTS.md b/AGENTS.md
index b67b18e..b2ba5ad 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
-# SheetCraft — instructions for agents
+# GridCraft — instructions for agents
-SheetCraft is a clean-room, open-source, Rust-native spreadsheet targeting Microsoft Excel parity — and superiority (speed, openness, agent control). It runs natively on macOS, Windows, Linux and BSD, and on the web via WASM. Siblings with the same conventions: `../designcraft` (InDesign-class, the closest reference), `../photocraft` (Photoshop), `../vectorcraft` (Illustrator), `../printcraft` (Acrobat), `../filmcraft` (Premiere), `../lightcraft` (Lightroom), `../effectcraft` (After Effects).
+GridCraft is a clean-room, open-source, Rust-native spreadsheet targeting Microsoft Excel parity — and superiority (speed, openness, agent control). It runs natively on macOS, Windows, Linux and BSD, and on the web via WASM. Siblings with the same conventions: `../designcraft` (InDesign-class, the closest reference), `../photocraft` (Photoshop), `../vectorcraft` (Illustrator), `../printcraft` (Acrobat), `../filmcraft` (Premiere), `../lightcraft` (Lightroom), `../effectcraft` (After Effects).
Standards and learnings shared across the crafting apps live in `../../craftrules` (checked out next to the craft apps, or `storytold/craftrules`). Read its `AGENTS.md` at the start of a session, follow its standards, and contribute reusable learnings back there. Never code: repos don't share code.
@@ -12,7 +12,7 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
## Assets and iconography — absolute rule
**No Microsoft, Adobe, Avid, Autodesk (or any other vendor's) iconography, images, artwork, fonts, templates, themes or sample files — ever.** This is the most important rule in this repository; breaking it is a serious failure.
-- Every asset in the repo is original work by SheetCraft contributors, or public domain / CC0, or open source / Creative Commons that allows redistribution (or licensed open source by the contributor who created it).
+- Every asset in the repo is original work by GridCraft contributors, or public domain / CC0, or open source / Creative Commons that allows redistribution (or licensed open source by the contributor who created it).
- Every asset file has a row in [`ATTRIBUTION.md`](ATTRIBUTION.md) (author, source, licence). `cargo xtask assets` enforces it. Adding an asset without a row is a bug.
- Prefer art generated in code: the UI icon set (`crates/ui-egui/src/icons.rs`), table and cell style palettes, themes and sample workbooks are drawn/defined in code and original.
- Never copy Office's icons, ribbon artwork, theme XML, table-style definitions, templates, clip art or wording beyond feature names. Excel feature *names* (menu labels, function names) are fine.
@@ -20,7 +20,7 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
- The ArtCraft brand files in `docs/brand/` are ArtCraft trademarks under `docs/brand/LICENSE-brand.txt`, the one exception.
## Never crash
-People trust SheetCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
+People trust GridCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
- **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`).
- **Errors are `Result`** through the crate's error type and `?`.
- **Input-derived numbers are hostile** (files, formulas, commands, MCP/control params): `get()` instead of indexing, checked/saturating arithmetic, cap input-sized allocations (huge ranges, `REPT`, `SEQUENCE`, whole-column references).
@@ -41,10 +41,10 @@ People trust SheetCraft with their numbers; a crash loses their work. **This out
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, assets, layers, wasm). One task id per commit (`M2.1: borders gallery`).
## Running and looking at the app
-- `cargo run --release -p sheetcraft -- --sample sales --control 7979` (sample workbook + control channel).
+- `cargo run --release -p gridcraft -- --sample sales --control 7979` (sample workbook + control channel).
- Drive it: JSON lines on `127.0.0.1:7979`, e.g. `{"id":1,"method":"engine.execute","params":{"command":"cell.set","params":{"cell":"B2","input":"=SUM(A1:A9)"}}}` then `{"id":2,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}`. Methods: `crates/ui-egui/src/control.rs`, docs: `docs/control-protocol.md`.
-- **Offscreen UI render** (no window): `cargo run --release -p sheetcraft-cli -- snapshot --sample sales out.png`.
-- Headless: `sheetcraft-cli eval '=SUM(1,2,3)'`, `sheetcraft-cli run --in book.xlsx --cmd 'home.bold={"range":"A1:C1"}' --out book.xlsx`, `sheetcraft-cli mcp`.
+- **Offscreen UI render** (no window): `cargo run --release -p gridcraft-cli -- snapshot --sample sales out.png`.
+- Headless: `gridcraft-cli eval '=SUM(1,2,3)'`, `gridcraft-cli run --in book.xlsx --cmd 'home.bold={"range":"A1:C1"}' --out book.xlsx`, `gridcraft-cli mcp`.
- **For UI work, look at the result** (snapshot PNG) and compare with `plan/excel/observed-ui.md`.
- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`. macOS has no `timeout`; use `perl -e 'alarm 60; exec @ARGV' cmd`.
- Parallel agents: separate `CARGO_TARGET_DIR` per agent; edit only the crates you own; delete your target dir when done.
diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
index affdada..92b553e 100644
--- a/ATTRIBUTION.md
+++ b/ATTRIBUTION.md
@@ -2,7 +2,7 @@
Every non-code asset in this repository (images, icons, fonts, example files, presets) is listed here with its author, source and licence. `cargo xtask assets` (part of `cargo xtask ci`) fails if an asset file is missing from this table.
-**Policy (mandatory):** SheetCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, artwork, fonts, templates, themes, table styles or sample files**. Every asset is original work by SheetCraft contributors or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution). Screenshots of Microsoft software are never committed. Font files are not added here: shared fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (`CRAFT_FONTS_DIR`). The one exception is the ArtCraft brand files in `docs/brand/`: ArtCraft trademarks, not open source, used under `docs/brand/LICENSE-brand.txt`.
+**Policy (mandatory):** GridCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, artwork, fonts, templates, themes, table styles or sample files**. Every asset is original work by GridCraft contributors or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution). Screenshots of Microsoft software are never committed. Font files are not added here: shared fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (`CRAFT_FONTS_DIR`). The one exception is the ArtCraft brand files in `docs/brand/`: ArtCraft trademarks, not open source, used under `docs/brand/LICENSE-brand.txt`.
Generated-in-code art is original and has no file to list: the UI icon set (`crates/ui-egui/src/icons.rs`), themes (`crates/engine/src/cmd/view.rs`), cell styles (`crates/engine/src/cmd/format.rs`), table styles (`crates/engine/src/tables.rs`), chart palettes and the sample workbooks (`crates/engine/src/sample.rs`).
@@ -17,23 +17,23 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
| `docs/brand/LICENSE-brand.txt` | (licence text) | craftrules | — | |
-| `assets/app-icon/sheetcraft.svg` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | PLACEHOLDER app icon (ledger page); replaced by the owner's creature art later |
-| `assets/app-icon/sheetcraft-1024.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, rendered from sheetcraft.svg |
-| `assets/app-icon/sheetcraft-macos-512.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS grid render |
-| `assets/app-icon/sheetcraft.icns` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS bundle icon |
-| `assets/app-icon/sheetcraft.ico` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Windows exe icon |
-| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.sheetcraft.png` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
-| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.sheetcraft.svg` | SheetCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, copy of sheetcraft.svg |
-| `assets/app-icon/README.md` | SheetCraft contributors | original | MIT OR Apache-2.0 | Icon documentation |
+| `assets/app-icon/gridcraft.svg` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | PLACEHOLDER app icon (ledger page); replaced by the owner's creature art later |
+| `assets/app-icon/gridcraft-1024.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, rendered from gridcraft.svg |
+| `assets/app-icon/gridcraft-macos-512.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS grid render |
+| `assets/app-icon/gridcraft.icns` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS bundle icon |
+| `assets/app-icon/gridcraft.ico` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Windows exe icon |
+| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.gridcraft.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, Linux hicolor render |
+| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.gridcraft.svg` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, copy of gridcraft.svg |
+| `assets/app-icon/README.md` | GridCraft contributors | original | MIT OR Apache-2.0 | Icon documentation |
| `assets/app-icon/LICENSE.txt` | (licence text) | original | — | Licence of the app icon files |
-| `docs/images/hero-sales.png` | SheetCraft contributors | screenshot of SheetCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
-| `docs/images/budget.png` | SheetCraft contributors | screenshot of SheetCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
-| `docs/images/grades-formulas.png` | SheetCraft contributors | screenshot of SheetCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
-| `docs/images/dark-insert.png` | SheetCraft contributors | screenshot of SheetCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
+| `docs/images/hero-sales.png` | GridCraft contributors | screenshot of GridCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
+| `docs/images/budget.png` | GridCraft contributors | screenshot of GridCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
+| `docs/images/grades-formulas.png` | GridCraft contributors | screenshot of GridCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
+| `docs/images/dark-insert.png` | GridCraft contributors | screenshot of GridCraft rendered offscreen (`crates/ui-egui/examples/snapshot.rs`) showing an original sample workbook | MIT OR Apache-2.0 | README screenshot; no third-party content |
diff --git a/CLAUDE.md b/CLAUDE.md
index b67b18e..b2ba5ad 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,6 @@
-# SheetCraft — instructions for agents
+# GridCraft — instructions for agents
-SheetCraft is a clean-room, open-source, Rust-native spreadsheet targeting Microsoft Excel parity — and superiority (speed, openness, agent control). It runs natively on macOS, Windows, Linux and BSD, and on the web via WASM. Siblings with the same conventions: `../designcraft` (InDesign-class, the closest reference), `../photocraft` (Photoshop), `../vectorcraft` (Illustrator), `../printcraft` (Acrobat), `../filmcraft` (Premiere), `../lightcraft` (Lightroom), `../effectcraft` (After Effects).
+GridCraft is a clean-room, open-source, Rust-native spreadsheet targeting Microsoft Excel parity — and superiority (speed, openness, agent control). It runs natively on macOS, Windows, Linux and BSD, and on the web via WASM. Siblings with the same conventions: `../designcraft` (InDesign-class, the closest reference), `../photocraft` (Photoshop), `../vectorcraft` (Illustrator), `../printcraft` (Acrobat), `../filmcraft` (Premiere), `../lightcraft` (Lightroom), `../effectcraft` (After Effects).
Standards and learnings shared across the crafting apps live in `../../craftrules` (checked out next to the craft apps, or `storytold/craftrules`). Read its `AGENTS.md` at the start of a session, follow its standards, and contribute reusable learnings back there. Never code: repos don't share code.
@@ -12,7 +12,7 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
## Assets and iconography — absolute rule
**No Microsoft, Adobe, Avid, Autodesk (or any other vendor's) iconography, images, artwork, fonts, templates, themes or sample files — ever.** This is the most important rule in this repository; breaking it is a serious failure.
-- Every asset in the repo is original work by SheetCraft contributors, or public domain / CC0, or open source / Creative Commons that allows redistribution (or licensed open source by the contributor who created it).
+- Every asset in the repo is original work by GridCraft contributors, or public domain / CC0, or open source / Creative Commons that allows redistribution (or licensed open source by the contributor who created it).
- Every asset file has a row in [`ATTRIBUTION.md`](ATTRIBUTION.md) (author, source, licence). `cargo xtask assets` enforces it. Adding an asset without a row is a bug.
- Prefer art generated in code: the UI icon set (`crates/ui-egui/src/icons.rs`), table and cell style palettes, themes and sample workbooks are drawn/defined in code and original.
- Never copy Office's icons, ribbon artwork, theme XML, table-style definitions, templates, clip art or wording beyond feature names. Excel feature *names* (menu labels, function names) are fine.
@@ -20,7 +20,7 @@ Standards and learnings shared across the crafting apps live in `../../craftrule
- The ArtCraft brand files in `docs/brand/` are ArtCraft trademarks under `docs/brand/LICENSE-brand.txt`, the one exception.
## Never crash
-People trust SheetCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
+People trust GridCraft with their numbers; a crash loses their work. **This outranks feature work.** Standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md).
- **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`).
- **Errors are `Result`** through the crate's error type and `?`.
- **Input-derived numbers are hostile** (files, formulas, commands, MCP/control params): `get()` instead of indexing, checked/saturating arithmetic, cap input-sized allocations (huge ranges, `REPT`, `SEQUENCE`, whole-column references).
@@ -41,10 +41,10 @@ People trust SheetCraft with their numbers; a crash loses their work. **This out
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, assets, layers, wasm). One task id per commit (`M2.1: borders gallery`).
## Running and looking at the app
-- `cargo run --release -p sheetcraft -- --sample sales --control 7979` (sample workbook + control channel).
+- `cargo run --release -p gridcraft -- --sample sales --control 7979` (sample workbook + control channel).
- Drive it: JSON lines on `127.0.0.1:7979`, e.g. `{"id":1,"method":"engine.execute","params":{"command":"cell.set","params":{"cell":"B2","input":"=SUM(A1:A9)"}}}` then `{"id":2,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}`. Methods: `crates/ui-egui/src/control.rs`, docs: `docs/control-protocol.md`.
-- **Offscreen UI render** (no window): `cargo run --release -p sheetcraft-cli -- snapshot --sample sales out.png`.
-- Headless: `sheetcraft-cli eval '=SUM(1,2,3)'`, `sheetcraft-cli run --in book.xlsx --cmd 'home.bold={"range":"A1:C1"}' --out book.xlsx`, `sheetcraft-cli mcp`.
+- **Offscreen UI render** (no window): `cargo run --release -p gridcraft-cli -- snapshot --sample sales out.png`.
+- Headless: `gridcraft-cli eval '=SUM(1,2,3)'`, `gridcraft-cli run --in book.xlsx --cmd 'home.bold={"range":"A1:C1"}' --out book.xlsx`, `gridcraft-cli mcp`.
- **For UI work, look at the result** (snapshot PNG) and compare with `plan/excel/observed-ui.md`.
- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`. macOS has no `timeout`; use `perl -e 'alarm 60; exec @ARGV' cmd`.
- Parallel agents: separate `CARGO_TARGET_DIR` per agent; edit only the crates you own; delete your target dir when done.
diff --git a/Cargo.lock b/Cargo.lock
index 63a897c..b475d3b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1390,6 +1390,175 @@ dependencies = [
"windows",
]
+[[package]]
+name = "gridcraft"
+version = "0.1.0"
+dependencies = [
+ "eframe",
+ "egui",
+ "gridcraft-engine",
+ "gridcraft-ui-egui",
+ "image",
+ "log",
+ "muda",
+ "rfd",
+ "serde_json",
+ "winresource",
+]
+
+[[package]]
+name = "gridcraft-calc"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+ "gridcraft-formula",
+ "gridcraft-functions",
+ "gridcraft-model",
+ "gridcraft-numfmt",
+ "log",
+]
+
+[[package]]
+name = "gridcraft-chart"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-calc",
+ "gridcraft-core",
+ "gridcraft-model",
+ "gridcraft-numfmt",
+ "serde",
+]
+
+[[package]]
+name = "gridcraft-cli"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-engine",
+ "gridcraft-mcp",
+ "log",
+ "serde_json",
+]
+
+[[package]]
+name = "gridcraft-core"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "gridcraft-engine"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-calc",
+ "gridcraft-chart",
+ "gridcraft-core",
+ "gridcraft-formula",
+ "gridcraft-functions",
+ "gridcraft-model",
+ "gridcraft-numfmt",
+ "gridcraft-pdf",
+ "gridcraft-xlsx",
+ "image",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "gridcraft-formula"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+ "serde",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "gridcraft-functions"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+ "regex",
+]
+
+[[package]]
+name = "gridcraft-mcp"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-engine",
+ "log",
+ "serde_json",
+]
+
+[[package]]
+name = "gridcraft-model"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+ "gridcraft-formula",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "gridcraft-numfmt"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+]
+
+[[package]]
+name = "gridcraft-pdf"
+version = "0.1.0"
+
+[[package]]
+name = "gridcraft-ui-egui"
+version = "0.1.0"
+dependencies = [
+ "egui",
+ "egui_kittest",
+ "gridcraft-chart",
+ "gridcraft-engine",
+ "image",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "gridcraft-web"
+version = "0.1.0"
+dependencies = [
+ "eframe",
+ "egui",
+ "gridcraft-engine",
+ "gridcraft-ui-egui",
+ "js-sys",
+ "log",
+ "rfd",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gridcraft-xlsx"
+version = "0.1.0"
+dependencies = [
+ "gridcraft-core",
+ "gridcraft-formula",
+ "gridcraft-model",
+ "quick-xml 0.38.4",
+ "thiserror 2.0.21",
+ "zip",
+]
+
[[package]]
name = "guillotiere"
version = "0.7.0"
@@ -2833,175 +3002,6 @@ dependencies = [
"serde_core",
]
-[[package]]
-name = "sheetcraft"
-version = "0.1.0"
-dependencies = [
- "eframe",
- "egui",
- "image",
- "log",
- "muda",
- "rfd",
- "serde_json",
- "sheetcraft-engine",
- "sheetcraft-ui-egui",
- "winresource",
-]
-
-[[package]]
-name = "sheetcraft-calc"
-version = "0.1.0"
-dependencies = [
- "log",
- "sheetcraft-core",
- "sheetcraft-formula",
- "sheetcraft-functions",
- "sheetcraft-model",
- "sheetcraft-numfmt",
-]
-
-[[package]]
-name = "sheetcraft-chart"
-version = "0.1.0"
-dependencies = [
- "serde",
- "sheetcraft-calc",
- "sheetcraft-core",
- "sheetcraft-model",
- "sheetcraft-numfmt",
-]
-
-[[package]]
-name = "sheetcraft-cli"
-version = "0.1.0"
-dependencies = [
- "log",
- "serde_json",
- "sheetcraft-engine",
- "sheetcraft-mcp",
-]
-
-[[package]]
-name = "sheetcraft-core"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "sheetcraft-engine"
-version = "0.1.0"
-dependencies = [
- "image",
- "log",
- "serde",
- "serde_json",
- "sheetcraft-calc",
- "sheetcraft-chart",
- "sheetcraft-core",
- "sheetcraft-formula",
- "sheetcraft-functions",
- "sheetcraft-model",
- "sheetcraft-numfmt",
- "sheetcraft-pdf",
- "sheetcraft-xlsx",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "sheetcraft-formula"
-version = "0.1.0"
-dependencies = [
- "serde",
- "sheetcraft-core",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "sheetcraft-functions"
-version = "0.1.0"
-dependencies = [
- "regex",
- "sheetcraft-core",
-]
-
-[[package]]
-name = "sheetcraft-mcp"
-version = "0.1.0"
-dependencies = [
- "log",
- "serde_json",
- "sheetcraft-engine",
-]
-
-[[package]]
-name = "sheetcraft-model"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "sheetcraft-core",
- "sheetcraft-formula",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "sheetcraft-numfmt"
-version = "0.1.0"
-dependencies = [
- "sheetcraft-core",
-]
-
-[[package]]
-name = "sheetcraft-pdf"
-version = "0.1.0"
-
-[[package]]
-name = "sheetcraft-ui-egui"
-version = "0.1.0"
-dependencies = [
- "egui",
- "egui_kittest",
- "image",
- "log",
- "serde",
- "serde_json",
- "sheetcraft-chart",
- "sheetcraft-engine",
-]
-
-[[package]]
-name = "sheetcraft-web"
-version = "0.1.0"
-dependencies = [
- "eframe",
- "egui",
- "js-sys",
- "log",
- "rfd",
- "serde_json",
- "sheetcraft-engine",
- "sheetcraft-ui-egui",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
-[[package]]
-name = "sheetcraft-xlsx"
-version = "0.1.0"
-dependencies = [
- "quick-xml 0.38.4",
- "sheetcraft-core",
- "sheetcraft-formula",
- "sheetcraft-model",
- "thiserror 2.0.21",
- "zip",
-]
-
[[package]]
name = "shlex"
version = "2.0.1"
@@ -4288,8 +4288,8 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
name = "xtask"
version = "0.1.0"
dependencies = [
+ "gridcraft-engine",
"serde_json",
- "sheetcraft-engine",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 2bfcd20..a7f99e0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,29 +1,29 @@
[workspace]
resolver = "3"
members = ["crates/*", "apps/*", "xtask"]
-default-members = ["crates/*", "apps/sheetcraft", "apps/sheetcraft-cli", "xtask"]
+default-members = ["crates/*", "apps/gridcraft", "apps/gridcraft-cli", "xtask"]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
-repository = "https://github.com/storytold/sheetcraft"
+repository = "https://github.com/storytold/gridcraft"
[workspace.dependencies]
# internal
-sheetcraft-core = { path = "crates/core" }
-sheetcraft-numfmt = { path = "crates/numfmt" }
-sheetcraft-formula = { path = "crates/formula" }
-sheetcraft-functions = { path = "crates/functions" }
-sheetcraft-model = { path = "crates/model" }
-sheetcraft-calc = { path = "crates/calc" }
-sheetcraft-xlsx = { path = "crates/xlsx" }
-sheetcraft-chart = { path = "crates/chart" }
-sheetcraft-engine = { path = "crates/engine" }
-sheetcraft-ui-egui = { path = "crates/ui-egui" }
-sheetcraft-mcp = { path = "crates/mcp" }
-sheetcraft-pdf = { path = "crates/pdf" }
+gridcraft-core = { path = "crates/core" }
+gridcraft-numfmt = { path = "crates/numfmt" }
+gridcraft-formula = { path = "crates/formula" }
+gridcraft-functions = { path = "crates/functions" }
+gridcraft-model = { path = "crates/model" }
+gridcraft-calc = { path = "crates/calc" }
+gridcraft-xlsx = { path = "crates/xlsx" }
+gridcraft-chart = { path = "crates/chart" }
+gridcraft-engine = { path = "crates/engine" }
+gridcraft-ui-egui = { path = "crates/ui-egui" }
+gridcraft-mcp = { path = "crates/mcp" }
+gridcraft-pdf = { path = "crates/pdf" }
# foundation
thiserror = "2"
diff --git a/LICENSE-MIT b/LICENSE-MIT
index 71b5437..eba4152 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 ArtCraft Team and the SheetCraft contributors
+Copyright (c) 2026 ArtCraft Team and the GridCraft contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/NOTICE b/NOTICE
index 1d9ddbe..1e3668b 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
-SheetCraft
-Copyright (c) 2026 ArtCraft Team and the SheetCraft contributors.
+GridCraft
+Copyright (c) 2026 ArtCraft Team and the GridCraft contributors.
Licensed under the MIT License or the Apache License, Version 2.0, at your option
(LICENSE-MIT, LICENSE-APACHE).
@@ -12,5 +12,5 @@ Third-party material
This product bundles third-party material under its own licenses, listed below. The per-file
list (author, source, license) is in ATTRIBUTION.md.
-None: every bundled asset is original to SheetCraft. Builds made with craft-fonts
+None: every bundled asset is original to GridCraft. Builds made with craft-fonts
(CRAFT_FONTS_DIR) embed the fonts listed in ATTRIBUTION.md under the SIL Open Font License 1.1.
diff --git a/README.md b/README.md
index c324e27..ff101ed 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-SheetCraft
+GridCraft
Spreadsheets and data; an open-source, clean-room reimplementation of Microsoft Excel, rebuilt in pure Rust.
@@ -31,7 +31,7 @@
- SheetCraft on getartcraft.com ·
+ GridCraft on getartcraft.com ·
ArtCraft ·
All Crafting Apps
@@ -39,8 +39,8 @@
-
- Quarterly Sales Dashboard : a table with a totals row, conditional formatting, sparklines and two live charts — built entirely from SheetCraft commands (File → New → Sample).
+
+ Quarterly Sales Dashboard : a table with a totals row, conditional formatting, sparklines and two live charts — built entirely from GridCraft commands (File → New → Sample).
> [!NOTE]
@@ -50,7 +50,7 @@
A look around ·
- Why SheetCraft ·
+ Why GridCraft ·
Quick start ·
For agents ·
Architecture ·
@@ -72,11 +72,11 @@
-## Why SheetCraft
+## Why GridCraft
- **Familiar.** Excel's ribbon, formula bar, Name Box, sheet tabs, keyboard shortcuts, fill
handle, point-mode formula entry with coloured references, Format Cells, Paste Special,
- conditional formatting, tables, charts and more. If you know Excel, you already know SheetCraft.
+ conditional formatting, tables, charts and more. If you know Excel, you already know GridCraft.
- **Compatible.** XLSX is the native format: styles, themes, formulas (including dynamic arrays),
tables, conditional formats, validation, comments, hyperlinks, charts, pictures, sparklines and
print settings round-trip. CSV and TSV too.
@@ -94,18 +94,18 @@
## Quick start
```sh
-cargo run --release -p sheetcraft # desktop app
-cargo run --release -p sheetcraft -- --sample sales # open a sample (sales, budget, grades)
-cargo run --release -p sheetcraft -- book.xlsx # open a workbook
-cargo run --release -p sheetcraft -- --sample sales --control 7979 # + JSON control channel
+cargo run --release -p gridcraft # desktop app
+cargo run --release -p gridcraft -- --sample sales # open a sample (sales, budget, grades)
+cargo run --release -p gridcraft -- book.xlsx # open a workbook
+cargo run --release -p gridcraft -- --sample sales --control 7979 # + JSON control channel
cargo xtask ci # fmt, clippy, tests, assets, layering, wasm
```
### Web
```sh
-cd apps/sheetcraft-web && trunk build --release # → dist/web (serve it with any static server)
-cd apps/sheetcraft-web && trunk serve --release # http://127.0.0.1:8771 (?sample=sales opens a sample)
+cd apps/gridcraft-web && trunk build --release # → dist/web (serve it with any static server)
+cd apps/gridcraft-web && trunk serve --release # http://127.0.0.1:8771 (?sample=sales opens a sample)
```
You need [trunk](https://trunkrs.dev) and the `wasm32-unknown-unknown` target. Open uses the
@@ -114,23 +114,23 @@ browser's file picker (dropping files works too) and Save downloads the workbook
## For agents: MCP, CLI and the control channel
```sh
-claude mcp add sheetcraft -- sheetcraft-cli mcp # headless MCP server
-sheetcraft-cli mcp --connect 7979 # MCP bridged to a running window
-sheetcraft-cli eval '=XLOOKUP("b",{"a","b"},{1,2})' # → 2
-sheetcraft-cli run --sample budget --cmd 'home.bold={"range":"B4:F4"}' --out budget.xlsx
-sheetcraft-cli cat budget.xlsx --range B4:F15 # aligned text table
-sheetcraft-cli commands --search chart # every command and its params
+claude mcp add gridcraft -- gridcraft-cli mcp # headless MCP server
+gridcraft-cli mcp --connect 7979 # MCP bridged to a running window
+gridcraft-cli eval '=XLOOKUP("b",{"a","b"},{1,2})' # → 2
+gridcraft-cli run --sample budget --cmd 'home.bold={"range":"B4:F4"}' --out budget.xlsx
+gridcraft-cli cat budget.xlsx --range B4:F15 # aligned text table
+gridcraft-cli commands --search chart # every command and its params
```
The MCP server offers tools such as `read_range`, `write_range`, `set_cell`, `evaluate_formula`,
`format_range`, `insert_chart`, `create_table`, `sort_range`, `filter` and `execute_command`
-(which reaches every one of SheetCraft's commands). The desktop app's control channel adds real
+(which reaches every one of GridCraft's commands). The desktop app's control channel adds real
pointer and keyboard input, dialogs and screenshots. See [`docs/mcp.md`](docs/mcp.md),
[`docs/cli.md`](docs/cli.md) and [`docs/control-protocol.md`](docs/control-protocol.md).
## Architecture
-SheetCraft is an engine-first Cargo workspace with enforced layering (`cargo xtask layers`). The
+GridCraft is an engine-first Cargo workspace with enforced layering (`cargo xtask layers`). The
egui frontend is a separate crate, so the UI can be swapped without touching the engine.
| Layer | Crates |
@@ -141,7 +141,7 @@ egui frontend is a separate crate, so the UI can be swapped without touching the
| L3 | `chart` (toolkit-free chart layout and rendering) |
| L5 | `engine` (session, commands, history, clipboard, fill, sort, filter, file I/O) |
| L6 | `ui-egui` (Excel-style UI, control channel) · `mcp` (MCP server) |
-| L7 | `apps/sheetcraft`, `apps/sheetcraft-cli`, `apps/sheetcraft-web` |
+| L7 | `apps/gridcraft`, `apps/gridcraft-cli`, `apps/gridcraft-web` |
- **Contributor and agent rules** (clean-room, asset policy, never-crash, quality gates): [`AGENTS.md`](AGENTS.md)
- **Bundled assets:** each one is listed with its licence in [`ATTRIBUTION.md`](ATTRIBUTION.md)
@@ -149,14 +149,14 @@ egui frontend is a separate crate, so the UI can be swapped without touching the
## Roadmap
-SheetCraft is pre-alpha and moving fast. Today it covers 60% of Excel's ribbon and menu
+GridCraft is pre-alpha and moving fast. Today it covers 60% of Excel's ribbon and menu
commands ([`docs/parity.md`](docs/parity.md)) and about 93% of its worksheet functions. PivotTables,
printing and PDF, page layout view, deeper chart formatting and scripting come next. The plan
and estimates are in [ROADMAP.md](ROADMAP.md).
## The Crafting Apps
-SheetCraft is one of the **Crafting Apps**: free, open-source creative tools from the
+GridCraft is one of the **Crafting Apps**: free, open-source creative tools from the
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
stand on its own.
@@ -169,7 +169,7 @@ stand on its own.
| | **PrintCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/printcraft) | [Website](https://getartcraft.com/apps/printcraft) |
| | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
| | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
-| | **SheetCraft** | **Spreadsheets and data · you are here** | [GitHub](https://github.com/storytold/sheetcraft) | [Website](https://getartcraft.com/apps/sheetcraft) |
+| | **GridCraft** | **Spreadsheets and data · you are here** | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
@@ -192,27 +192,27 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud
discord.gg/artcraft ·
getartcraft.com ·
The Crafting Apps ·
- SheetCraft
+ GridCraft
## License and credits
-SheetCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
-Copyright (c) 2026 ArtCraft Team and the SheetCraft contributors. Required notices are in [NOTICE](NOTICE).
+GridCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
+Copyright (c) 2026 ArtCraft Team and the GridCraft contributors. Required notices are in [NOTICE](NOTICE).
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
with its author, source and license in [ATTRIBUTION.md](ATTRIBUTION.md).
All UI icons are drawn in code and are original; the sample workbooks and every screenshot in
-this README were made with SheetCraft itself. SheetCraft bundles no fonts: it uses the fonts
+this README were made with GridCraft itself. GridCraft bundles no fonts: it uses the fonts
installed on your system.
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
-part of this repository and SheetCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
+part of this repository and GridCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
Forks and modified versions must remove them.
-Microsoft, Excel and Microsoft 365 are trademarks or registered trademarks of Microsoft Corporation in the United States and/or other countries. SheetCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation; these names are used only to describe the workflows it is compatible with.
+Microsoft, Excel and Microsoft 365 are trademarks or registered trademarks of Microsoft Corporation in the United States and/or other countries. GridCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation; these names are used only to describe the workflows it is compatible with.
diff --git a/ROADMAP.md b/ROADMAP.md
index 61538de..b89da9e 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,6 +1,6 @@
-# SheetCraft roadmap
+# GridCraft roadmap
-SheetCraft aims at full Microsoft Excel parity — and to be better: faster, open (XLSX is the
+GridCraft aims at full Microsoft Excel parity — and to be better: faster, open (XLSX is the
native format), fully scriptable by agents (MCP + control channel + CLI), and available on the
web.
@@ -81,4 +81,4 @@ work** (≈ 9–10 days running continuously with 3–4 parallel agents), broken
- `cargo xtask parity` — catalog coverage (feature names from Excel's ribbon and menus).
- `cargo test --workspace` — engine, formula, function, XLSX and UI behaviour.
-- `cargo run --release -p sheetcraft-ui-egui --example snapshot -- --sample sales out.png` — look at the UI offscreen.
+- `cargo run --release -p gridcraft-ui-egui --example snapshot -- --sample sales out.png` — look at the UI offscreen.
diff --git a/apps/sheetcraft-cli/Cargo.toml b/apps/gridcraft-cli/Cargo.toml
similarity index 50%
rename from apps/sheetcraft-cli/Cargo.toml
rename to apps/gridcraft-cli/Cargo.toml
index 571905a..a55279d 100644
--- a/apps/sheetcraft-cli/Cargo.toml
+++ b/apps/gridcraft-cli/Cargo.toml
@@ -1,20 +1,20 @@
[package]
-name = "sheetcraft-cli"
+name = "gridcraft-cli"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft command line: inspect, convert, evaluate, script and serve MCP without a window"
+description = "GridCraft command line: inspect, convert, evaluate, script and serve MCP without a window"
publish = false
[lints]
workspace = true
[[bin]]
-name = "sheetcraft-cli"
+name = "gridcraft-cli"
path = "src/main.rs"
[dependencies]
-sheetcraft-engine = { workspace = true }
-sheetcraft-mcp = { workspace = true }
+gridcraft-engine = { workspace = true }
+gridcraft-mcp = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
diff --git a/apps/sheetcraft-cli/src/main.rs b/apps/gridcraft-cli/src/main.rs
similarity index 89%
rename from apps/sheetcraft-cli/src/main.rs
rename to apps/gridcraft-cli/src/main.rs
index b933ef1..561162f 100644
--- a/apps/sheetcraft-cli/src/main.rs
+++ b/apps/gridcraft-cli/src/main.rs
@@ -1,23 +1,23 @@
-//! Headless SheetCraft.
+//! Headless GridCraft.
//!
//! ```text
-//! sheetcraft-cli info [--json]
-//! sheetcraft-cli convert [--sheet NAME]
-//! sheetcraft-cli eval [--in FILE] [--sheet S] [--cell A1] [--json]
-//! sheetcraft-cli cat [--range A1:D20] [--sheet S] [--formulas] [--csv]
-//! sheetcraft-cli run [--in FILE | --sample NAME] [--cmd 'id={json}']... [--script FILE.jsonl] [--out FILE] [--print RANGE] [--quiet]
-//! sheetcraft-cli commands [--json] [--search X]
-//! sheetcraft-cli functions [--json] [--search X] [--category C]
-//! sheetcraft-cli mcp [--connect PORT] [--in FILE | --sample NAME]
-//! sheetcraft-cli send [json]
-//! sheetcraft-cli version | --version
+//! gridcraft-cli info [--json]
+//! gridcraft-cli convert [--sheet NAME]
+//! gridcraft-cli eval [--in FILE] [--sheet S] [--cell A1] [--json]
+//! gridcraft-cli cat [--range A1:D20] [--sheet S] [--formulas] [--csv]
+//! gridcraft-cli run [--in FILE | --sample NAME] [--cmd 'id={json}']... [--script FILE.jsonl] [--out FILE] [--print RANGE] [--quiet]
+//! gridcraft-cli commands [--json] [--search X]
+//! gridcraft-cli functions [--json] [--search X] [--category C]
+//! gridcraft-cli mcp [--connect PORT] [--in FILE | --sample NAME]
+//! gridcraft-cli send [json]
+//! gridcraft-cli version | --version
//! ```
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
use std::process::ExitCode;
-/// `println!` that ends the program quietly when stdout is closed (`sheetcraft-cli commands |
+/// `println!` that ends the program quietly when stdout is closed (`gridcraft-cli commands |
/// head`) instead of panicking on a broken pipe.
macro_rules! outln {
($($arg:tt)*) => {{
@@ -28,21 +28,21 @@ macro_rules! outln {
}};
}
+use gridcraft_engine::Session;
use serde_json::{Value, json};
-use sheetcraft_engine::Session;
const USAGE: &str = "usage:
- sheetcraft-cli info [--json] sheets, used ranges, tables, charts, names
- sheetcraft-cli convert [--sheet NAME] xlsx/csv/tsv/json/html by extension
- sheetcraft-cli eval [--in FILE] [--sheet S] [--cell A1] [--json]
- sheetcraft-cli cat [--range A1:D20] [--sheet S] [--formulas] [--csv]
- sheetcraft-cli run [--in FILE | --sample NAME] [--cmd 'id={json}']... [--script FILE.jsonl]
+ gridcraft-cli info [--json] sheets, used ranges, tables, charts, names
+ gridcraft-cli convert [--sheet NAME] xlsx/csv/tsv/json/html by extension
+ gridcraft-cli eval [--in FILE] [--sheet S] [--cell A1] [--json]
+ gridcraft-cli cat [--range A1:D20] [--sheet S] [--formulas] [--csv]
+ gridcraft-cli run [--in FILE | --sample NAME] [--cmd 'id={json}']... [--script FILE.jsonl]
[--out FILE] [--print RANGE] [--quiet]
- sheetcraft-cli commands [--json] [--search X]
- sheetcraft-cli functions [--json] [--search X] [--category C]
- sheetcraft-cli mcp [--connect PORT] [--in FILE | --sample NAME]
- sheetcraft-cli send [json]
- sheetcraft-cli version";
+ gridcraft-cli commands [--json] [--search X]
+ gridcraft-cli functions [--json] [--search X] [--category C]
+ gridcraft-cli mcp [--connect PORT] [--in FILE | --sample NAME]
+ gridcraft-cli send [json]
+ gridcraft-cli version";
/// stdout went away. A reader that stopped early (a closed pipe) ends the program quietly; any
/// other write error is reported.
@@ -50,7 +50,7 @@ fn stdout_failed(e: std::io::Error) -> ! {
if e.kind() == std::io::ErrorKind::BrokenPipe {
std::process::exit(0);
}
- eprintln!("sheetcraft-cli: can't write to stdout: {e}");
+ eprintln!("gridcraft-cli: can't write to stdout: {e}");
std::process::exit(1);
}
@@ -59,7 +59,7 @@ fn main() -> ExitCode {
let rest = args.get(1..).unwrap_or_default();
let r = match args.first().map(String::as_str) {
Some("--version" | "-V" | "version") => {
- outln!("sheetcraft-cli {}", env!("CARGO_PKG_VERSION"));
+ outln!("gridcraft-cli {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
Some("--help" | "-h" | "help") => {
@@ -81,7 +81,7 @@ fn main() -> ExitCode {
match r {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
- eprintln!("sheetcraft-cli: {e}");
+ eprintln!("gridcraft-cli: {e}");
ExitCode::FAILURE
}
}
@@ -258,14 +258,14 @@ fn convert(args: &[String]) -> Result<(), String> {
let input = a.pos(0, "")?;
let out = a.pos(1, "")?;
a.no_extra(2)?;
- if sheetcraft_engine::io::FileKind::from_path(out).is_none() {
+ if gridcraft_engine::io::FileKind::from_path(out).is_none() {
return Err(format!("{out}: unknown output format (use .xlsx, .csv, .tsv, .json or .html)"));
}
let mut s = open(input)?;
activate_sheet(&mut s, a.opt("sheet"))?;
let d = s.doc().map_err(|e| e.to_string())?;
- let bytes = sheetcraft_engine::io::save_bytes(&d.wb, out).map_err(|e| e.to_string())?;
- sheetcraft_engine::io::write_file(out, &bytes).map_err(|e| e.to_string())?;
+ let bytes = gridcraft_engine::io::save_bytes(&d.wb, out).map_err(|e| e.to_string())?;
+ gridcraft_engine::io::write_file(out, &bytes).map_err(|e| e.to_string())?;
eprintln!("wrote {out} ({} bytes)", bytes.len());
Ok(())
}
@@ -480,9 +480,9 @@ fn functions(args: &[String]) -> Result<(), String> {
}
/// `mcp` (headless, in-process engine) or `mcp --connect PORT|HOST:PORT` (drive a running app
-/// started with `sheetcraft --control PORT`). JSON-RPC on stdin/stdout; logs on stderr.
+/// started with `gridcraft --control PORT`). JSON-RPC on stdin/stdout; logs on stderr.
fn mcp(args: &[String]) -> Result<(), String> {
- use sheetcraft_mcp::{Backend, Headless, Remote, Server, control_addr};
+ use gridcraft_mcp::{Backend, Headless, Remote, Server, control_addr};
let a = Args::parse(args, &["connect", "in", "sample"], &[])?;
a.no_extra(0)?;
let backend: Box = match a.opt("connect") {
@@ -490,7 +490,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
let addr = control_addr(c);
Box::new(
Remote::connect(&addr)
- .map_err(|e| format!("cannot connect to the SheetCraft app at {addr}: {e} (start it with `sheetcraft --control PORT`)"))?,
+ .map_err(|e| format!("cannot connect to the GridCraft app at {addr}: {e} (start it with `gridcraft --control PORT`)"))?,
)
}
None => {
@@ -506,7 +506,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
Box::new(Headless::with_session(session))
}
};
- eprintln!("sheetcraft-cli: MCP server on stdio ({})", backend.describe());
+ eprintln!("gridcraft-cli: MCP server on stdio ({})", backend.describe());
let stdin = std::io::stdin();
let stdout = std::io::stdout();
Server::new(backend).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
@@ -514,7 +514,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
/// `send PORT METHOD [JSON]`: one control-channel request to a running app.
fn send(args: &[String]) -> Result<(), String> {
- use sheetcraft_mcp::{Backend, Remote, control_addr};
+ use gridcraft_mcp::{Backend, Remote, control_addr};
let port = args.first().ok_or_else(|| format!("missing \n{USAGE}"))?;
let method = args.get(1).ok_or_else(|| format!("missing \n{USAGE}"))?;
let params: Value = match args.get(2) {
@@ -526,7 +526,7 @@ fn send(args: &[String]) -> Result<(), String> {
}
let addr = control_addr(port);
let mut r = Remote::connect(&addr)
- .map_err(|e| format!("cannot connect to the SheetCraft app at {addr}: {e} (start it with `sheetcraft --control {port}`)"))?;
+ .map_err(|e| format!("cannot connect to the GridCraft app at {addr}: {e} (start it with `gridcraft --control {port}`)"))?;
let v = r.call(method, params)?;
outln!("{}", pretty(&v));
Ok(())
diff --git a/apps/sheetcraft-cli/tests/cli.rs b/apps/gridcraft-cli/tests/cli.rs
similarity index 89%
rename from apps/sheetcraft-cli/tests/cli.rs
rename to apps/gridcraft-cli/tests/cli.rs
index f85903f..9a7ffba 100644
--- a/apps/sheetcraft-cli/tests/cli.rs
+++ b/apps/gridcraft-cli/tests/cli.rs
@@ -5,29 +5,29 @@ use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
fn bin() -> Command {
- Command::new(env!("CARGO_BIN_EXE_sheetcraft-cli"))
+ Command::new(env!("CARGO_BIN_EXE_gridcraft-cli"))
}
fn run(args: &[&str]) -> Output {
- bin().args(args).output().expect("run sheetcraft-cli")
+ bin().args(args).output().expect("run gridcraft-cli")
}
fn ok(args: &[&str]) -> String {
let o = run(args);
- assert!(o.status.success(), "sheetcraft-cli {args:?} failed: {}", String::from_utf8_lossy(&o.stderr));
+ assert!(o.status.success(), "gridcraft-cli {args:?} failed: {}", String::from_utf8_lossy(&o.stderr));
String::from_utf8(o.stdout).unwrap()
}
fn tmpdir(name: &str) -> PathBuf {
- let d = std::env::temp_dir().join(format!("sheetcraft-cli-test-{}-{name}", std::process::id()));
+ let d = std::env::temp_dir().join(format!("gridcraft-cli-test-{}-{name}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
d
}
#[test]
fn version_and_usage() {
- assert_eq!(ok(&["--version"]).trim(), format!("sheetcraft-cli {}", env!("CARGO_PKG_VERSION")));
- assert_eq!(ok(&["version"]).trim(), format!("sheetcraft-cli {}", env!("CARGO_PKG_VERSION")));
+ assert_eq!(ok(&["--version"]).trim(), format!("gridcraft-cli {}", env!("CARGO_PKG_VERSION")));
+ assert_eq!(ok(&["version"]).trim(), format!("gridcraft-cli {}", env!("CARGO_PKG_VERSION")));
let o = run(&["frobnicate"]);
assert!(!o.status.success());
assert!(String::from_utf8_lossy(&o.stderr).contains("unknown subcommand"));
@@ -147,6 +147,6 @@ fn mcp_over_stdio() {
let text = String::from_utf8(out.stdout).unwrap();
let lines: Vec = text.lines().map(|l| serde_json::from_str(l).unwrap()).collect();
assert_eq!(lines.len(), 2, "{text}");
- assert_eq!(lines[0]["result"]["serverInfo"]["name"], "sheetcraft");
+ assert_eq!(lines[0]["result"]["serverInfo"]["name"], "gridcraft");
assert!(lines[1]["result"]["content"][0]["text"].as_str().unwrap().contains("42"));
}
diff --git a/apps/sheetcraft-web/Cargo.toml b/apps/gridcraft-web/Cargo.toml
similarity index 75%
rename from apps/sheetcraft-web/Cargo.toml
rename to apps/gridcraft-web/Cargo.toml
index aad352e..991c633 100644
--- a/apps/sheetcraft-web/Cargo.toml
+++ b/apps/gridcraft-web/Cargo.toml
@@ -1,6 +1,6 @@
[package]
-name = "sheetcraft-web"
-description = "SheetCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
+name = "gridcraft-web"
+description = "GridCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -10,12 +10,12 @@ publish = false
workspace = true
[[bin]]
-name = "sheetcraft-web"
+name = "gridcraft-web"
path = "src/main.rs"
[dependencies]
-sheetcraft-engine = { workspace = true }
-sheetcraft-ui-egui = { workspace = true }
+gridcraft-engine = { workspace = true }
+gridcraft-ui-egui = { workspace = true }
egui = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
diff --git a/apps/sheetcraft-web/Trunk.toml b/apps/gridcraft-web/Trunk.toml
similarity index 67%
rename from apps/sheetcraft-web/Trunk.toml
rename to apps/gridcraft-web/Trunk.toml
index 948d368..bab4ee5 100644
--- a/apps/sheetcraft-web/Trunk.toml
+++ b/apps/gridcraft-web/Trunk.toml
@@ -1,4 +1,4 @@
-# Build with: trunk build --release (from apps/sheetcraft-web)
+# Build with: trunk build --release (from apps/gridcraft-web)
[build]
target = "index.html"
dist = "../../dist/web"
diff --git a/apps/gridcraft-web/index.html b/apps/gridcraft-web/index.html
new file mode 100644
index 0000000..96d6347
--- /dev/null
+++ b/apps/gridcraft-web/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ GridCraft
+
+
+
+
+
+ Loading GridCraft…
+
+
diff --git a/apps/sheetcraft-web/src/main.rs b/apps/gridcraft-web/src/main.rs
similarity index 74%
rename from apps/sheetcraft-web/src/main.rs
rename to apps/gridcraft-web/src/main.rs
index 599ccdb..58c4f43 100644
--- a/apps/sheetcraft-web/src/main.rs
+++ b/apps/gridcraft-web/src/main.rs
@@ -1,6 +1,6 @@
-//! SheetCraft in the browser.
+//! GridCraft in the browser.
//!
-//! Runs the same [`sheetcraft_ui_egui::SheetApp`] as the desktop app through eframe's web runner
+//! Runs the same [`gridcraft_ui_egui::SheetApp`] as the desktop app through eframe's web runner
//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from
//! this directory (output in `dist/web`).
//!
@@ -21,5 +21,5 @@ fn main() {
#[cfg(not(target_arch = "wasm32"))]
fn main() {
- eprintln!("sheetcraft-web only runs in the browser: build it with `trunk build --release` in apps/sheetcraft-web");
+ eprintln!("gridcraft-web only runs in the browser: build it with `trunk build --release` in apps/gridcraft-web");
}
diff --git a/apps/sheetcraft-web/src/web.rs b/apps/gridcraft-web/src/web.rs
similarity index 94%
rename from apps/sheetcraft-web/src/web.rs
rename to apps/gridcraft-web/src/web.rs
index c867465..c06d890 100644
--- a/apps/sheetcraft-web/src/web.rs
+++ b/apps/gridcraft-web/src/web.rs
@@ -1,11 +1,11 @@
//! The browser shell: web `Services`, drag-and-drop, and the eframe web runner.
-use sheetcraft_engine::Session;
-use sheetcraft_ui_egui::{Inbox, Services, SheetApp};
+use gridcraft_engine::Session;
+use gridcraft_ui_egui::{Inbox, Services, SheetApp};
use wasm_bindgen::JsCast as _;
-const CANVAS_ID: &str = "sheetcraft_canvas";
-const LOADING_ID: &str = "sheetcraft_loading";
+const CANVAS_ID: &str = "gridcraft_canvas";
+const LOADING_ID: &str = "gridcraft_loading";
pub fn start() {
eframe::WebLogger::init(log::LevelFilter::Info).ok();
@@ -46,7 +46,7 @@ pub fn start() {
if let Some(el) = document.get_element_by_id(LOADING_ID) {
match result {
Ok(()) => el.remove(),
- Err(e) => el.set_inner_html(&format!("SheetCraft failed to start: {e:?}
A browser with WebGPU or WebGL2 is required.
")),
+ Err(e) => el.set_inner_html(&format!("GridCraft failed to start: {e:?}
A browser with WebGPU or WebGL2 is required.
")),
}
}
});
diff --git a/apps/sheetcraft/Cargo.toml b/apps/gridcraft/Cargo.toml
similarity index 69%
rename from apps/sheetcraft/Cargo.toml
rename to apps/gridcraft/Cargo.toml
index bbfd32d..0d3e34c 100644
--- a/apps/sheetcraft/Cargo.toml
+++ b/apps/gridcraft/Cargo.toml
@@ -1,18 +1,18 @@
[package]
-name = "sheetcraft"
+name = "gridcraft"
version.workspace = true
edition.workspace = true
license.workspace = true
publish = false
-default-run = "sheetcraft"
-description = "SheetCraft desktop app"
+default-run = "gridcraft"
+description = "GridCraft desktop app"
[lints]
workspace = true
[dependencies]
-sheetcraft-engine = { workspace = true }
-sheetcraft-ui-egui = { workspace = true }
+gridcraft-engine = { workspace = true }
+gridcraft-ui-egui = { workspace = true }
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] }
egui = { workspace = true }
serde_json = { workspace = true }
@@ -20,7 +20,7 @@ log = { workspace = true }
image = { workspace = true }
[build-dependencies]
-# Icon + VERSIONINFO for sheetcraft.exe; build.rs only uses it when the target is Windows.
+# Icon + VERSIONINFO for gridcraft.exe; build.rs only uses it when the target is Windows.
winresource = "0.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
diff --git a/apps/gridcraft/build.rs b/apps/gridcraft/build.rs
new file mode 100644
index 0000000..95d8da3
--- /dev/null
+++ b/apps/gridcraft/build.rs
@@ -0,0 +1,30 @@
+//! Windows only: embed the app icon and version info (VERSIONINFO) into `gridcraft.exe`, so it
+//! shows in Explorer, the taskbar, the Start menu and Alt-Tab.
+//!
+//! On every other target this does nothing. A missing resource compiler is a warning, so a
+//! cross-compile from macOS or Linux still links, unless `GRIDCRAFT_REQUIRE_WINRES=1` turns it
+//! into an error (for release builds).
+
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=../../assets/app-icon/gridcraft.ico");
+ println!("cargo:rerun-if-env-changed=GRIDCRAFT_REQUIRE_WINRES");
+ if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
+ return;
+ }
+ let mut res = winresource::WindowsResource::new();
+ res.set_icon("../../assets/app-icon/gridcraft.ico")
+ .set("ProductName", "GridCraft")
+ .set("FileDescription", "GridCraft spreadsheet")
+ .set("CompanyName", "Learning Machines LLC")
+ .set("LegalCopyright", "Copyright (c) the GridCraft authors. MIT OR Apache-2.0.")
+ .set("OriginalFilename", "gridcraft.exe")
+ .set("InternalName", "gridcraft");
+ if let Err(e) = res.compile() {
+ if std::env::var_os("GRIDCRAFT_REQUIRE_WINRES").is_some() {
+ eprintln!("embedding Windows resources failed: {e}");
+ std::process::exit(1);
+ }
+ println!("cargo:warning=gridcraft.exe built without icon/version resources: {e}");
+ }
+}
diff --git a/apps/sheetcraft/src/control_server.rs b/apps/gridcraft/src/control_server.rs
similarity index 95%
rename from apps/sheetcraft/src/control_server.rs
rename to apps/gridcraft/src/control_server.rs
index 42c388d..bdbc28d 100644
--- a/apps/sheetcraft/src/control_server.rs
+++ b/apps/gridcraft/src/control_server.rs
@@ -6,8 +6,8 @@ use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::Duration;
+use gridcraft_ui_egui::ControlRequest;
use serde_json::{Value as Json, json};
-use sheetcraft_ui_egui::ControlRequest;
const MAX_LINE: usize = 64 * 1024 * 1024;
@@ -21,7 +21,7 @@ pub fn start(port: u16, ctx: egui::Context) -> std::io::Result] [--sample ] [files…]`
+//! Usage: `gridcraft [--control ] [--sample ] [files…]`
//!
-//! `--control ` (or `SHEETCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control
+//! `--control ` (or `GRIDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control
//! server: `{"id":1,"method":"ui.inspect","params":{}}` → `{"id":1,"ok":true,"result":…}`.
-//! See `sheetcraft_ui_egui::control` and `docs/control-protocol.md` for the methods.
+//! See `gridcraft_ui_egui::control` and `docs/control-protocol.md` for the methods.
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
@@ -12,9 +12,9 @@ mod control_server;
#[cfg(target_os = "macos")]
mod native_menu;
+use gridcraft_engine::Session;
+use gridcraft_ui_egui::{Services, SheetApp};
use serde_json::json;
-use sheetcraft_engine::Session;
-use sheetcraft_ui_egui::{Services, SheetApp};
struct App(SheetApp, #[cfg(target_os = "macos")] Option);
@@ -22,7 +22,7 @@ impl eframe::App for App {
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
#[cfg(target_os = "macos")]
{
- if self.1.is_none() && std::env::var_os("SHEETCRAFT_NO_NATIVE_MENU").is_none() {
+ if self.1.is_none() && std::env::var_os("GRIDCRAFT_NO_NATIVE_MENU").is_none() {
self.1 = Some(native_menu::NativeMenu::install(ctx));
}
if let Some(m) = &self.1 {
@@ -50,19 +50,19 @@ impl eframe::App for App {
fn config_dir() -> Option {
if cfg!(target_os = "macos") {
- std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join("Library/Application Support/SheetCraft"))
+ std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join("Library/Application Support/GridCraft"))
} else if cfg!(windows) {
- std::env::var_os("APPDATA").map(|a| std::path::PathBuf::from(a).join("SheetCraft"))
+ std::env::var_os("APPDATA").map(|a| std::path::PathBuf::from(a).join("GridCraft"))
} else {
std::env::var_os("XDG_CONFIG_HOME")
.map(std::path::PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join(".config")))
- .map(|c| c.join("sheetcraft"))
+ .map(|c| c.join("gridcraft"))
}
}
fn load_prefs(app: &mut SheetApp) {
- if std::env::var_os("SHEETCRAFT_NO_PREFS").is_some() {
+ if std::env::var_os("GRIDCRAFT_NO_PREFS").is_some() {
return;
}
let Some(dir) = config_dir() else { return };
@@ -79,7 +79,7 @@ fn load_prefs(app: &mut SheetApp) {
}
fn save_prefs(app: &SheetApp) {
- if std::env::var_os("SHEETCRAFT_NO_PREFS").is_some() {
+ if std::env::var_os("GRIDCRAFT_NO_PREFS").is_some() {
return;
}
let Some(dir) = config_dir() else { return };
@@ -127,7 +127,7 @@ fn services() -> Services {
}
fn icon() -> Option {
- let bytes = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.sheetcraft.png");
+ let bytes = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.gridcraft.png");
let img = image::load_from_memory(bytes).ok()?.to_rgba8();
Some(egui::IconData { width: img.width(), height: img.height(), rgba: img.into_raw() })
}
@@ -135,10 +135,10 @@ fn icon() -> Option {
fn main() -> eframe::Result<()> {
let args: Vec = std::env::args().skip(1).collect();
if args.iter().any(|a| a == "--version" || a == "-V") {
- println!("SheetCraft {}", env!("CARGO_PKG_VERSION"));
+ println!("GridCraft {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
- let mut control_port: Option = std::env::var("SHEETCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
+ let mut control_port: Option = std::env::var("GRIDCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
let mut sample: Option = None;
let mut files: Vec = Vec::new();
let mut i = 0;
@@ -175,10 +175,10 @@ fn main() -> eframe::Result<()> {
let control_port = control_port;
load_prefs(&mut app);
let mut viewport = egui::ViewportBuilder::default()
- .with_title("SheetCraft")
+ .with_title("GridCraft")
.with_inner_size([1440.0, 900.0])
.with_min_inner_size([640.0, 420.0])
- .with_app_id("ai.storyteller.sheetcraft")
+ .with_app_id("ai.storyteller.gridcraft")
.with_drag_and_drop(true);
if cfg!(target_os = "macos") {
viewport = viewport.with_fullsize_content_view(true).with_titlebar_shown(false).with_title_shown(false);
@@ -188,7 +188,7 @@ fn main() -> eframe::Result<()> {
}
let options = eframe::NativeOptions { viewport, ..Default::default() };
eframe::run_native(
- "SheetCraft",
+ "GridCraft",
options,
Box::new(move |cc| {
SheetApp::setup_context(&cc.egui_ctx, app.ui.dark);
diff --git a/apps/sheetcraft/src/native_menu.rs b/apps/gridcraft/src/native_menu.rs
similarity index 97%
rename from apps/sheetcraft/src/native_menu.rs
rename to apps/gridcraft/src/native_menu.rs
index 679c8ac..1a48f65 100644
--- a/apps/sheetcraft/src/native_menu.rs
+++ b/apps/gridcraft/src/native_menu.rs
@@ -4,10 +4,10 @@
use std::str::FromStr;
use std::sync::mpsc::{Receiver, channel};
+use gridcraft_ui_egui::SheetApp;
use muda::accelerator::Accelerator;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use serde_json::{Value, json};
-use sheetcraft_ui_egui::SheetApp;
type Entry = (&'static str, &'static str, Option<&'static str>);
@@ -170,7 +170,7 @@ fn tree() -> Vec<(&'static str, Vec)> {
"Help",
vec![
("Agent Control (MCP)…", "dialog:agents", None),
- ("SheetCraft on getartcraft.com", "url:https://getartcraft.com/apps/sheetcraft", None),
+ ("GridCraft on getartcraft.com", "url:https://getartcraft.com/apps/gridcraft", None),
("Join the ArtCraft Discord…", "url:https://discord.gg/artcraft", None),
],
),
@@ -185,8 +185,8 @@ pub struct NativeMenu {
impl NativeMenu {
pub fn install(ctx: &egui::Context) -> NativeMenu {
let menu = Menu::new();
- let app_menu = Submenu::new("SheetCraft", true);
- let about = MenuItem::with_id("url:https://getartcraft.com/apps/sheetcraft", "About SheetCraft", true, None);
+ let app_menu = Submenu::new("GridCraft", true);
+ let about = MenuItem::with_id("url:https://getartcraft.com/apps/gridcraft", "About GridCraft", true, None);
let _ = app_menu.append_items(&[
&about,
&PredefinedMenuItem::separator(),
diff --git a/apps/sheetcraft-web/index.html b/apps/sheetcraft-web/index.html
deleted file mode 100644
index bf979bd..0000000
--- a/apps/sheetcraft-web/index.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- SheetCraft
-
-
-
-
-
- Loading SheetCraft…
-
-
diff --git a/apps/sheetcraft/build.rs b/apps/sheetcraft/build.rs
deleted file mode 100644
index 969f956..0000000
--- a/apps/sheetcraft/build.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! Windows only: embed the app icon and version info (VERSIONINFO) into `sheetcraft.exe`, so it
-//! shows in Explorer, the taskbar, the Start menu and Alt-Tab.
-//!
-//! On every other target this does nothing. A missing resource compiler is a warning, so a
-//! cross-compile from macOS or Linux still links, unless `SHEETCRAFT_REQUIRE_WINRES=1` turns it
-//! into an error (for release builds).
-
-fn main() {
- println!("cargo:rerun-if-changed=build.rs");
- println!("cargo:rerun-if-changed=../../assets/app-icon/sheetcraft.ico");
- println!("cargo:rerun-if-env-changed=SHEETCRAFT_REQUIRE_WINRES");
- if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
- return;
- }
- let mut res = winresource::WindowsResource::new();
- res.set_icon("../../assets/app-icon/sheetcraft.ico")
- .set("ProductName", "SheetCraft")
- .set("FileDescription", "SheetCraft spreadsheet")
- .set("CompanyName", "Learning Machines LLC")
- .set("LegalCopyright", "Copyright (c) the SheetCraft authors. MIT OR Apache-2.0.")
- .set("OriginalFilename", "sheetcraft.exe")
- .set("InternalName", "sheetcraft");
- if let Err(e) = res.compile() {
- if std::env::var_os("SHEETCRAFT_REQUIRE_WINRES").is_some() {
- eprintln!("embedding Windows resources failed: {e}");
- std::process::exit(1);
- }
- println!("cargo:warning=sheetcraft.exe built without icon/version resources: {e}");
- }
-}
diff --git a/assets/app-icon/LICENSE.txt b/assets/app-icon/LICENSE.txt
index 15da9c4..28d1656 100644
--- a/assets/app-icon/LICENSE.txt
+++ b/assets/app-icon/LICENSE.txt
@@ -1,8 +1,8 @@
-SheetCraft app icon (sheetcraft.svg and every file derived from it in this directory:
-sheetcraft-1024.png, sheetcraft-macos-512.png, sheetcraft.icns, sheetcraft.ico, hicolor/).
+GridCraft app icon (gridcraft.svg and every file derived from it in this directory:
+gridcraft-1024.png, gridcraft-macos-512.png, gridcraft.icns, gridcraft.ico, hicolor/).
-PLACEHOLDER artwork: original work written for this project by SheetCraft contributors (simple
-hand-written SVG geometry). No third-party material. Licensed like the rest of SheetCraft, under
+PLACEHOLDER artwork: original work written for this project by GridCraft contributors (simple
+hand-written SVG geometry). No third-party material. Licensed like the rest of GridCraft, under
either of
* Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md
index 75f313b..fdf0b0e 100644
--- a/assets/app-icon/README.md
+++ b/assets/app-icon/README.md
@@ -1,13 +1,13 @@
-# SheetCraft app icon (PLACEHOLDER)
+# GridCraft app icon (PLACEHOLDER)
**This icon is temporary.** Every Crafting App's icon is an engraved creature portrait that the
-product owner makes (craftrules `standards/icon-design.md`); SheetCraft's hasn't been made yet.
+product owner makes (craftrules `standards/icon-design.md`); GridCraft's hasn't been made yet.
Until it is, this placeholder stands in: a simple ledger page with a header row, a row-number
column, a selected cell with its fill handle and a double rule under a total, on a full-bleed
-SheetCraft-green tile. It is original geometry written by hand for this repository, deliberately
+GridCraft-green tile. It is original geometry written by hand for this repository, deliberately
not modelled on any vendor's spreadsheet logo (no letters, no "X", no overlapping panels).
-When the owner's art arrives, replace `sheetcraft.svg`, run `packaging/icons.sh`, update this
+When the owner's art arrives, replace `gridcraft.svg`, run `packaging/icons.sh`, update this
README (provenance, creature) and the rows in `ATTRIBUTION.md`, and drop the word placeholder.
## Palette
@@ -18,35 +18,35 @@ Exactly three colours:
|---|---|---|
| Ink | `#0b0b0c` | Line work and contour |
| Paper | `#efe9dc` | The page |
-| SheetCraft green (app colour, **proposed**) | `#1f9d55` | The full-bleed field and the header cells |
+| GridCraft green (app colour, **proposed**) | `#1f9d55` | The full-bleed field and the header cells |
-The app colour is a proposal pending the owner's approval: SheetCraft green `#1f9d55`, with ink
+The app colour is a proposal pending the owner's approval: GridCraft green `#1f9d55`, with ink
(darker) variant `#147a40` for text and accents on light backgrounds. It differs from
DesignCraft's `#7bb51c`.
## Geometry
A 512 × 512 viewBox, clipped to a rounded square with `rx=112`. Windows and Linux use the full
-tile. The macOS icons (`.icns`, `sheetcraft-macos-512.png`) sit on Apple's grid: an 824/1024
+tile. The macOS icons (`.icns`, `gridcraft-macos-512.png`) sit on Apple's grid: an 824/1024
body with a transparent margin.
## Files
| File | Use |
|---|---|
-| `sheetcraft.svg` | Canonical artwork; also `hicolor/scalable` |
-| `sheetcraft-1024.png` | 1024 px render of the full tile |
-| `sheetcraft-macos-512.png` | Runtime Dock icon on macOS (for `apps/sheetcraft` to embed) |
-| `sheetcraft.icns` | macOS bundle icon (`CFBundleIconFile`) |
-| `sheetcraft.ico` | Windows exe icon, 16–256 px (embedded by `apps/sheetcraft/build.rs`) |
-| `hicolor//apps/ai.storyteller.sheetcraft.png` | Linux icon theme, 16–512 px; the 256 px one is also the runtime window icon on Windows and Linux |
-| `hicolor/scalable/apps/ai.storyteller.sheetcraft.svg` | Linux scalable icon |
+| `gridcraft.svg` | Canonical artwork; also `hicolor/scalable` |
+| `gridcraft-1024.png` | 1024 px render of the full tile |
+| `gridcraft-macos-512.png` | Runtime Dock icon on macOS (for `apps/gridcraft` to embed) |
+| `gridcraft.icns` | macOS bundle icon (`CFBundleIconFile`) |
+| `gridcraft.ico` | Windows exe icon, 16–256 px (embedded by `apps/gridcraft/build.rs`) |
+| `hicolor//apps/ai.storyteller.gridcraft.png` | Linux icon theme, 16–512 px; the 256 px one is also the runtime window icon on Windows and Linux |
+| `hicolor/scalable/apps/ai.storyteller.gridcraft.svg` | Linux scalable icon |
| `LICENSE.txt` | MIT OR Apache-2.0 |
-The app ID is `ai.storyteller.sheetcraft`. It's used for the hicolor icon names, the Wayland app
-ID and `packaging/linux/ai.storyteller.sheetcraft.desktop`.
+The app ID is `ai.storyteller.gridcraft`. It's used for the hicolor icon names, the Wayland app
+ID and `packaging/linux/ai.storyteller.gridcraft.desktop`.
## Regenerate
-Edit `sheetcraft.svg`, then run `packaging/icons.sh`. It needs `resvg`, and `iconutil` for the
+Edit `gridcraft.svg`, then run `packaging/icons.sh`. It needs `resvg`, and `iconutil` for the
`.icns` (macOS only). It packs the `.ico` with `cargo xtask ico`.
diff --git a/assets/app-icon/sheetcraft-1024.png b/assets/app-icon/gridcraft-1024.png
similarity index 100%
rename from assets/app-icon/sheetcraft-1024.png
rename to assets/app-icon/gridcraft-1024.png
diff --git a/assets/app-icon/sheetcraft-macos-512.png b/assets/app-icon/gridcraft-macos-512.png
similarity index 100%
rename from assets/app-icon/sheetcraft-macos-512.png
rename to assets/app-icon/gridcraft-macos-512.png
diff --git a/assets/app-icon/sheetcraft.icns b/assets/app-icon/gridcraft.icns
similarity index 100%
rename from assets/app-icon/sheetcraft.icns
rename to assets/app-icon/gridcraft.icns
diff --git a/assets/app-icon/sheetcraft.ico b/assets/app-icon/gridcraft.ico
similarity index 100%
rename from assets/app-icon/sheetcraft.ico
rename to assets/app-icon/gridcraft.ico
diff --git a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.sheetcraft.svg b/assets/app-icon/gridcraft.svg
similarity index 94%
rename from assets/app-icon/hicolor/scalable/apps/ai.storyteller.sheetcraft.svg
rename to assets/app-icon/gridcraft.svg
index 548a272..fd53f9e 100644
--- a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.sheetcraft.svg
+++ b/assets/app-icon/gridcraft.svg
@@ -1,5 +1,5 @@
-
diff --git a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/128x128/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/128x128/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/16x16/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/16x16/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/24x24/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/24x24/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/24x24/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/256x256/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/256x256/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/32x32/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/32x32/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/48x48/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/48x48/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/512x512/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/512x512/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.sheetcraft.png b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/64x64/apps/ai.storyteller.sheetcraft.png
rename to assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png
diff --git a/assets/app-icon/sheetcraft.svg b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.gridcraft.svg
similarity index 94%
rename from assets/app-icon/sheetcraft.svg
rename to assets/app-icon/hicolor/scalable/apps/ai.storyteller.gridcraft.svg
index 548a272..fd53f9e 100644
--- a/assets/app-icon/sheetcraft.svg
+++ b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.gridcraft.svg
@@ -1,5 +1,5 @@
-
diff --git a/crates/calc/Cargo.toml b/crates/calc/Cargo.toml
index 94791c4..8f1bd12 100644
--- a/crates/calc/Cargo.toml
+++ b/crates/calc/Cargo.toml
@@ -1,17 +1,17 @@
[package]
-name = "sheetcraft-calc"
+name = "gridcraft-calc"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft calculation engine: dependency graph, recalculation, evaluator, dynamic arrays"
+description = "GridCraft calculation engine: dependency graph, recalculation, evaluator, dynamic arrays"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
-sheetcraft-formula = { workspace = true }
-sheetcraft-functions = { workspace = true }
-sheetcraft-numfmt = { workspace = true }
-sheetcraft-model = { workspace = true }
+gridcraft-core = { workspace = true }
+gridcraft-formula = { workspace = true }
+gridcraft-functions = { workspace = true }
+gridcraft-numfmt = { workspace = true }
+gridcraft-model = { workspace = true }
log = { workspace = true }
diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs
index aed4b71..abef54f 100644
--- a/crates/calc/src/eval.rs
+++ b/crates/calc/src/eval.rs
@@ -6,10 +6,10 @@
use std::sync::Arc;
-use sheetcraft_core::{Array, CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value, compare};
-use sheetcraft_formula::{BinOp, Expr, RefKind, Reference, SheetSel, StructItem, StructRef, UnOp};
-use sheetcraft_functions::{Arg, Ctx};
-use sheetcraft_model::Workbook;
+use gridcraft_core::{Array, CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value, compare};
+use gridcraft_formula::{BinOp, Expr, RefKind, Reference, SheetSel, StructItem, StructRef, UnOp};
+use gridcraft_functions::{Arg, Ctx};
+use gridcraft_model::Workbook;
/// Largest array a reference may expand to.
pub const MAX_CELLS: u64 = 16_000_000;
@@ -73,7 +73,7 @@ struct FnCtx<'a, 'h> {
}
impl Ctx for FnCtx<'_, '_> {
- fn date_system(&self) -> sheetcraft_core::DateSystem {
+ fn date_system(&self) -> gridcraft_core::DateSystem {
self.ev.host.workbook().date_system
}
fn now_serial(&self) -> f64 {
@@ -224,7 +224,7 @@ impl<'h> Evaluator<'h> {
if let Some(def) = wb.name(bare, scope_sheet) {
let text = def.formula.clone();
let scope = def.scope.unwrap_or(self.sheet);
- return match sheetcraft_formula::parse(&text) {
+ return match gridcraft_formula::parse(&text) {
Ok(expr) => {
let saved = self.sheet;
// Unqualified references in a name refer to the scope sheet.
@@ -649,7 +649,7 @@ impl<'h> Evaluator<'h> {
Ok(c) => c,
Err(e) => return Value::Error(e),
};
- match sheetcraft_numfmt::text_function(v, &code, sys) {
+ match gridcraft_numfmt::text_function(v, &code, sys) {
Ok(s) => Value::text(s),
Err(e) => Value::Error(e),
}
@@ -685,7 +685,7 @@ impl<'h> Evaluator<'h> {
}
fn builtin(&mut self, name: &str, args: &[Expr]) -> Ev {
- let Some(spec) = sheetcraft_functions::lookup(name) else {
+ let Some(spec) = gridcraft_functions::lookup(name) else {
// A defined name holding a LAMBDA, or a LET-bound lambda.
if let Ev::L(l) = self.name(name) {
return self.apply_lambda(&l, args);
@@ -716,7 +716,7 @@ impl<'h> Evaluator<'h> {
evaluated.push(Arg { value, from_ref });
}
let mut ctx = FnCtx { ev: self };
- sheetcraft_functions::call(spec, &evaluated, &mut ctx).pipe(Ev::V)
+ gridcraft_functions::call(spec, &evaluated, &mut ctx).pipe(Ev::V)
}
pub fn apply_lambda(&mut self, l: &Lambda, args: &[Expr]) -> Ev {
@@ -942,7 +942,7 @@ impl<'h> Evaluator<'h> {
}
};
let text = if a1 { text } else { r1c1_to_a1(&text, self.at).unwrap_or(text) };
- match sheetcraft_formula::parse(&text) {
+ match gridcraft_formula::parse(&text) {
Ok(e @ (Expr::Ref(_) | Expr::Name(_) | Expr::Struct(_))) => match self.eval(&e) {
r @ Ev::R(_) => r,
_ => err(CellError::Ref),
@@ -1024,7 +1024,7 @@ impl<'h> Evaluator<'h> {
let wb = self.host.workbook();
let sheet = wb.sheet(a.sheet);
match kind.as_str() {
- "address" => Ev::V(Value::text(format!("${}${}", sheetcraft_core::col_to_letters(c.col), c.row + 1))),
+ "address" => Ev::V(Value::text(format!("${}${}", gridcraft_core::col_to_letters(c.col), c.row + 1))),
"row" => Ev::V(Value::number(c.row as f64 + 1.0)),
"col" => Ev::V(Value::number(c.col as f64 + 1.0)),
"contents" => Ev::V(self.host.cell_value(a.sheet, c)),
@@ -1115,9 +1115,9 @@ impl<'h> Evaluator<'h> {
}
if let Some(k) = k {
values.push(Value::Empty);
- return Ev::V(sheetcraft_functions::aggregate_values_k(func, &values[..values.len() - 1], &k));
+ return Ev::V(gridcraft_functions::aggregate_values_k(func, &values[..values.len() - 1], &k));
}
- Ev::V(sheetcraft_functions::aggregate_values(func, &values))
+ Ev::V(gridcraft_functions::aggregate_values(func, &values))
}
}
@@ -1187,7 +1187,7 @@ pub fn r1c1_to_a1(s: &str, at: CellRef) -> Option {
};
let (r, ra) = num(rpart, at.row)?;
let (c, ca) = num(cpart, at.col)?;
- Some(format!("{}{}{}{}", if ca { "$" } else { "" }, sheetcraft_core::col_to_letters(c), if ra { "$" } else { "" }, r + 1))
+ Some(format!("{}{}{}{}", if ca { "$" } else { "" }, gridcraft_core::col_to_letters(c), if ra { "$" } else { "" }, r + 1))
};
let out = match body.split_once(':') {
Some((a, b)) => format!("{}:{}", conv(a)?, conv(b)?),
@@ -1336,7 +1336,7 @@ pub fn precedents(wb: &Workbook, sheet: usize, e: &Expr) -> (Vec , bool) {
dynamic = true
}
Expr::Name(_) | Expr::Struct(_) => dynamic = true,
- Expr::Call(n, _) if sheetcraft_functions::lookup(n).is_none() && !is_special(n) => dynamic = true,
+ Expr::Call(n, _) if gridcraft_functions::lookup(n).is_none() && !is_special(n) => dynamic = true,
_ => {}
});
(out, dynamic)
@@ -1416,7 +1416,7 @@ pub const SPECIAL_FUNCTIONS: &[&str] = &[
];
pub fn is_known_function(n: &str) -> bool {
- is_special(&n.to_ascii_uppercase()) || sheetcraft_functions::lookup(n).is_some()
+ is_special(&n.to_ascii_uppercase()) || gridcraft_functions::lookup(n).is_some()
}
#[allow(dead_code)]
diff --git a/crates/calc/src/lib.rs b/crates/calc/src/lib.rs
index 0c38184..6668b44 100644
--- a/crates/calc/src/lib.rs
+++ b/crates/calc/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft's calculation engine: the evaluator (references, names, tables, LET/LAMBDA,
+//! GridCraft's calculation engine: the evaluator (references, names, tables, LET/LAMBDA,
//! dynamic arrays) and dependency-driven recalculation with spills and cycle detection.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
diff --git a/crates/calc/src/recalc.rs b/crates/calc/src/recalc.rs
index e568bb4..13b47c4 100644
--- a/crates/calc/src/recalc.rs
+++ b/crates/calc/src/recalc.rs
@@ -37,9 +37,9 @@ impl Hasher for FxHasher {
type HashMap = std::collections::HashMap>;
type HashSet = std::collections::HashSet>;
-use sheetcraft_core::{Array, CellError, CellRef, RangeRef, Value};
-use sheetcraft_formula::Expr;
-use sheetcraft_model::{CalcMode, Workbook};
+use gridcraft_core::{Array, CellError, CellRef, RangeRef, Value};
+use gridcraft_formula::Expr;
+use gridcraft_model::{CalcMode, Workbook};
use crate::eval::{Area, Evaluator, Host, precedents};
@@ -403,7 +403,7 @@ impl Calc {
let t1 = prof_now();
let n = dirty.len();
self.run(wb, dirty, false);
- if std::env::var_os("SHEETCRAFT_PROFILE").is_some() {
+ if std::env::var_os("GRIDCRAFT_PROFILE").is_some() {
eprintln!("recalc: {} seeds, closure {} cells {:.1} ms, run {:.1} ms", seeds.len(), n, t1 - t0, prof_now() - t1);
}
}
@@ -478,7 +478,7 @@ impl Calc {
(host.results, host.spills, host.cycles)
};
self.rng = rng;
- if std::env::var_os("SHEETCRAFT_PROFILE").is_some() {
+ if std::env::var_os("GRIDCRAFT_PROFILE").is_some() {
eprintln!(" eval {} formulas {:.1} ms", results.len(), prof_now() - tp);
}
let tw = prof_now();
@@ -511,8 +511,8 @@ impl Calc {
if let Some(arr) = new {
let end = CellRef::new(k.1.row.saturating_add(arr.rows as u32 - 1), k.1.col.saturating_add(arr.cols as u32 - 1));
let range = RangeRef::new(k.1, end);
- let blocked = end.row >= sheetcraft_core::MAX_ROWS
- || end.col >= sheetcraft_core::MAX_COLS
+ let blocked = end.row >= gridcraft_core::MAX_ROWS
+ || end.col >= gridcraft_core::MAX_COLS
|| range.iter().any(|c| c != k.1 && (sheet.cells.has(c) || sheet.spill.contains_key(&c)))
|| sheet.merges.iter().any(|m| m.intersects(&range));
if blocked {
@@ -538,7 +538,7 @@ impl Calc {
if full {
// Everything was already evaluated once; spill dependents need another pass.
}
- if std::env::var_os("SHEETCRAFT_PROFILE").is_some() {
+ if std::env::var_os("GRIDCRAFT_PROFILE").is_some() {
eprintln!(" write-back {:.1} ms", prof_now() - tw);
}
spill_changes.sort_by_key(|(s, c)| (*s, c.row, c.col));
@@ -568,7 +568,7 @@ pub fn now_serial() -> f64 {
/// validation, Evaluate Formula, Name Manager previews, the CLI `eval`).
pub fn evaluate(wb: &Workbook, sheet: usize, at: CellRef, formula: &str) -> Value {
let body = formula.strip_prefix('=').unwrap_or(formula);
- let Ok(expr) = sheetcraft_formula::parse(body) else { return Value::Error(CellError::Name) };
+ let Ok(expr) = gridcraft_formula::parse(body) else { return Value::Error(CellError::Name) };
evaluate_expr(wb, sheet, at, &expr)
}
diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs
index 72724e8..67db083 100644
--- a/crates/calc/src/tests.rs
+++ b/crates/calc/src/tests.rs
@@ -1,5 +1,5 @@
-use sheetcraft_core::{CellError, CellRef, Value};
-use sheetcraft_model::{Cell, Formula, Workbook};
+use gridcraft_core::{CellError, CellRef, Value};
+use gridcraft_model::{Cell, Formula, Workbook};
use crate::Calc;
@@ -20,7 +20,7 @@ impl T {
let cell = if input.starts_with('=') {
Cell::formula(Formula::new(input))
} else {
- Cell::value(sheetcraft_core::parse::parse_input(input, self.wb.date_system).value)
+ Cell::value(gridcraft_core::parse::parse_input(input, self.wb.date_system).value)
};
self.wb.sheet_mut(0).unwrap().cells.set(c(at), cell);
self.calc.cells_changed(&mut self.wb, &[(0, c(at))]);
@@ -174,8 +174,8 @@ fn let_lambda() {
#[test]
fn names_and_sheets() {
let mut t = T::new();
- t.wb.names.push(sheetcraft_model::DefinedName { name: "Rate".into(), scope: None, formula: "0.5".into(), comment: String::new(), hidden: false });
- t.wb.sheets.push(std::sync::Arc::new(sheetcraft_model::Sheet::new("Data")));
+ t.wb.names.push(gridcraft_model::DefinedName { name: "Rate".into(), scope: None, formula: "0.5".into(), comment: String::new(), hidden: false });
+ t.wb.sheets.push(std::sync::Arc::new(gridcraft_model::Sheet::new("Data")));
t.wb.sheet_mut(1).unwrap().set_value(c("A1"), Value::Number(8.0));
t.calc.rebuild(&t.wb);
t.set("A1", "=Rate*Data!A1");
@@ -198,7 +198,7 @@ fn subtotal_skips_hidden() {
t.set("A1", "1");
t.set("A2", "2");
t.set("A3", "3");
- t.wb.sheet_mut(0).unwrap().rows.insert(1, sheetcraft_model::LineInfo { hidden: true, ..Default::default() });
+ t.wb.sheet_mut(0).unwrap().rows.insert(1, gridcraft_model::LineInfo { hidden: true, ..Default::default() });
t.set("B1", "=SUBTOTAL(109,A1:A3)");
t.set("B2", "=SUBTOTAL(9,A1:A3)");
assert_eq!(t.num("B1"), 4.0);
diff --git a/crates/chart/Cargo.toml b/crates/chart/Cargo.toml
index 0818207..7b1976c 100644
--- a/crates/chart/Cargo.toml
+++ b/crates/chart/Cargo.toml
@@ -1,16 +1,16 @@
[package]
-name = "sheetcraft-chart"
+name = "gridcraft-chart"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft charts: data resolution, layout into toolkit-free primitives, sparklines and a small rasteriser"
+description = "GridCraft charts: data resolution, layout into toolkit-free primitives, sparklines and a small rasteriser"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
-sheetcraft-model = { workspace = true }
-sheetcraft-calc = { workspace = true }
-sheetcraft-numfmt = { workspace = true }
+gridcraft-core = { workspace = true }
+gridcraft-model = { workspace = true }
+gridcraft-calc = { workspace = true }
+gridcraft-numfmt = { workspace = true }
serde = { workspace = true }
diff --git a/crates/chart/src/axis.rs b/crates/chart/src/axis.rs
index 8ca9610..eb4f733 100644
--- a/crates/chart/src/axis.rs
+++ b/crates/chart/src/axis.rs
@@ -1,7 +1,7 @@
//! Value-axis scaling ("nice numbers") and label formatting.
-use sheetcraft_core::{DateSystem, Value, number_to_text};
-use sheetcraft_numfmt::{NumberFormat, format_value};
+use gridcraft_core::{DateSystem, Value, number_to_text};
+use gridcraft_numfmt::{NumberFormat, format_value};
/// Largest magnitude we plot; bigger values are clamped so ranges stay finite.
pub(crate) const MAX_ABS: f64 = 1e300;
diff --git a/crates/chart/src/lib.rs b/crates/chart/src/lib.rs
index 2b892d3..93e40fb 100644
--- a/crates/chart/src/lib.rs
+++ b/crates/chart/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft charts (layer L3, toolkit-free).
+//! GridCraft charts (layer L3, toolkit-free).
//!
//! - [`resolve`] pulls series names, categories and values out of a workbook,
//! - [`render`] lays a chart out into a list of drawing primitives ([`Prim`]) that any UI
@@ -6,7 +6,7 @@
//! - [`render_sparkline`] draws in-cell sparklines,
//! - [`rasterize`] turns primitives into RGBA pixels for PNG export and tests.
//!
-//! The look (palette, greys, spacing) is SheetCraft's own design.
+//! The look (palette, greys, spacing) is GridCraft's own design.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
@@ -19,8 +19,8 @@ mod sparkline;
#[cfg(test)]
mod tests;
+pub use gridcraft_model::{Chart, ChartKind, LegendPos, SparklineKind};
use serde::{Deserialize, Serialize};
-pub use sheetcraft_model::{Chart, ChartKind, LegendPos, SparklineKind};
pub use axis::{Scale, format_number, nice_scale};
pub use raster::rasterize;
diff --git a/crates/chart/src/render.rs b/crates/chart/src/render.rs
index f584214..c67e9b7 100644
--- a/crates/chart/src/render.rs
+++ b/crates/chart/src/render.rs
@@ -3,7 +3,7 @@
use std::collections::HashSet;
use std::f32::consts::{PI, TAU};
-use sheetcraft_model::{Chart, ChartKind, LegendPos, Theme};
+use gridcraft_model::{Chart, ChartKind, LegendPos, Theme};
use crate::axis::{Fmt, Scale, clean, fixed_scale, nice_scale};
use crate::resolve::{MAX_POINTS, MAX_SERIES, series_color};
diff --git a/crates/chart/src/resolve.rs b/crates/chart/src/resolve.rs
index 5b40497..77f7a87 100644
--- a/crates/chart/src/resolve.rs
+++ b/crates/chart/src/resolve.rs
@@ -1,9 +1,9 @@
//! Pulls chart data (names, categories, values, formats, colours) out of a workbook.
-use sheetcraft_calc::evaluate;
-use sheetcraft_core::{CellRef, RangeRef, Value, number_to_text};
-use sheetcraft_model::{Chart, ChartKind, Theme, Workbook, style::apply_tint};
-use sheetcraft_numfmt::{NumberFormat, format_value};
+use gridcraft_calc::evaluate;
+use gridcraft_core::{CellRef, RangeRef, Value, number_to_text};
+use gridcraft_model::{Chart, ChartKind, Theme, Workbook, style::apply_tint};
+use gridcraft_numfmt::{NumberFormat, format_value};
use crate::{ChartData, Rgba, SeriesData};
diff --git a/crates/chart/src/sparkline.rs b/crates/chart/src/sparkline.rs
index 5a151ad..f2b94ce 100644
--- a/crates/chart/src/sparkline.rs
+++ b/crates/chart/src/sparkline.rs
@@ -1,6 +1,6 @@
//! In-cell sparklines.
-use sheetcraft_model::SparklineKind;
+use gridcraft_model::SparklineKind;
use crate::render::{DOWN, finalize};
use crate::resolve::MAX_POINTS;
diff --git a/crates/chart/src/tests.rs b/crates/chart/src/tests.rs
index 1f05fbe..441085d 100644
--- a/crates/chart/src/tests.rs
+++ b/crates/chart/src/tests.rs
@@ -1,7 +1,7 @@
use std::sync::Arc;
-use sheetcraft_core::{CellRef, Value};
-use sheetcraft_model::{Anchor, Chart, ChartKind, Color, LegendPos, Series, SparklineKind, Style, Workbook};
+use gridcraft_core::{CellRef, Value};
+use gridcraft_model::{Anchor, Chart, ChartKind, Color, LegendPos, Series, SparklineKind, Style, Workbook};
use crate::*;
@@ -200,7 +200,7 @@ fn axis_labels_use_number_format() {
#[test]
fn resolve_from_workbook() {
let mut wb = Workbook::new();
- let cur = wb.styles.intern(Style { num_fmt: sheetcraft_model::NumFmt(Arc::from("$#,##0")), ..Style::default() });
+ let cur = wb.styles.intern(Style { num_fmt: gridcraft_model::NumFmt(Arc::from("$#,##0")), ..Style::default() });
{
let s = wb.sheet_mut(0).unwrap();
s.name = "Sales".into();
@@ -307,7 +307,7 @@ fn resolve_from_workbook() {
#[test]
fn palette_beyond_six_series() {
- let theme = sheetcraft_model::Theme::default();
+ let theme = gridcraft_model::Theme::default();
let p = default_palette(&theme, 14);
assert_eq!(p.len(), 14);
assert_ne!(p[0], p[6]);
diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml
index 7bc6e5f..26cd19f 100644
--- a/crates/core/Cargo.toml
+++ b/crates/core/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "sheetcraft-core"
+name = "gridcraft-core"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft core types: cell addresses, values, errors, date serials"
+description = "GridCraft core types: cell addresses, values, errors, date serials"
[lints]
workspace = true
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
index c2bc21b..00876f5 100644
--- a/crates/core/src/lib.rs
+++ b/crates/core/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft core types: cell addresses, values and Excel's coercion rules, error values,
+//! GridCraft core types: cell addresses, values and Excel's coercion rules, error values,
//! date serials and typed-input parsing. No dependencies on the rest of the workspace.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml
index d37b91f..53e2350 100644
--- a/crates/engine/Cargo.toml
+++ b/crates/engine/Cargo.toml
@@ -1,25 +1,25 @@
[package]
-name = "sheetcraft-engine"
+name = "gridcraft-engine"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft engine: sessions, commands, history, clipboard, fill, sort, filter"
+description = "GridCraft engine: sessions, commands, history, clipboard, fill, sort, filter"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
-sheetcraft-numfmt = { workspace = true }
-sheetcraft-formula = { workspace = true }
-sheetcraft-functions = { workspace = true }
-sheetcraft-model = { workspace = true }
-sheetcraft-calc = { workspace = true }
+gridcraft-core = { workspace = true }
+gridcraft-numfmt = { workspace = true }
+gridcraft-formula = { workspace = true }
+gridcraft-functions = { workspace = true }
+gridcraft-model = { workspace = true }
+gridcraft-calc = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
-sheetcraft-xlsx = { workspace = true }
-sheetcraft-chart = { workspace = true }
-sheetcraft-pdf = { workspace = true }
+gridcraft-xlsx = { workspace = true }
+gridcraft-chart = { workspace = true }
+gridcraft-pdf = { workspace = true }
image = { workspace = true }
diff --git a/crates/engine/examples/perf.rs b/crates/engine/examples/perf.rs
index b6e2a54..2a533a2 100644
--- a/crates/engine/examples/perf.rs
+++ b/crates/engine/examples/perf.rs
@@ -1,10 +1,10 @@
//! Performance scenarios on large workbooks (release build):
-//! `cargo run --release -p sheetcraft-engine --example perf -- [rows]`
+//! `cargo run --release -p gridcraft-engine --example perf -- [rows]`
use std::time::Instant;
+use gridcraft_engine::Session;
use serde_json::json;
-use sheetcraft_engine::Session;
fn main() {
let rows: usize = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(100_000);
diff --git a/crates/engine/src/cf.rs b/crates/engine/src/cf.rs
index bf8ae98..a102863 100644
--- a/crates/engine/src/cf.rs
+++ b/crates/engine/src/cf.rs
@@ -3,8 +3,8 @@
use std::collections::HashMap;
-use sheetcraft_core::{CellRef, RangeRef, Value, compare};
-use sheetcraft_model::*;
+use gridcraft_core::{CellRef, RangeRef, Value, compare};
+use gridcraft_model::*;
/// The conditional look of one cell.
#[derive(Clone, Debug, Default, PartialEq)]
@@ -77,7 +77,7 @@ fn threshold(wb: &Workbook, sheet: usize, at: CellRef, st: &RuleStats, k: &CfVal
CfValueKind::Number(n) => *n,
CfValueKind::Percent(p) => min + (max - min) * p / 100.0,
CfValueKind::Percentile(p) => percentile(&st.sorted, *p),
- CfValueKind::Formula(f) => sheetcraft_calc::evaluate(wb, sheet, at, f).to_number().unwrap_or(0.0),
+ CfValueKind::Formula(f) => gridcraft_calc::evaluate(wb, sheet, at, f).to_number().unwrap_or(0.0),
}
}
@@ -128,7 +128,7 @@ impl CfCache {
let text = v.display();
let matched_style: Option<&Style> = match &cf.rule {
CfRule::CellIs { op, a, b, style } => {
- let eval = |f: &str| sheetcraft_calc::evaluate(wb, sheet, c, &relative(f, first, c));
+ let eval = |f: &str| gridcraft_calc::evaluate(wb, sheet, c, &relative(f, first, c));
let av = eval(a);
let ok = match op {
CfOperator::Between | CfOperator::NotBetween => {
@@ -147,7 +147,7 @@ impl CfCache {
(ok && !v.is_empty()).then_some(style.as_ref())
}
CfRule::Expression { formula, style } => {
- sheetcraft_calc::evaluate(wb, sheet, c, &relative(formula, first, c)).to_bool().unwrap_or(false).then_some(style.as_ref())
+ gridcraft_calc::evaluate(wb, sheet, c, &relative(formula, first, c)).to_bool().unwrap_or(false).then_some(style.as_ref())
}
CfRule::ContainsText { text: t, style } => text.to_lowercase().contains(&t.to_lowercase()).then_some(style.as_ref()),
CfRule::NotContainsText { text: t, style } => (!text.to_lowercase().contains(&t.to_lowercase())).then_some(style.as_ref()),
@@ -186,13 +186,13 @@ impl CfCache {
},
CfRule::TimePeriod { period, style } => match v.as_f64() {
Some(n) => {
- let today = sheetcraft_calc::now_serial().floor();
+ let today = gridcraft_calc::now_serial().floor();
let d = n.floor();
- let wd = sheetcraft_core::date::datetime_from_serial(wb.date_system, today).map(|x| x.weekday as f64).unwrap_or(0.0);
+ let wd = gridcraft_core::date::datetime_from_serial(wb.date_system, today).map(|x| x.weekday as f64).unwrap_or(0.0);
let week_start = today - wd;
let (ty, tm) =
- sheetcraft_core::date::datetime_from_serial(wb.date_system, today).map(|x| (x.year, x.month)).unwrap_or((2026, 1));
- let ym = sheetcraft_core::date::datetime_from_serial(wb.date_system, d).map(|x| (x.year, x.month));
+ gridcraft_core::date::datetime_from_serial(wb.date_system, today).map(|x| (x.year, x.month)).unwrap_or((2026, 1));
+ let ym = gridcraft_core::date::datetime_from_serial(wb.date_system, d).map(|x| (x.year, x.month));
let month_off = |k: i32| {
let m = tm as i32 + k;
let y = ty + (m - 1).div_euclid(12);
@@ -299,12 +299,10 @@ fn relative(f: &str, first: CellRef, c: CellRef) -> String {
if first == c {
return f.to_string();
}
- match sheetcraft_formula::parse(f.trim_start_matches('=')) {
- Ok(e) => sheetcraft_formula::print(&sheetcraft_formula::adjust::shift_relative(
- e,
- c.row as i64 - first.row as i64,
- c.col as i64 - first.col as i64,
- )),
+ match gridcraft_formula::parse(f.trim_start_matches('=')) {
+ Ok(e) => {
+ gridcraft_formula::print(&gridcraft_formula::adjust::shift_relative(e, c.row as i64 - first.row as i64, c.col as i64 - first.col as i64))
+ }
Err(_) => f.to_string(),
}
}
diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs
index 2182124..8961d87 100644
--- a/crates/engine/src/cmd/data.rs
+++ b/crates/engine/src/cmd/data.rs
@@ -3,9 +3,9 @@
use std::cmp::Ordering;
use std::sync::Arc;
+use gridcraft_core::{CellRef, RangeRef, Value, sort_compare};
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef, Value, sort_compare};
-use sheetcraft_model::*;
use super::*;
use crate::selection::current_region;
@@ -93,7 +93,7 @@ pub fn specs() -> Vec {
fn col_param(v: Option<&Json>, base: u32) -> Option {
match v? {
- Json::String(s) => sheetcraft_core::letters_to_col(s.trim_start_matches('$')),
+ Json::String(s) => gridcraft_core::letters_to_col(s.trim_start_matches('$')),
Json::Number(n) => n.as_u64().map(|o| base + o as u32),
_ => None,
}
@@ -274,7 +274,7 @@ fn do_sort(s: &mut Session, r: RangeRef, header: bool, keys: Vec, by_co
&& let Some(f) = &c.formula
&& let Some(e) = f.expr()
{
- let shifted = sheetcraft_formula::adjust::shift_relative(e, dst.row as i64 - src.row as i64, dst.col as i64 - src.col as i64);
+ let shifted = gridcraft_formula::adjust::shift_relative(e, dst.row as i64 - src.row as i64, dst.col as i64 - src.col as i64);
c.formula = Some(Arc::new(Formula::from_expr(shifted)));
}
moved.push((dst, cell));
@@ -401,10 +401,10 @@ fn filter_by(s: &mut Session, p: &Json) -> Result {
/// Does a value pass a custom comparison (`>`, `=*abc*`, `<>x`…)?
fn custom_ok(v: &Value, text: &str, op: &str, crit: &str) -> bool {
- let num = sheetcraft_core::parse::parse_number_text(crit);
+ let num = gridcraft_core::parse::parse_number_text(crit);
let cmp = match (v.as_f64(), num) {
(Some(a), Some(b)) => a.partial_cmp(&b),
- _ => Some(sheetcraft_core::compare_text(text, crit)),
+ _ => Some(gridcraft_core::compare_text(text, crit)),
};
let wild = || super::edit::wildcard(&text.to_lowercase(), &crit.to_lowercase());
match op {
@@ -781,14 +781,12 @@ pub fn list_items(wb: &Workbook, sheet: usize, dv: &Validation) -> Vec {
let f = dv.f1.trim();
if f.starts_with('"')
|| (!f.contains('!')
- && sheetcraft_formula::parse(f)
- .map(|e| !matches!(e, sheetcraft_formula::Expr::Ref(_) | sheetcraft_formula::Expr::Name(_)))
- .unwrap_or(true))
+ && gridcraft_formula::parse(f).map(|e| !matches!(e, gridcraft_formula::Expr::Ref(_) | gridcraft_formula::Expr::Name(_))).unwrap_or(true))
{
return f.trim_matches('"').split(',').map(|x| x.trim().to_string()).filter(|x| !x.is_empty()).collect();
}
let at = dv.ranges.first().map(|r| r.start).unwrap_or_default();
- match sheetcraft_calc::evaluate(wb, sheet, at, f) {
+ match gridcraft_calc::evaluate(wb, sheet, at, f) {
Value::Array(a) => a.data.iter().filter(|v| !v.is_empty()).map(Value::display).collect(),
v if !v.is_empty() && !v.is_error() => vec![v.display()],
_ => vec![],
@@ -805,11 +803,11 @@ pub fn check_validation(wb: &Workbook, sheet: usize, at: CellRef, input: &str) -
if input.is_empty() && dv.allow_blank {
return None;
}
- let v = sheetcraft_core::parse::parse_input(input, wb.date_system).value;
- let num = |f: &str| sheetcraft_calc::evaluate(wb, sheet, at, f).to_number().ok();
+ let v = gridcraft_core::parse::parse_input(input, wb.date_system).value;
+ let num = |f: &str| gridcraft_calc::evaluate(wb, sheet, at, f).to_number().ok();
let ok = match dv.kind {
ValidationKind::List => list_items(wb, sheet, dv).iter().any(|x| x.eq_ignore_ascii_case(input)),
- ValidationKind::Custom => sheetcraft_calc::evaluate(wb, sheet, at, &dv.f1).to_bool().unwrap_or(false),
+ ValidationKind::Custom => gridcraft_calc::evaluate(wb, sheet, at, &dv.f1).to_bool().unwrap_or(false),
ValidationKind::TextLength => {
let n = input.chars().count() as f64;
compare_op(dv.op, n, num(&dv.f1), dv.f2.as_deref().and_then(num))
diff --git a/crates/engine/src/cmd/draw.rs b/crates/engine/src/cmd/draw.rs
index 243d580..e1fcd72 100644
--- a/crates/engine/src/cmd/draw.rs
+++ b/crates/engine/src/cmd/draw.rs
@@ -1,8 +1,8 @@
//! Draw tab: pen and eraser tools, ink strokes, Ink to Shape.
+use gridcraft_core::CellRef;
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::CellRef;
-use sheetcraft_model::*;
use super::*;
diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs
index 6208a54..cffe154 100644
--- a/crates/engine/src/cmd/edit.rs
+++ b/crates/engine/src/cmd/edit.rs
@@ -1,8 +1,8 @@
//! Cell entry, selection, undo/redo, clipboard, clear, fill, find & replace.
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
+use gridcraft_model::{Cell, Formula, StyleId};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
-use sheetcraft_model::{Cell, Formula, StyleId};
use super::*;
use crate::selection::{current_region, jump};
@@ -120,7 +120,7 @@ pub fn specs() -> Vec {
/// What typing `input` into a cell produces (constant or formula, plus an automatic number
/// format). Errors when the formula can't be parsed.
-pub(crate) fn input_to_cell(input: &str, old: Option<&Cell>, wb: &mut sheetcraft_model::Workbook) -> Result> {
+pub(crate) fn input_to_cell(input: &str, old: Option<&Cell>, wb: &mut gridcraft_model::Workbook) -> Result > {
let style = old.map(|c| c.style).unwrap_or_default();
if input.is_empty() {
let c = Cell { value: Value::Empty, formula: None, style };
@@ -133,19 +133,19 @@ pub(crate) fn input_to_cell(input: &str, old: Option<&Cell>, wb: &mut sheetcraft
let is_formula = input.starts_with('=')
|| (input.len() > 1
&& (input.starts_with('+') || input.starts_with('-'))
- && sheetcraft_core::parse::parse_number_text(input).is_none()
+ && gridcraft_core::parse::parse_number_text(input).is_none()
&& input.chars().nth(1).is_some_and(|c| c.is_ascii_alphabetic() || c == '('));
if is_formula {
let body = input.strip_prefix('=').unwrap_or(input);
// Excel closes missing parentheses for you.
let mut text = body.to_string();
- let mut parsed = sheetcraft_formula::parse(&text);
+ let mut parsed = gridcraft_formula::parse(&text);
for _ in 0..8 {
if parsed.is_ok() {
break;
}
text.push(')');
- parsed = sheetcraft_formula::parse(&text);
+ parsed = gridcraft_formula::parse(&text);
}
let expr = parsed.map_err(|e| EngineError::Other(format!("There's a problem with this formula: {e}")))?;
let mut cell = Cell::formula(Formula::from_expr(expr));
@@ -168,17 +168,17 @@ pub(crate) fn input_to_cell(input: &str, old: Option<&Cell>, wb: &mut sheetcraft
None
};
if let Some(code) = auto {
- cell.style = wb.styles.derive(style, |s| s.num_fmt = sheetcraft_model::NumFmt::new(code));
+ cell.style = wb.styles.derive(style, |s| s.num_fmt = gridcraft_model::NumFmt::new(code));
}
}
return Ok(Some(cell));
}
- let parsed = sheetcraft_core::parse::parse_input(input, wb.date_system);
+ let parsed = gridcraft_core::parse::parse_input(input, wb.date_system);
let mut style = style;
if let Some(code) = parsed.format {
let cur = wb.styles.get(style).num_fmt.as_str().to_string();
if cur == "General" {
- style = wb.styles.derive(style, |s| s.num_fmt = sheetcraft_model::NumFmt::new(code));
+ style = wb.styles.derive(style, |s| s.num_fmt = gridcraft_model::NumFmt::new(code));
}
}
// Wrap text automatically when the input has line breaks (Alt+Enter).
@@ -308,7 +308,7 @@ fn range_fill(s: &mut Session, p: &Json) -> Result {
if let Some(f) = &cell.formula
&& let Some(e) = f.expr()
{
- let shifted = sheetcraft_formula::adjust::shift_relative(e, at.row as i64 - origin.row as i64, at.col as i64 - origin.col as i64);
+ let shifted = gridcraft_formula::adjust::shift_relative(e, at.row as i64 - origin.row as i64, at.col as i64 - origin.col as i64);
cell.formula = Some(std::sync::Arc::new(Formula::from_expr(shifted)));
}
cell.style = cx.wb.sheet(sheet).and_then(|sh| sh.cell(at)).map(|c| c.style).unwrap_or(cell.style);
@@ -777,7 +777,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result {
edit(s, |cx| {
// Styles from another workbook must be re-interned.
let mut style_map = std::collections::HashMap::new();
- let mut map_style = |id: StyleId, wb: &mut sheetcraft_model::Workbook| -> StyleId {
+ let mut map_style = |id: StyleId, wb: &mut gridcraft_model::Workbook| -> StyleId {
if same_doc {
return id;
}
@@ -817,14 +817,14 @@ fn paste_special(s: &mut Session, p: &Json) -> Result {
return Some(std::sync::Arc::new(Formula::from_expr(e)));
}
let moved =
- sheetcraft_formula::adjust::shift_relative(e, dest.row as i64 - sc.row as i64, dest.col as i64 - sc.col as i64);
+ gridcraft_formula::adjust::shift_relative(e, dest.row as i64 - sc.row as i64, dest.col as i64 - sc.col as i64);
Some(std::sync::Arc::new(Formula::from_expr(moved)))
};
if link {
let sheet_prefix = if dest_sheet == clip.sheet && same_doc {
String::new()
} else {
- format!("{}!", sheetcraft_formula::quote_sheet(&src_sheet_name))
+ format!("{}!", gridcraft_formula::quote_sheet(&src_sheet_name))
};
new.formula = Some(std::sync::Arc::new(Formula::new(&format!("={sheet_prefix}{}", sc.a1()))));
new.value = Value::Empty;
@@ -878,7 +878,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result {
"multiply" => Value::number(x * y),
"divide" => {
if y == 0.0 {
- Value::Error(sheetcraft_core::CellError::Div0)
+ Value::Error(gridcraft_core::CellError::Div0)
} else {
Value::number(x / y)
}
@@ -930,7 +930,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result {
// References elsewhere to the moved block now point to its new place.
let dest_name = cx.wb.sheet(dest_sheet).map(|s| s.name.clone()).unwrap_or_default();
if dest_sheet == clip.sheet {
- let e = sheetcraft_formula::adjust::Edit::Move { from: src, to_row: at.row, to_col: at.col };
+ let e = gridcraft_formula::adjust::Edit::Move { from: src, to_row: at.row, to_col: at.col };
rewrite_all_formulas(&mut cx.wb, &src_sheet_name, &e);
}
let _ = dest_name;
@@ -947,7 +947,7 @@ fn paste_special(s: &mut Session, p: &Json) -> Result {
}
/// Applies a reference adjustment to every formula in the workbook (and names, CF, DV).
-pub(crate) fn rewrite_all_formulas(wb: &mut sheetcraft_model::Workbook, target: &str, e: &sheetcraft_formula::adjust::Edit) {
+pub(crate) fn rewrite_all_formulas(wb: &mut gridcraft_model::Workbook, target: &str, e: &gridcraft_formula::adjust::Edit) {
for si in 0..wb.sheets.len() {
let host = wb.sheets.get(si).map(|s| s.name.clone()).unwrap_or_default();
let keys: Vec<(CellRef, std::sync::Arc)> =
@@ -958,8 +958,8 @@ pub(crate) fn rewrite_all_formulas(wb: &mut sheetcraft_model::Workbook, target:
let Some(sheet) = wb.sheet_mut(si) else { continue };
for (c, f) in keys {
let Some(expr) = f.expr() else { continue };
- let new = sheetcraft_formula::adjust::adjust(expr, &host, target, e);
- let text = sheetcraft_formula::print(&new);
+ let new = gridcraft_formula::adjust::adjust(expr, &host, target, e);
+ let text = gridcraft_formula::print(&new);
if text != f.text
&& let Some(cell) = sheet.cells.get_mut(c)
{
@@ -970,10 +970,10 @@ pub(crate) fn rewrite_all_formulas(wb: &mut sheetcraft_model::Workbook, target:
}
}
for n in wb.names.iter_mut() {
- if let Ok(expr) = sheetcraft_formula::parse(&n.formula) {
+ if let Ok(expr) = gridcraft_formula::parse(&n.formula) {
// Names are workbook-level: unqualified refs don't occur; qualified ones adjust.
- let new = sheetcraft_formula::adjust::adjust(expr, "\u{0}", target, e);
- n.formula = sheetcraft_formula::print(&new);
+ let new = gridcraft_formula::adjust::adjust(expr, "\u{0}", target, e);
+ n.formula = gridcraft_formula::print(&new);
}
}
}
diff --git a/crates/engine/src/cmd/extra.rs b/crates/engine/src/cmd/extra.rs
index ba36e50..5ebc8b8 100644
--- a/crates/engine/src/cmd/extra.rs
+++ b/crates/engine/src/cmd/extra.rs
@@ -1,9 +1,9 @@
//! More ribbon commands: Chart Design and Format, object arrangement, workbook views, table
//! tools, Get Data, scripts (Automate), cell checkboxes, theme colours and fonts.
+use gridcraft_core::{CellRef, RangeRef, Value};
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef, Value};
-use sheetcraft_model::*;
use super::*;
@@ -224,7 +224,7 @@ pub fn specs() -> Vec {
has_doc,
new_cell_style
),
- cmd!(query "home.addIns", "Add-ins", ["Home", "Add-ins"], None, "{} → SheetCraft extends through MCP and scripts instead of add-ins", always, |_, _| Ok(json!({"addIns": [], "hint": "Use sheetcraft-cli mcp or Automate › scripts"}))),
+ cmd!(query "home.addIns", "Add-ins", ["Home", "Add-ins"], None, "{} → GridCraft extends through MCP and scripts instead of add-ins", always, |_, _| Ok(json!({"addIns": [], "hint": "Use gridcraft-cli mcp or Automate › scripts"}))),
cmd!(query "formulas.recentlyUsed", "Recently Used", ["Formulas", "Function Library"], None, "{} → recently used functions in this workbook", has_doc, recently_used),
cmd!(query "formulas.financial", "Financial", ["Formulas", "Function Library"], None, "{}", always, |s, _| s.execute("formulas.functions", json!({"category": "Financial"}))),
cmd!(query "formulas.logical", "Logical", ["Formulas", "Function Library"], None, "{}", always, |s, _| s.execute("formulas.functions", json!({"category": "Logical"}))),
@@ -356,7 +356,7 @@ fn select_data(s: &mut Session, p: &Json) -> Result {
let src = d.wb.sheet(src_si).ok_or(EngineError::NoDocument)?;
let by_rows = bool_param(p, "byRows").unwrap_or(r.width() > r.height() + 1);
let series = super::insert::series_from_range(src, r, by_rows);
- let source = format!("{}!{}", sheetcraft_formula::quote_sheet(&src.name), r.a1());
+ let source = format!("{}!{}", gridcraft_formula::quote_sheet(&src.name), r.a1());
with_chart(s, p, move |c| {
c.series = series;
c.source = Some(source);
@@ -703,8 +703,8 @@ fn from_csv(s: &mut Session, p: &Json) -> Result {
let delim = str_param(p, "delimiter")
.and_then(|d| d.chars().next())
.unwrap_or(if text.lines().next().is_some_and(|l| l.contains('\t')) { '\t' } else { ',' });
- let opts = sheetcraft_xlsx::CsvOptions { delimiter: delim as u8, ..Default::default() };
- let wb = sheetcraft_xlsx::read_csv(&bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
+ let opts = gridcraft_xlsx::CsvOptions { delimiter: delim as u8, ..Default::default() };
+ let wb = gridcraft_xlsx::read_csv(&bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
let sh = wb.sheet(0).ok_or(EngineError::NoDocument)?;
let Some(u) = sh.used_range() else { return Err(EngineError::Other("The file is empty.".into())) };
(u.start.row..=u.end.row)
@@ -933,7 +933,7 @@ fn recently_used(s: &mut Session, _: &Json) -> Result {
continue;
}
let input = p.get("input").and_then(Json::as_str).unwrap_or("");
- if let Ok(e) = sheetcraft_formula::parse(input.trim_start_matches('=')) {
+ if let Ok(e) = gridcraft_formula::parse(input.trim_start_matches('=')) {
for f in e.functions() {
if !seen.iter().any(|x| x == f) {
seen.push(f.to_string());
diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs
index b9a6d58..d6bc2ba 100644
--- a/crates/engine/src/cmd/format.rs
+++ b/crates/engine/src/cmd/format.rs
@@ -1,8 +1,8 @@
//! Formatting: Home › Font, Alignment, Number, Styles, Cells › Format.
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::*;
use super::*;
@@ -558,7 +558,7 @@ fn merge(s: &mut Session, p: &Json, how: &str) -> Result {
let others: Vec = sh.cells.iter_range(*r).map(|(c, _)| c).filter(|c| *c != r.start).collect();
for c in others {
if let Some(mut cell) = sh.cells.get(c).cloned() {
- cell.value = sheetcraft_core::Value::Empty;
+ cell.value = gridcraft_core::Value::Empty;
cell.formula = None;
sh.cells.set(c, cell);
}
@@ -610,10 +610,10 @@ fn set_fmt(s: &mut Session, p: &Json, code: &str) -> Result {
}
/// Adds or removes one decimal place in the number format (Increase/Decrease Decimal).
-pub fn adjust_decimals(code: &str, value: &sheetcraft_core::Value, delta: i32) -> String {
+pub fn adjust_decimals(code: &str, value: &gridcraft_core::Value, delta: i32) -> String {
if code == "General" {
// Start from the displayed number of decimals.
- let text = value.as_f64().map(sheetcraft_core::number_to_text).unwrap_or_default();
+ let text = value.as_f64().map(gridcraft_core::number_to_text).unwrap_or_default();
let dec = text.split_once('.').map_or(0, |(_, d)| d.len()) as i32;
let n = (dec + delta).max(0) as usize;
return if n == 0 { "0".into() } else { format!("0.{}", "0".repeat(n)) };
@@ -1067,7 +1067,7 @@ mod tests {
#[test]
fn decimal_steps() {
- let v = sheetcraft_core::Value::Number(1.5);
+ let v = gridcraft_core::Value::Number(1.5);
assert_eq!(adjust_decimals("General", &v, 1), "0.00");
assert_eq!(adjust_decimals("General", &v, -1), "0");
assert_eq!(adjust_decimals("0", &v, 1), "0.0");
diff --git a/crates/engine/src/cmd/formulas.rs b/crates/engine/src/cmd/formulas.rs
index 89acb44..2bea26c 100644
--- a/crates/engine/src/cmd/formulas.rs
+++ b/crates/engine/src/cmd/formulas.rs
@@ -1,8 +1,8 @@
//! Formulas tab: AutoSum, Insert Function, names, auditing, calculation.
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::{CalcMode, DefinedName};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::{CalcMode, DefinedName};
use super::*;
@@ -144,7 +144,7 @@ fn insert_function(s: &mut Session, p: &Json) -> Result {
}
pub fn function_list() -> Vec {
- let mut v: Vec = sheetcraft_functions::all()
+ let mut v: Vec = gridcraft_functions::all()
.iter()
.map(|f| json!({"name": f.name, "category": format!("{:?}", f.category), "signature": f.signature, "description": f.description}))
.collect();
@@ -245,12 +245,12 @@ fn define_name(s: &mut Session, p: &Json) -> Result {
None => {
let sh = d.wb.active().ok_or(EngineError::NoDocument)?;
let r = d.selection.current();
- let abs = |c: CellRef| format!("${}${}", sheetcraft_core::col_to_letters(c.col), c.row + 1);
+ let abs = |c: CellRef| format!("${}${}", gridcraft_core::col_to_letters(c.col), c.row + 1);
let body = if r.is_single() { abs(r.start) } else { format!("{}:{}", abs(r.start), abs(r.end)) };
- format!("{}!{}", sheetcraft_formula::quote_sheet(&sh.name), body)
+ format!("{}!{}", gridcraft_formula::quote_sheet(&sh.name), body)
}
};
- if sheetcraft_formula::parse(&refers).is_err() {
+ if gridcraft_formula::parse(&refers).is_err() {
return Err(EngineError::Other("There's a problem with this formula.".into()));
}
let comment = str_param(p, "comment").unwrap_or("").to_string();
@@ -284,7 +284,7 @@ fn name_manager(s: &mut Session, _: &Json) -> Result {
.filter(|n| !n.hidden)
.map(|n| {
let sheet = n.scope.unwrap_or(d.wb.active_sheet);
- let v = sheetcraft_calc::evaluate(&d.wb, sheet, CellRef::default(), &n.formula);
+ let v = gridcraft_calc::evaluate(&d.wb, sheet, CellRef::default(), &n.formula);
json!({"name": n.name, "refersTo": format!("={}", n.formula), "scope": n.scope.and_then(|i| d.wb.sheet(i)).map(|s| s.name.clone()).unwrap_or_else(|| "Workbook".into()), "value": crate::cmd::inspect::value_json(&v), "comment": n.comment})
})
.collect();
@@ -297,13 +297,13 @@ fn create_from_selection(s: &mut Session, p: &Json) -> Result {
let left = bool_param(p, "left").unwrap_or(false);
let d = s.doc()?;
let sh = d.wb.active().ok_or(EngineError::NoDocument)?;
- let q = sheetcraft_formula::quote_sheet(&sh.name);
+ let q = gridcraft_formula::quote_sheet(&sh.name);
let abs = |r: RangeRef| {
format!(
"{q}!${}${}:${}${}",
- sheetcraft_core::col_to_letters(r.start.col),
+ gridcraft_core::col_to_letters(r.start.col),
r.start.row + 1,
- sheetcraft_core::col_to_letters(r.end.col),
+ gridcraft_core::col_to_letters(r.end.col),
r.end.row + 1
)
};
@@ -405,20 +405,20 @@ fn evaluate_formula(s: &mut Session, p: &Json) -> Result {
.ok_or_else(|| EngineError::Other("The cell has no formula.".into()))?
}
};
- let expr = sheetcraft_formula::parse(&text).map_err(|e| EngineError::Other(e.to_string()))?;
+ let expr = gridcraft_formula::parse(&text).map_err(|e| EngineError::Other(e.to_string()))?;
// Steps: each sub-expression (innermost first) with its value.
let mut steps = Vec::new();
let mut nodes = Vec::new();
expr.walk(&mut |e| {
- if matches!(e, sheetcraft_formula::Expr::Call(..) | sheetcraft_formula::Expr::Binary(..) | sheetcraft_formula::Expr::Ref(_)) {
+ if matches!(e, gridcraft_formula::Expr::Call(..) | gridcraft_formula::Expr::Binary(..) | gridcraft_formula::Expr::Ref(_)) {
nodes.push(e.clone());
}
});
for e in nodes.into_iter().rev().take(200) {
- let v = sheetcraft_calc::recalc::evaluate_expr(&d.wb, sheet, at, &e);
- steps.push(json!({"expression": sheetcraft_formula::print(&e), "value": crate::cmd::inspect::value_json(&v)}));
+ let v = gridcraft_calc::recalc::evaluate_expr(&d.wb, sheet, at, &e);
+ steps.push(json!({"expression": gridcraft_formula::print(&e), "value": crate::cmd::inspect::value_json(&v)}));
}
- let result = sheetcraft_calc::recalc::evaluate_expr(&d.wb, sheet, at, &expr);
+ let result = gridcraft_calc::recalc::evaluate_expr(&d.wb, sheet, at, &expr);
Ok(json!({"formula": format!("={text}"), "steps": steps, "result": crate::cmd::inspect::value_json(&result)}))
}
@@ -426,7 +426,7 @@ fn evaluate(s: &mut Session, p: &Json) -> Result {
let d = s.doc()?;
let f = str_param(p, "formula").ok_or_else(|| bad("formulas.evaluate", "missing `formula`"))?;
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
- let v = sheetcraft_calc::evaluate(&d.wb, d.wb.active_sheet, at, f);
+ let v = gridcraft_calc::evaluate(&d.wb, d.wb.active_sheet, at, f);
Ok(crate::cmd::inspect::value_json(&v))
}
diff --git a/crates/engine/src/cmd/insert.rs b/crates/engine/src/cmd/insert.rs
index 010fc06..e4b5bc9 100644
--- a/crates/engine/src/cmd/insert.rs
+++ b/crates/engine/src/cmd/insert.rs
@@ -1,9 +1,9 @@
//! Insert tab: tables, charts, sparklines, pictures, shapes, hyperlinks, comments and notes,
//! conditional formatting (Home › Styles) and symbols.
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::*;
use super::*;
use crate::selection::current_region;
@@ -221,7 +221,7 @@ fn insert_table(s: &mut Session, p: &Json) -> Result {
let sh = cx.sheet_mut(sheet)?;
sh.cells.shift_rows_in_cols(r.start.col, r.end.col, r.start.row, 1);
for (i, c) in (r.start.col..=r.end.col).enumerate() {
- sh.set_value(CellRef::new(r.start.row, c), sheetcraft_core::Value::text(format!("Column{}", i + 1)));
+ sh.set_value(CellRef::new(r.start.row, c), gridcraft_core::Value::text(format!("Column{}", i + 1)));
}
cx.structural = true;
RangeRef::new(r.start, CellRef::new(r.end.row + 1, r.end.col))
@@ -240,7 +240,7 @@ fn insert_table(s: &mut Session, p: &Json) -> Result {
k += 1;
}
// Headers are text.
- sh.set_value(CellRef::new(r.start.row, c), sheetcraft_core::Value::text(n.as_str()));
+ sh.set_value(CellRef::new(r.start.row, c), gridcraft_core::Value::text(n.as_str()));
names.push(n);
}
sh.tables.push(Table {
@@ -334,9 +334,9 @@ fn write_totals(sh: &mut Sheet, ti: usize) {
if let Some(code) = col.totals.subtotal_code() {
let f = format!("=SUBTOTAL({code},{}[{}])", t.name, col.name);
sh.cells
- .set(c, Cell { formula: Some(std::sync::Arc::new(Formula::new(&f))), value: sheetcraft_core::Value::Empty, style: sh.style_id(c) });
+ .set(c, Cell { formula: Some(std::sync::Arc::new(Formula::new(&f))), value: gridcraft_core::Value::Empty, style: sh.style_id(c) });
} else if let Some(l) = &col.totals_label {
- sh.set_value(c, sheetcraft_core::Value::text(l.as_str()));
+ sh.set_value(c, gridcraft_core::Value::text(l.as_str()));
} else {
sh.cells.remove(c);
}
@@ -388,7 +388,7 @@ fn table_rename(s: &mut Session, p: &Json) -> Result {
if name.is_empty()
|| name.contains(' ')
|| name.chars().next().is_some_and(|c| c.is_ascii_digit())
- || sheetcraft_core::CellRef::parse(&name).is_some()
+ || gridcraft_core::CellRef::parse(&name).is_some()
{
return Err(EngineError::Other("The name that you entered is not valid.".into()));
}
@@ -408,11 +408,11 @@ fn table_rename(s: &mut Session, p: &Json) -> Result {
for (c, f) in keys {
let Some(e) = f.expr() else { continue };
let ne = e.map(&mut |x| match x {
- sheetcraft_formula::Expr::Struct(mut st) if st.table.eq_ignore_ascii_case(&old) => {
+ gridcraft_formula::Expr::Struct(mut st) if st.table.eq_ignore_ascii_case(&old) => {
st.table = name.clone();
- sheetcraft_formula::Expr::Struct(st)
+ gridcraft_formula::Expr::Struct(st)
}
- sheetcraft_formula::Expr::Name(n) if n.eq_ignore_ascii_case(&old) => sheetcraft_formula::Expr::Name(name.clone()),
+ gridcraft_formula::Expr::Name(n) if n.eq_ignore_ascii_case(&old) => gridcraft_formula::Expr::Name(name.clone()),
o => o,
});
if let Some(cell) = sh.cells.get_mut(c) {
@@ -480,17 +480,17 @@ pub fn chart_kind(t: &str, sub: &str) -> ChartKind {
/// Builds series from a block: first row/column as names/categories when they're text.
pub fn series_from_range(sh: &Sheet, r: RangeRef, by_rows: bool) -> Vec {
- let q = sheetcraft_formula::quote_sheet(&sh.name);
+ let q = gridcraft_formula::quote_sheet(&sh.name);
let abs = |r: RangeRef| {
format!(
"{q}!${}${}:${}${}",
- sheetcraft_core::col_to_letters(r.start.col),
+ gridcraft_core::col_to_letters(r.start.col),
r.start.row + 1,
- sheetcraft_core::col_to_letters(r.end.col),
+ gridcraft_core::col_to_letters(r.end.col),
r.end.row + 1
)
};
- let abs1 = |c: CellRef| format!("{q}!${}${}", sheetcraft_core::col_to_letters(c.col), c.row + 1);
+ let abs1 = |c: CellRef| format!("{q}!${}${}", gridcraft_core::col_to_letters(c.col), c.row + 1);
let is_text = |c: CellRef| sh.value(c).is_text() || sh.value(c).is_empty();
let header_row = r.height() > 1
&& (r.start.col..=r.end.col).any(|c| sh.value(CellRef::new(r.start.row, c)).is_text())
@@ -552,7 +552,7 @@ fn insert_chart(s: &mut Session, p: &Json) -> Result {
let at = cell_param(p, "at").unwrap_or_else(|| CellRef::new(r.start.row, r.end.col + 2));
let title = str_param(p, "title").map(str::to_string).or_else(|| {
if series.len() == 1 {
- series[0].name.as_ref().map(|n| sheetcraft_calc::evaluate(&d.wb, sheet, at, n).display())
+ series[0].name.as_ref().map(|n| gridcraft_calc::evaluate(&d.wb, sheet, at, n).display())
} else {
Some("Chart Title".into())
}
@@ -575,7 +575,7 @@ fn insert_chart(s: &mut Session, p: &Json) -> Result {
style: 1,
x_title: None,
y_title: None,
- source: Some(format!("{}!{}", sheetcraft_formula::quote_sheet(&sh.name), r.a1())),
+ source: Some(format!("{}!{}", gridcraft_formula::quote_sheet(&sh.name), r.a1())),
by_rows,
};
edit(s, |cx| {
@@ -846,7 +846,7 @@ fn insert_link(s: &mut Session, p: &Json) -> Result {
});
let sh = cx.sheet_mut(sheet)?;
if let Some(t) = text.clone().or_else(|| sh.value(at).is_empty().then(|| target.clone())) {
- sh.cells.set(at, Cell { value: sheetcraft_core::Value::text(t.as_str()), formula: None, style: link_style });
+ sh.cells.set(at, Cell { value: gridcraft_core::Value::text(t.as_str()), formula: None, style: link_style });
} else {
sh.set_style(at, link_style);
}
@@ -996,7 +996,7 @@ fn add_cf(s: &mut Session, p: &Json) -> Result {
let a = rule.get("value").map(super::edit::json_to_input).unwrap_or_default();
let b = rule.get("value2").map(super::edit::json_to_input);
let quote = |v: String| {
- if sheetcraft_core::parse::parse_number_text(&v).is_some() || v.starts_with('=') {
+ if gridcraft_core::parse::parse_number_text(&v).is_some() || v.starts_with('=') {
v.trim_start_matches('=').to_string()
} else {
format!("\"{}\"", v.replace('"', "\"\""))
diff --git a/crates/engine/src/cmd/inspect.rs b/crates/engine/src/cmd/inspect.rs
index 5a60a9b..989f587 100644
--- a/crates/engine/src/cmd/inspect.rs
+++ b/crates/engine/src/cmd/inspect.rs
@@ -1,7 +1,7 @@
//! Read-only commands for agents: inspect the workbook, read ranges, cell details.
+use gridcraft_core::{CellRef, RangeRef, Value};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef, Value};
use super::*;
diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs
index 1f615b7..397a05c 100644
--- a/crates/engine/src/cmd/mod.rs
+++ b/crates/engine/src/cmd/mod.rs
@@ -18,11 +18,11 @@ pub mod spelling;
pub mod view;
pub mod whatif;
+use gridcraft_calc::Key;
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::Workbook;
use serde::Serialize;
use serde_json::Value as Json;
-use sheetcraft_calc::Key;
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::Workbook;
use crate::{DocState, EngineError, Result, Selection, Session};
@@ -217,7 +217,7 @@ pub(crate) fn edit(s: &mut Session, f: impl FnOnce(&mut Ctx) -> Result) ->
}
pub(crate) fn commit(d: &mut DocState, f: impl FnOnce(&mut Ctx) -> Result) -> Result {
- let prof = std::env::var_os("SHEETCRAFT_PROFILE").is_some();
+ let prof = std::env::var_os("GRIDCRAFT_PROFILE").is_some();
let t0 = std::time::Instant::now();
let mut sel = d.selection.clone();
let mut ctx = Ctx { wb: (*d.wb).clone(), changed: Vec::new(), structural: false, sel: &mut sel, fit_rows: Vec::new() };
@@ -259,9 +259,9 @@ pub fn auto_row_height(wb: &mut Workbook, si: usize, row: u32) {
}
let default = sh.default_row_height;
// Fast path: rows of default-size, unwrapped, single-line content keep the default height.
- let plain = sh.cells.row(row, 0, sheetcraft_core::MAX_COLS - 1).all(|(_, cell)| {
+ let plain = sh.cells.row(row, 0, gridcraft_core::MAX_COLS - 1).all(|(_, cell)| {
let st = wb.styles.get(cell.style);
- st.font.size <= sheetcraft_model::DEFAULT_FONT_SIZE && !st.align.wrap && !cell.value.as_text().is_some_and(|t| t.contains('\n'))
+ st.font.size <= gridcraft_model::DEFAULT_FONT_SIZE && !st.align.wrap && !cell.value.as_text().is_some_and(|t| t.contains('\n'))
});
if plain {
if sh.rows.get(&row).is_some_and(|i| i.size.is_some())
@@ -270,7 +270,7 @@ pub fn auto_row_height(wb: &mut Workbook, si: usize, row: u32) {
{
{
e.size = None;
- if *e == sheetcraft_model::LineInfo::default() {
+ if *e == gridcraft_model::LineInfo::default() {
shm.rows.remove(&row);
}
}
@@ -278,10 +278,10 @@ pub fn auto_row_height(wb: &mut Workbook, si: usize, row: u32) {
return;
}
let mut need: f32 = default;
- for (c, cell) in sh.cells.row(row, 0, sheetcraft_core::MAX_COLS - 1) {
+ for (c, cell) in sh.cells.row(row, 0, gridcraft_core::MAX_COLS - 1) {
let st = wb.styles.get(cell.style);
let size = st.font.size;
- let line = (size * 1.8).round().max(default * size / sheetcraft_model::DEFAULT_FONT_SIZE);
+ let line = (size * 1.8).round().max(default * size / gridcraft_model::DEFAULT_FONT_SIZE);
let mut lines = 1.0f32;
if !cell.value.is_empty() || cell.formula.is_some() {
let text = crate::display::cell_text(wb, sh, CellRef::new(row, c));
@@ -295,7 +295,7 @@ pub fn auto_row_height(wb: &mut Workbook, si: usize, row: u32) {
} else {
explicit
};
- } else if st.font.size <= sheetcraft_model::DEFAULT_FONT_SIZE {
+ } else if st.font.size <= gridcraft_model::DEFAULT_FONT_SIZE {
continue;
}
let h = if lines > 1.0 { (size * 96.0 / 72.0 * 1.22 * lines + 5.0).ceil() } else { line };
@@ -304,7 +304,7 @@ pub fn auto_row_height(wb: &mut Workbook, si: usize, row: u32) {
let Some(shm) = wb.sheet_mut(si) else { return };
let e = shm.rows.entry(row).or_default();
e.size = if (need - default).abs() < 0.5 { None } else { Some(need) };
- if *e == sheetcraft_model::LineInfo::default() {
+ if *e == gridcraft_model::LineInfo::default() {
shm.rows.remove(&row);
}
}
@@ -313,7 +313,7 @@ impl Ctx<'_> {
pub fn sheet_index(&self) -> usize {
self.wb.active_sheet
}
- pub fn sheet_mut(&mut self, i: usize) -> Result<&mut sheetcraft_model::Sheet> {
+ pub fn sheet_mut(&mut self, i: usize) -> Result<&mut gridcraft_model::Sheet> {
self.wb.sheet_mut(i).ok_or_else(|| EngineError::Other("no such sheet".into()))
}
pub fn touch(&mut self, sheet: usize, c: CellRef) {
diff --git a/crates/engine/src/cmd/pivot.rs b/crates/engine/src/cmd/pivot.rs
index dc089f7..8dd4b32 100644
--- a/crates/engine/src/cmd/pivot.rs
+++ b/crates/engine/src/cmd/pivot.rs
@@ -6,9 +6,9 @@
use std::sync::Arc;
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::*;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::*;
use super::*;
use crate::pivot as pv;
@@ -1280,8 +1280,8 @@ fn recommended(s: &mut Session, p: &Json) -> Result {
#[cfg(test)]
mod tests {
+ use gridcraft_core::{CellRef, Value};
use serde_json::json;
- use sheetcraft_core::{CellRef, Value};
use crate::Session;
@@ -1316,7 +1316,7 @@ mod tests {
fn text(s: &Session, a: &str) -> String {
val(s, a).display()
}
- fn pt(s: &Session) -> sheetcraft_model::PivotTable {
+ fn pt(s: &Session) -> gridcraft_model::PivotTable {
s.doc().unwrap().wb.active().unwrap().pivots.last().unwrap().clone()
}
/// Finds the row (0-based) whose first report column holds `label`.
diff --git a/crates/engine/src/cmd/print.rs b/crates/engine/src/cmd/print.rs
index 500f5e8..a51a2f1 100644
--- a/crates/engine/src/cmd/print.rs
+++ b/crates/engine/src/cmd/print.rs
@@ -3,16 +3,16 @@
//! Sheets are paginated like Excel's default "down, then over": the print area (or the used
//! range plus drawing objects) is split into bands of rows and columns that fit the printable
//! area at the page scale, honouring manual breaks, repeated title rows/columns, headings, Fit
-//! To and centring. Each page is drawn into a PDF with `sheetcraft-pdf`: fills, gridlines,
+//! To and centring. Each page is drawn into a PDF with `gridcraft-pdf`: fills, gridlines,
//! borders, formatted text (fonts mapped to the standard 14 PDF fonts), merged cells, wrapping,
//! overflow into empty neighbours, conditional formats, table styles, charts, images, shapes and
//! sparklines. Headers and footers support `&L`/`&C`/`&R` sections and the `&P &N &D &T &A &F &Z`
//! codes.
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
+use gridcraft_model::*;
+use gridcraft_pdf::{Font, Page, PdfDoc, Rgb};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
-use sheetcraft_model::*;
-use sheetcraft_pdf::{Font, Page, PdfDoc, Rgb};
use super::*;
@@ -464,8 +464,8 @@ fn rgb_of(c: Color, wb: &Workbook) -> Option {
c.resolve(&wb.theme)
}
-fn numfmt_rgb(c: sheetcraft_numfmt::FormatColor) -> Rgb {
- use sheetcraft_numfmt::FormatColor as F;
+fn numfmt_rgb(c: gridcraft_numfmt::FormatColor) -> Rgb {
+ use gridcraft_numfmt::FormatColor as F;
match c {
F::Black | F::Indexed(_) => [0, 0, 0],
F::Blue => [0, 0, 255],
@@ -539,9 +539,9 @@ fn cell_look(wb: &Workbook, si: usize, sh: &Sheet, c: CellRef, cf: &mut Option f32 {
- sheetcraft_pdf::text_width(if bold { Font::HelveticaBold } else { Font::Helvetica }, size, text)
+ gridcraft_pdf::text_width(if bold { Font::HelveticaBold } else { Font::Helvetica }, size, text)
}
}
@@ -563,8 +563,8 @@ impl Xf {
}
/// Draws chart primitives onto a page.
-fn draw_prims(pg: &mut Page, prims: &[sheetcraft_chart::Prim], xf: Xf) {
- use sheetcraft_chart::{HAlign as H, Prim, VAlign as V};
+fn draw_prims(pg: &mut Page, prims: &[gridcraft_chart::Prim], xf: Xf) {
+ use gridcraft_chart::{HAlign as H, Prim, VAlign as V};
let k = xf.k;
for p in prims {
match p {
@@ -603,7 +603,7 @@ fn draw_prims(pg: &mut Page, prims: &[sheetcraft_chart::Prim], xf: Xf) {
Prim::Text { x, y, text, size, color, bold, align, valign, rotation } => {
let font = if *bold { Font::HelveticaBold } else { Font::Helvetica };
let size = size * k;
- let w = sheetcraft_pdf::text_width(font, size, text);
+ let w = gridcraft_pdf::text_width(font, size, text);
let dx = match align {
H::Left => 0.0,
H::Center => -w / 2.0,
@@ -631,18 +631,18 @@ fn wrap_lines(text: &str, font: Font, size: f32, width: f32) -> Vec {
let mut line = String::new();
for word in para.split(' ') {
let cand = if line.is_empty() { word.to_string() } else { format!("{line} {word}") };
- if line.is_empty() || sheetcraft_pdf::text_width(font, size, &cand) <= width {
+ if line.is_empty() || gridcraft_pdf::text_width(font, size, &cand) <= width {
line = cand;
} else {
out.push(std::mem::take(&mut line));
line = word.to_string();
}
// Break a single over-long word by characters.
- while sheetcraft_pdf::text_width(font, size, &line) > width && line.chars().count() > 1 {
+ while gridcraft_pdf::text_width(font, size, &line) > width && line.chars().count() > 1 {
let mut cut = String::new();
let mut rest = String::new();
for ch in line.chars() {
- if rest.is_empty() && sheetcraft_pdf::text_width(font, size, &format!("{cut}{ch}")) <= width || cut.is_empty() {
+ if rest.is_empty() && gridcraft_pdf::text_width(font, size, &format!("{cut}{ch}")) <= width || cut.is_empty() {
cut.push(ch);
} else {
rest.push(ch);
@@ -887,9 +887,9 @@ fn draw_text(pg: &mut Page, b: &Block, c: CellRef, l: &Look, rect: (f32, f32, f3
} else if code == "General"
&& let Value::Number(n) = v
{
- let digit = sheetcraft_pdf::text_width(font, size, "0").max(0.1);
+ let digit = gridcraft_pdf::text_width(font, size, "0").max(0.1);
let max_chars = (avail / digit).floor().max(1.0) as usize;
- sheetcraft_numfmt::format_general_fit(n, max_chars.min(11)).unwrap_or_else(|| "#".repeat(max_chars.min(255)))
+ gridcraft_numfmt::format_general_fit(n, max_chars.min(11)).unwrap_or_else(|| "#".repeat(max_chars.min(255)))
} else {
crate::display::cell_text(wb, sh, c)
};
@@ -899,15 +899,15 @@ fn draw_text(pg: &mut Page, b: &Block, c: CellRef, l: &Look, rect: (f32, f32, f3
if text.is_empty() {
return;
}
- let mut tw = sheetcraft_pdf::text_width(font, size, &text);
+ let mut tw = gridcraft_pdf::text_width(font, size, &text);
if st.align.shrink && !wrap && tw > avail && tw > 0.0 {
size = (size * avail / tw).max(1.0);
- tw = sheetcraft_pdf::text_width(font, size, &text);
+ tw = gridcraft_pdf::text_width(font, size, &text);
}
if is_num && !wrap && tw > avail + 0.01 && !sh.show_formulas {
- let hash = sheetcraft_pdf::text_width(font, size, "#").max(0.1);
+ let hash = gridcraft_pdf::text_width(font, size, "#").max(0.1);
text = "#".repeat(((avail / hash).floor().max(1.0) as usize).min(255));
- tw = sheetcraft_pdf::text_width(font, size, &text);
+ tw = gridcraft_pdf::text_width(font, size, &text);
}
let h_align = match st.align.h {
HAlign::General => {
@@ -984,7 +984,7 @@ fn draw_text(pg: &mut Page, b: &Block, c: CellRef, l: &Look, rect: (f32, f32, f3
let mut yy = y + pad + size * 0.8;
for ch in chars {
let s = ch.to_string();
- let cw = sheetcraft_pdf::text_width(font, size, &s);
+ let cw = gridcraft_pdf::text_width(font, size, &s);
pg.text(x + (w - cw) / 2.0, yy, size, font, color, &s);
yy += line_h;
if yy > y + h + line_h {
@@ -995,7 +995,7 @@ fn draw_text(pg: &mut Page, b: &Block, c: CellRef, l: &Look, rect: (f32, f32, f3
return;
}
for (i, line) in lines.iter().enumerate() {
- let lw = sheetcraft_pdf::text_width(font, size, line);
+ let lw = gridcraft_pdf::text_width(font, size, line);
let lx = match h_align {
HAlign::Right => x + w - pad - lw - indent,
HAlign::Center => x + (w - lw) / 2.0,
@@ -1053,14 +1053,14 @@ fn draw_objects(pg: &mut Page, wb: &Workbook, si: usize, sh: &Sheet, b: &Block)
if !block.contains(sp.cell) {
continue;
}
- let vals: Vec> = match sheetcraft_calc::evaluate(wb, si, sp.cell, &sp.source) {
+ let vals: Vec > = match gridcraft_calc::evaluate(wb, si, sp.cell, &sp.source) {
Value::Array(a) => a.iter().take(10_000).map(Value::as_f64).collect(),
v => vec![v.as_f64()],
};
let color = rgb_of(sp.color, wb).unwrap_or([0x15, 0x60, 0x82]);
let (x, y, w, h) = b.rect(RangeRef::cell(sp.cell));
let (wpx, hpx) = (w / b.k, h / b.k);
- let prims = sheetcraft_chart::render_sparkline(sp.kind, &vals, [color[0], color[1], color[2], 255], sp.markers, wpx, hpx);
+ let prims = gridcraft_chart::render_sparkline(sp.kind, &vals, [color[0], color[1], color[2], 255], sp.markers, wpx, hpx);
draw_prims(pg, &prims, Xf { ox: x, oy: y, k: b.k });
}
for im in &sh.images {
@@ -1113,7 +1113,7 @@ fn draw_objects(pg: &mut Page, wb: &Workbook, si: usize, sh: &Sheet, b: &Block)
let lines = wrap_lines(&shp.text, Font::Helvetica, size, (w - 4.0 * b.k).max(size));
let mut yy = y + 2.0 * b.k + size * 0.8;
for l in lines.iter().take(500) {
- let lw = sheetcraft_pdf::text_width(Font::Helvetica, size, l);
+ let lw = gridcraft_pdf::text_width(Font::Helvetica, size, l);
let lx = if shp.kind == ShapeKind::TextBox { x + 2.0 * b.k } else { x + (w - lw) / 2.0 };
pg.text(lx, yy, size, Font::Helvetica, [0, 0, 0], l);
yy += size * 1.2;
@@ -1126,8 +1126,8 @@ fn draw_objects(pg: &mut Page, wb: &Workbook, si: usize, sh: &Sheet, b: &Block)
continue;
}
let (x, y, _, _) = place(r);
- let data = sheetcraft_chart::resolve(wb, si, ch);
- let prims = sheetcraft_chart::render(ch, &data, r.2 as f32, r.3 as f32, &PdfMeasure);
+ let data = gridcraft_chart::resolve(wb, si, ch);
+ let prims = gridcraft_chart::render(ch, &data, r.2 as f32, r.3 as f32, &PdfMeasure);
draw_prims(pg, &prims, Xf { ox: x, oy: y, k: b.k });
}
pg.restore();
@@ -1147,8 +1147,8 @@ fn draw_headings(pg: &mut Page, b: &Block, col_heads: bool, row_heads: bool, at:
}
pg.fill_rect(x0, y, x1 - x0, size.1, fill);
pg.rect_stroke(x0, y, x1 - x0, size.1, lw, line);
- let t = sheetcraft_core::col_to_letters(col);
- let tw = sheetcraft_pdf::text_width(Font::Helvetica, fsize, &t);
+ let t = gridcraft_core::col_to_letters(col);
+ let tw = gridcraft_pdf::text_width(Font::Helvetica, fsize, &t);
pg.text(x0 + (x1 - x0 - tw) / 2.0, y + size.1 / 2.0 + fsize * 0.35, fsize, Font::Helvetica, [0x40, 0x40, 0x40], &t);
}
}
@@ -1162,7 +1162,7 @@ fn draw_headings(pg: &mut Page, b: &Block, col_heads: bool, row_heads: bool, at:
pg.fill_rect(x, y0, size.0, y1 - y0, fill);
pg.rect_stroke(x, y0, size.0, y1 - y0, lw, line);
let t = (row + 1).to_string();
- let tw = sheetcraft_pdf::text_width(Font::Helvetica, fsize, &t);
+ let tw = gridcraft_pdf::text_width(Font::Helvetica, fsize, &t);
pg.text(x + (size.0 - tw) / 2.0, y0 + (y1 - y0) / 2.0 + fsize * 0.35, fsize, Font::Helvetica, [0x40, 0x40, 0x40], &t);
}
}
@@ -1182,11 +1182,11 @@ fn draw_header_footer(pg: &mut Page, lay: &SheetLayout, cx: &HfContext) {
continue;
}
let size = sec.size.unwrap_or(10.0).clamp(1.0, 72.0);
- let font = Font::styled(sheetcraft_pdf::Family::Sans, sec.bold, sec.italic);
+ let font = Font::styled(gridcraft_pdf::Family::Sans, sec.bold, sec.italic);
let lines: Vec<&str> = sec.text.split('\n').take(20).collect();
let n = lines.len() as f32;
for (j, line) in lines.iter().enumerate() {
- let lw = sheetcraft_pdf::text_width(font, size, line);
+ let lw = gridcraft_pdf::text_width(font, size, line);
let x = match i {
0 => l,
1 => (pw - lw) / 2.0,
@@ -1241,7 +1241,7 @@ pub fn render_pdf(wb: &Workbook, layouts: &[SheetLayout], file: &str, path: &str
doc.info.title = if wb.props.title.is_empty() { file.to_string() } else { wb.props.title.clone() };
doc.info.author = wb.props.author.clone();
let total: usize = layouts.iter().map(|l| l.pages.len()).sum();
- let now = sheetcraft_calc::now_serial();
+ let now = gridcraft_calc::now_serial();
let date = crate::display::format(&Value::Number(now.floor()), "m/d/yyyy", wb).text;
let time = crate::display::format(&Value::Number(now), "h:mm AM/PM", wb).text;
let mut page_no = 0usize;
@@ -1349,7 +1349,7 @@ fn print_preview(s: &mut Session, p: &Json) -> Result {
"sheet": name,
"range": r.a1(),
"rows": format!("{}:{}", rb.0 + 1, rb.1 + 1),
- "columns": format!("{}:{}", sheetcraft_core::col_to_letters(cb.0), sheetcraft_core::col_to_letters(cb.1)),
+ "columns": format!("{}:{}", gridcraft_core::col_to_letters(cb.0), gridcraft_core::col_to_letters(cb.1)),
"scale": (lay.scale * 1000.0).round() / 10.0,
"paper": lay.settings.paper,
"orientation": if lay.settings.orientation == Orientation::Landscape { "landscape" } else { "portrait" },
@@ -1441,7 +1441,7 @@ fn page_setup(s: &mut Session, p: &Json) -> Result {
let mut v: Vec = a
.iter()
.filter_map(|x| match x {
- Json::String(t) => sheetcraft_core::letters_to_col(t.trim().trim_start_matches('$')),
+ Json::String(t) => gridcraft_core::letters_to_col(t.trim().trim_start_matches('$')),
Json::Number(n) => n.as_u64().filter(|c| *c >= 2 && *c <= MAX_COLS as u64).map(|c| c as u32 - 1),
_ => None,
})
@@ -1628,7 +1628,7 @@ mod tests {
fn export_writes_file() {
let mut s = s();
s.execute("cell.set", json!({"cell": "B2", "input": "hello"})).unwrap();
- let dir = std::env::temp_dir().join(format!("sheetcraft-print-{}", std::process::id()));
+ let dir = std::env::temp_dir().join(format!("gridcraft-print-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("out.pdf");
let r = s.execute("file.exportPdf", json!({"path": path.to_str().unwrap()})).unwrap();
@@ -1758,7 +1758,7 @@ mod tests {
assert_eq!(bands(0, 1_000_000, |_| 10.0, |_| 5.0, &[], 3).len(), 3);
let w = wrap_lines("the quick brown fox jumps", Font::Helvetica, 10.0, 50.0);
assert!(w.len() >= 2);
- assert!(w.iter().all(|l| sheetcraft_pdf::text_width(Font::Helvetica, 10.0, l) <= 50.0 || !l.contains(' ')));
+ assert!(w.iter().all(|l| gridcraft_pdf::text_width(Font::Helvetica, 10.0, l) <= 50.0 || !l.contains(' ')));
let w = wrap_lines("Supercalifragilistic", Font::Helvetica, 10.0, 20.0);
assert!(w.len() > 2);
}
diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs
index 0b8d11c..1613e04 100644
--- a/crates/engine/src/cmd/review.rs
+++ b/crates/engine/src/cmd/review.rs
@@ -1,7 +1,7 @@
//! Review tab: protection, spelling (basic), workbook statistics.
+use gridcraft_model::{SheetProtection, password_hash};
use serde_json::{Value as Json, json};
-use sheetcraft_model::{SheetProtection, password_hash};
use super::*;
diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs
index a8c9129..837f080 100644
--- a/crates/engine/src/cmd/sheet.rs
+++ b/crates/engine/src/cmd/sheet.rs
@@ -2,10 +2,10 @@
use std::sync::Arc;
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
+use gridcraft_formula::adjust::{Axis, Edit};
+use gridcraft_model::{Sheet, Visibility};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
-use sheetcraft_formula::adjust::{Axis, Edit};
-use sheetcraft_model::{Sheet, Visibility};
use super::*;
@@ -76,15 +76,15 @@ fn shift_sheet_features(sh: &mut Sheet, axis: Axis, at: u32, count: u32, insert:
};
let map_range = |r: RangeRef| -> Option {
let e = if insert { Edit::Insert { axis, at, count } } else { Edit::Delete { axis, at, count } };
- let expr = sheetcraft_formula::Expr::Ref(sheetcraft_formula::Reference {
- sheet: sheetcraft_formula::SheetSel::Current,
- kind: sheetcraft_formula::RefKind::Range(
- sheetcraft_formula::Anchor { row: r.start.row, col: r.start.col, row_abs: false, col_abs: false },
- sheetcraft_formula::Anchor { row: r.end.row, col: r.end.col, row_abs: false, col_abs: false },
+ let expr = gridcraft_formula::Expr::Ref(gridcraft_formula::Reference {
+ sheet: gridcraft_formula::SheetSel::Current,
+ kind: gridcraft_formula::RefKind::Range(
+ gridcraft_formula::Anchor { row: r.start.row, col: r.start.col, row_abs: false, col_abs: false },
+ gridcraft_formula::Anchor { row: r.end.row, col: r.end.col, row_abs: false, col_abs: false },
),
});
- match sheetcraft_formula::adjust::adjust(expr, "S", "S", &e) {
- sheetcraft_formula::Expr::Ref(r) => Some(r.range()),
+ match gridcraft_formula::adjust::adjust(expr, "S", "S", &e) {
+ gridcraft_formula::Expr::Ref(r) => Some(r.range()),
_ => None,
}
};
@@ -122,7 +122,7 @@ fn shift_sheet_features(sh: &mut Sheet, axis: Axis, at: u32, count: u32, insert:
let name = format!("Column{}", t.columns.len() + 1);
t.columns.insert(
(idx + k as usize).min(t.columns.len()),
- sheetcraft_model::TableColumn { name, totals: Default::default(), totals_label: None, formula: None },
+ gridcraft_model::TableColumn { name, totals: Default::default(), totals_label: None, formula: None },
);
}
}
@@ -172,14 +172,14 @@ fn shift_sheet_features(sh: &mut Sheet, axis: Axis, at: u32, count: u32, insert:
}
/// Rewrites every formula reference (and chart series) for a structural edit on `target`.
-fn rewrite(wb: &mut sheetcraft_model::Workbook, target: &str, e: &Edit) {
+fn rewrite(wb: &mut gridcraft_model::Workbook, target: &str, e: &Edit) {
super::edit::rewrite_all_formulas(wb, target, e);
for si in 0..wb.sheets.len() {
let host = wb.sheets.get(si).map(|s| s.name.clone()).unwrap_or_default();
let Some(sh) = wb.sheet_mut(si) else { continue };
let fix = |text: &str| -> String {
- match sheetcraft_formula::parse(text) {
- Ok(expr) => sheetcraft_formula::print(&sheetcraft_formula::adjust::adjust(expr, &host, target, e)),
+ match gridcraft_formula::parse(text) {
+ Ok(expr) => gridcraft_formula::print(&gridcraft_formula::adjust::adjust(expr, &host, target, e)),
Err(_) => text.to_string(),
}
};
@@ -222,7 +222,7 @@ fn insert_lines(s: &mut Session, p: &Json, axis: Axis) -> Result {
if let Some(info) = prev {
for i in at..at + count {
let map = if axis == Axis::Rows { &mut sh.rows } else { &mut sh.cols };
- map.insert(i, sheetcraft_model::LineInfo { hidden: false, outline: 0, collapsed: false, ..info });
+ map.insert(i, gridcraft_model::LineInfo { hidden: false, outline: 0, collapsed: false, ..info });
}
}
}
@@ -373,14 +373,14 @@ fn delete_sheet(s: &mut Session, p: &Json) -> Result {
edit_doc(d, |cx| {
cx.wb.sheets.remove(i);
for si in 0..cx.wb.sheets.len() {
- let keys: Vec<(CellRef, Arc)> =
+ let keys: Vec<(CellRef, Arc)> =
cx.wb.sheets.get(si).map(|s| s.cells.iter().filter_map(|(c, x)| x.formula.clone().map(|f| (c, f))).collect()).unwrap_or_default();
let Some(sh) = cx.wb.sheet_mut(si) else { continue };
for (c, f) in keys {
if let Some(e) = f.expr() {
- let ne = sheetcraft_formula::adjust::delete_sheet(e, &name);
+ let ne = gridcraft_formula::adjust::delete_sheet(e, &name);
if let Some(cell) = sh.cells.get_mut(c) {
- cell.formula = Some(Arc::new(sheetcraft_model::Formula::from_expr(ne)));
+ cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
}
}
}
@@ -414,23 +414,23 @@ fn rename_sheet(s: &mut Session, p: &Json) -> Result {
let old = cx.wb.sheet(i).map(|s| s.name.clone()).unwrap_or_default();
cx.sheet_mut(i)?.name = name.clone();
for si in 0..cx.wb.sheets.len() {
- let keys: Vec<(CellRef, Arc)> =
+ let keys: Vec<(CellRef, Arc)> =
cx.wb.sheets.get(si).map(|s| s.cells.iter().filter_map(|(c, x)| x.formula.clone().map(|f| (c, f))).collect()).unwrap_or_default();
let Some(sh) = cx.wb.sheet_mut(si) else { continue };
for (c, f) in keys {
if let Some(e) = f.expr() {
- let ne = sheetcraft_formula::adjust::rename_sheet(e, &old, &name);
- let text = sheetcraft_formula::print(&ne);
+ let ne = gridcraft_formula::adjust::rename_sheet(e, &old, &name);
+ let text = gridcraft_formula::print(&ne);
if text != f.text
&& let Some(cell) = sh.cells.get_mut(c)
{
- cell.formula = Some(Arc::new(sheetcraft_model::Formula::from_expr(ne)));
+ cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
}
}
}
let fix = |t: &str| {
- sheetcraft_formula::parse(t)
- .map(|e| sheetcraft_formula::print(&sheetcraft_formula::adjust::rename_sheet(e, &old, &name)))
+ gridcraft_formula::parse(t)
+ .map(|e| gridcraft_formula::print(&gridcraft_formula::adjust::rename_sheet(e, &old, &name)))
.unwrap_or_else(|_| t.to_string())
};
for ch in sh.charts.iter_mut() {
@@ -442,8 +442,8 @@ fn rename_sheet(s: &mut Session, p: &Json) -> Result {
}
}
for n in cx.wb.names.iter_mut() {
- if let Ok(e) = sheetcraft_formula::parse(&n.formula) {
- n.formula = sheetcraft_formula::print(&sheetcraft_formula::adjust::rename_sheet(e, &old, &name));
+ if let Ok(e) = gridcraft_formula::parse(&n.formula) {
+ n.formula = gridcraft_formula::print(&gridcraft_formula::adjust::rename_sheet(e, &old, &name));
}
}
cx.structural = true;
@@ -482,7 +482,7 @@ fn tab_color(s: &mut Session, p: &Json) -> Result {
let i = target_sheet(s, p)?;
let c = super::format::color_param(p.get("color"));
edit(s, |cx| {
- cx.sheet_mut(i)?.tab_color = c.filter(|c| *c != sheetcraft_model::Color::Auto);
+ cx.sheet_mut(i)?.tab_color = c.filter(|c| *c != gridcraft_model::Color::Auto);
Ok(Json::Null)
})
}
diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs
index 165705b..10892bc 100644
--- a/crates/engine/src/cmd/spelling.rs
+++ b/crates/engine/src/cmd/spelling.rs
@@ -4,8 +4,8 @@
use std::collections::HashSet;
use std::sync::OnceLock;
+use gridcraft_core::{RangeRef, Value};
use serde_json::{Value as Json, json};
-use sheetcraft_core::{RangeRef, Value};
use super::*;
@@ -110,7 +110,7 @@ fn suggestions(w: &str) -> Vec {
fn spelling(s: &mut Session, p: &Json) -> Result {
if dictionary().is_empty() {
- return Err(EngineError::Other("No dictionary is available on this system (SheetCraft uses the system word list).".into()));
+ return Err(EngineError::Other("No dictionary is available on this system (GridCraft uses the system word list).".into()));
}
let si = target_sheet(s, p)?;
let d = s.doc()?;
diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs
index 907283b..d8450cd 100644
--- a/crates/engine/src/cmd/view.rs
+++ b/crates/engine/src/cmd/view.rs
@@ -1,8 +1,8 @@
//! View tab (sheet view state that's saved with the file) and Page Layout settings.
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::Orientation;
use serde_json::{Value as Json, json};
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::Orientation;
use super::*;
@@ -295,7 +295,7 @@ fn header_footer(s: &mut Session, p: &Json) -> Result {
})
}
-/// SheetCraft's own theme set (original palettes).
+/// GridCraft's own theme set (original palettes).
pub fn themes() -> Vec<(&'static str, [u32; 12])> {
vec![
("Craft", [0xFFFFFF, 0x000000, 0xE8E8E8, 0x0E2841, 0x156082, 0xE97132, 0x196B24, 0x0F9ED5, 0xA02B93, 0x4EA72E, 0x467886, 0x96607D]),
diff --git a/crates/engine/src/cmd/whatif.rs b/crates/engine/src/cmd/whatif.rs
index b161869..111225d 100644
--- a/crates/engine/src/cmd/whatif.rs
+++ b/crates/engine/src/cmd/whatif.rs
@@ -8,11 +8,11 @@
use std::collections::{HashMap, HashSet};
+use gridcraft_calc::Calc;
+use gridcraft_core::{CellRef, RangeRef, Value};
+use gridcraft_model::{CalcMode, Cell, DefinedName, Sheet, StyleId, Workbook};
use serde::{Deserialize, Serialize};
use serde_json::{Value as Json, json};
-use sheetcraft_calc::Calc;
-use sheetcraft_core::{CellRef, RangeRef, Value};
-use sheetcraft_model::{CalcMode, Cell, DefinedName, Sheet, StyleId, Workbook};
use super::*;
@@ -162,7 +162,7 @@ fn json_value(v: &Json, wb: &Workbook) -> Value {
Json::Bool(b) => Value::Bool(*b),
Json::Number(n) => n.as_f64().map(Value::number).unwrap_or_default(),
Json::String(s) if s.is_empty() => Value::Empty,
- Json::String(s) => sheetcraft_core::parse::parse_input(s, wb.date_system).value,
+ Json::String(s) => gridcraft_core::parse::parse_input(s, wb.date_system).value,
other => Value::text(other.to_string()),
}
}
@@ -309,7 +309,7 @@ fn goal_seek(s: &mut Session, p: &Json) -> Result {
let (ssi, set) = cell_arg(s, p, "set", CMD)?;
let (csi, chg) = cell_arg(s, p, "changing", CMD)?;
let target = f64_param(p, "to")
- .or_else(|| str_param(p, "to").and_then(sheetcraft_core::parse::parse_number_text))
+ .or_else(|| str_param(p, "to").and_then(gridcraft_core::parse::parse_number_text))
.filter(|v| v.is_finite())
.ok_or_else(|| bad(CMD, "`to` must be a number"))?;
let d = s.doc()?;
@@ -403,7 +403,7 @@ fn store_scenarios(wb: &mut Workbook, list: &[Scenario]) {
.map(|c| format!("\"{}\"", c.iter().collect::().replace('"', "\"\"")))
.collect::>()
.join("&"),
- comment: "SheetCraft Scenario Manager data".into(),
+ comment: "GridCraft Scenario Manager data".into(),
hidden: true,
});
}
@@ -584,7 +584,7 @@ fn scenario_summary(s: &mut Session, p: &Json) -> Result {
for (i, c) in changing.iter().enumerate() {
let fmt = changing_fmts.get(i).cloned().unwrap_or_default();
let st = cx.wb.styles.derive(StyleId::DEFAULT, |st| st.num_fmt = fmt);
- put(&mut sheet, row, 1, Value::text(format!("${}${}", sheetcraft_core::col_to_letters(c.col), c.row + 1)), StyleId::DEFAULT);
+ put(&mut sheet, row, 1, Value::text(format!("${}${}", gridcraft_core::col_to_letters(c.col), c.row + 1)), StyleId::DEFAULT);
let cur = current.get(i).cloned().unwrap_or_default();
put(&mut sheet, row, 2, cur.clone(), st);
for (k, m) in per.iter().enumerate() {
@@ -598,7 +598,7 @@ fn scenario_summary(s: &mut Session, p: &Json) -> Result {
for (i, c) in results.iter().enumerate() {
let fmt = result_fmts.get(i).cloned().unwrap_or_default();
let st = cx.wb.styles.derive(StyleId::DEFAULT, |st| st.num_fmt = fmt);
- put(&mut sheet, row, 1, Value::text(format!("${}${}", sheetcraft_core::col_to_letters(c.col), c.row + 1)), StyleId::DEFAULT);
+ put(&mut sheet, row, 1, Value::text(format!("${}${}", gridcraft_core::col_to_letters(c.col), c.row + 1)), StyleId::DEFAULT);
put(&mut sheet, row, 2, current_results.get(i).cloned().unwrap_or_default(), st);
for (k, r) in scenario_results.iter().enumerate() {
put(&mut sheet, row, 3 + k as u32, r.get(i).cloned().unwrap_or_default(), st);
@@ -613,8 +613,8 @@ fn scenario_summary(s: &mut Session, p: &Json) -> Result {
Value::text("Notes: Current Values column represents values of changing cells at time Scenario Summary Report was created."),
StyleId::DEFAULT,
);
- sheet.cols.insert(1, sheetcraft_model::LineInfo { size: Some(110.0), ..Default::default() });
- sheet.cols.insert(2, sheetcraft_model::LineInfo { size: Some(100.0), ..Default::default() });
+ sheet.cols.insert(1, gridcraft_model::LineInfo { size: Some(110.0), ..Default::default() });
+ sheet.cols.insert(2, gridcraft_model::LineInfo { size: Some(100.0), ..Default::default() });
cx.wb.sheets.push(std::sync::Arc::new(sheet));
cx.structural = true;
Ok(cx.wb.sheets.len() - 1)
@@ -710,7 +710,7 @@ enum Cond {
/// Column offset in the list, comparison operator and operand text.
Compare(u32, Value),
/// A computed criterion: formula relative to the first data row, evaluated at `at`.
- Computed(sheetcraft_formula::Expr, CellRef),
+ Computed(gridcraft_formula::Expr, CellRef),
}
/// Splits `">=10"` into (`">="`, `"10"`); text without an operator means "begins with".
@@ -741,7 +741,7 @@ pub(crate) fn criterion_matches(v: &Value, text: &str, crit: &Value) -> bool {
_ => true,
};
}
- let num = sheetcraft_core::parse::parse_number_text(operand);
+ let num = gridcraft_core::parse::parse_number_text(operand);
let lower_text = text.to_lowercase();
let lower_op = operand.to_lowercase();
use std::cmp::Ordering;
@@ -750,7 +750,7 @@ pub(crate) fn criterion_matches(v: &Value, text: &str, crit: &Value) -> bool {
(Value::Number(_), None) => None,
(Value::Empty, _) => None,
(_, Some(_)) if op != "begins" => None,
- _ => Some(sheetcraft_core::compare_text(text, operand)),
+ _ => Some(gridcraft_core::compare_text(text, operand)),
};
let wild = |pat: &str| super::edit::wildcard(&lower_text, pat);
match op {
@@ -845,8 +845,8 @@ fn advanced_filter(s: &mut Session, p: &Json) -> Result {
criterion_matches(&v, &text, crit)
}
Cond::Computed(e, at) => {
- let shifted = sheetcraft_formula::adjust::shift_relative(e.clone(), row as i64 - first_data as i64, 0);
- let v = sheetcraft_calc::recalc::evaluate_expr(wb, csi, *at, &shifted);
+ let shifted = gridcraft_formula::adjust::shift_relative(e.clone(), row as i64 - first_data as i64, 0);
+ let v = gridcraft_calc::recalc::evaluate_expr(wb, csi, *at, &shifted);
matches!(v.scalar().to_bool(), Ok(true))
}
})
@@ -913,7 +913,7 @@ fn advanced_filter(s: &mut Session, p: &Json) -> Result {
if *k {
if let Some(e) = shm.rows.get_mut(row) {
e.hidden = false;
- if *e == sheetcraft_model::LineInfo::default() {
+ if *e == gridcraft_model::LineInfo::default() {
shm.rows.remove(row);
}
}
@@ -964,7 +964,7 @@ impl Agg {
"countNums" | "countnums" => Value::number(self.nums as f64),
"average" => {
if self.nums == 0 {
- Value::Error(sheetcraft_core::CellError::Div0)
+ Value::Error(gridcraft_core::CellError::Div0)
} else {
Value::number(self.sum / self.nums as f64)
}
@@ -1077,8 +1077,8 @@ fn consolidate(s: &mut Session, p: &Json) -> Result {
}
}
let (out_r0, out_c0) = (dest.row + top as u32, dest.col + left as u32);
- if out_r0 as u64 + row_keys.len() as u64 > sheetcraft_core::MAX_ROWS as u64
- || out_c0 as u64 + col_keys.len() as u64 > sheetcraft_core::MAX_COLS as u64
+ if out_r0 as u64 + row_keys.len() as u64 > gridcraft_core::MAX_ROWS as u64
+ || out_c0 as u64 + col_keys.len() as u64 > gridcraft_core::MAX_COLS as u64
{
return Err(EngineError::Other("The consolidated data doesn't fit below and right of the destination.".into()));
}
@@ -1121,8 +1121,8 @@ fn consolidate(s: &mut Session, p: &Json) -> Result {
#[cfg(test)]
mod tests {
+ use gridcraft_core::{CellRef, Value};
use serde_json::json;
- use sheetcraft_core::{CellRef, Value};
use crate::Session;
diff --git a/crates/engine/src/display.rs b/crates/engine/src/display.rs
index ed7b699..24df503 100644
--- a/crates/engine/src/display.rs
+++ b/crates/engine/src/display.rs
@@ -3,9 +3,9 @@
use std::collections::HashMap;
use std::sync::Mutex;
-use sheetcraft_core::{CellRef, RangeRef, Value};
-use sheetcraft_model::{Sheet, Workbook};
-use sheetcraft_numfmt::{Formatted, NumberFormat, format_value};
+use gridcraft_core::{CellRef, RangeRef, Value};
+use gridcraft_model::{Sheet, Workbook};
+use gridcraft_numfmt::{Formatted, NumberFormat, format_value};
/// Parsed number formats are cached by code.
fn parsed(code: &str) -> NumberFormat {
diff --git a/crates/engine/src/fill.rs b/crates/engine/src/fill.rs
index 6789d52..cf9e08f 100644
--- a/crates/engine/src/fill.rs
+++ b/crates/engine/src/fill.rs
@@ -3,9 +3,9 @@
use std::sync::Arc;
-use sheetcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial, serial_from_ymd};
-use sheetcraft_core::{CellRef, RangeRef, Value};
-use sheetcraft_model::{Cell, Formula};
+use gridcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial, serial_from_ymd};
+use gridcraft_core::{CellRef, RangeRef, Value};
+use gridcraft_model::{Cell, Formula};
use crate::Result;
use crate::cmd::Ctx;
@@ -87,7 +87,7 @@ pub fn fill(cx: &mut Ctx, sheet: usize, src: RangeRef, target: RangeRef, mode: F
let style = if mode == FillMode::ValuesOnly { sh.cell(*t).map(|c| c.style).unwrap_or_default() } else { cell.style };
if let Some(f) = &cell.formula {
if let Some(e) = f.expr() {
- let shifted = sheetcraft_formula::adjust::shift_relative(e, t.row as i64 - sc.row as i64, t.col as i64 - sc.col as i64);
+ let shifted = gridcraft_formula::adjust::shift_relative(e, t.row as i64 - sc.row as i64, t.col as i64 - sc.col as i64);
cell.formula = Some(Arc::new(Formula::from_expr(shifted)));
cell.value = Value::Empty;
}
@@ -151,7 +151,7 @@ enum Pattern {
}
impl Pattern {
- fn detect(cells: &[(CellRef, Option)], custom: &[Vec], sys: sheetcraft_core::DateSystem, fmt: &str) -> Pattern {
+ fn detect(cells: &[(CellRef, Option)], custom: &[Vec], sys: gridcraft_core::DateSystem, fmt: &str) -> Pattern {
let vals: Vec = cells.iter().map(|(_, c)| c.as_ref().map(|c| c.value.clone()).unwrap_or_default()).collect();
if cells.iter().any(|(_, c)| c.as_ref().is_some_and(|c| c.formula.is_some())) {
return Pattern::Copy;
@@ -258,9 +258,9 @@ impl Pattern {
}
Pattern::Months { last, first, months } => {
let base = if forward { *last } else { *first };
- let d = datetime_from_serial(sheetcraft_core::DateSystem::D1900, base)?;
+ let d = datetime_from_serial(gridcraft_core::DateSystem::D1900, base)?;
let m = d.month as i64 + months * k * sign;
- let s = serial_from_ymd(sheetcraft_core::DateSystem::D1900, d.year as i64, m, d.day as i64)?;
+ let s = serial_from_ymd(gridcraft_core::DateSystem::D1900, d.year as i64, m, d.day as i64)?;
Some(Value::number(s))
}
}
@@ -363,7 +363,7 @@ pub fn flash_fill(cx: &mut Ctx, sheet: usize, at: CellRef) -> Result {
top -= 1;
}
let mut bottom = at.row;
- while bottom + 1 < sheetcraft_core::MAX_ROWS && !sh.value(CellRef::new(bottom + 1, src_col)).is_empty() && bottom - top < 1_000_000 {
+ while bottom + 1 < gridcraft_core::MAX_ROWS && !sh.value(CellRef::new(bottom + 1, src_col)).is_empty() && bottom - top < 1_000_000 {
bottom += 1;
}
// Header row: skip a first row whose target is text and source looks like a header? Keep simple.
diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs
index 5de8d49..aef3681 100644
--- a/crates/engine/src/io.rs
+++ b/crates/engine/src/io.rs
@@ -2,12 +2,12 @@
use std::fmt::Write as _;
-use sheetcraft_core::CellRef;
-use sheetcraft_model::Workbook;
+use gridcraft_core::CellRef;
+use gridcraft_model::Workbook;
use crate::{EngineError, Result};
-/// File formats SheetCraft reads or writes.
+/// File formats GridCraft reads or writes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileKind {
Xlsx,
@@ -35,14 +35,14 @@ impl FileKind {
/// don't say.
pub fn open_bytes(name: &str, bytes: &[u8]) -> Result<(Workbook, Vec)> {
let kind = FileKind::from_path(name);
- let sniffed = sheetcraft_xlsx::sniff(bytes);
- if sniffed == sheetcraft_xlsx::Format::Xlsx || kind == Some(FileKind::Xlsx) {
- let (wb, report) = sheetcraft_xlsx::read_xlsx(bytes).map_err(|e| EngineError::Other(format!("We can't open '{name}': {e}")))?;
+ let sniffed = gridcraft_xlsx::sniff(bytes);
+ if sniffed == gridcraft_xlsx::Format::Xlsx || kind == Some(FileKind::Xlsx) {
+ let (wb, report) = gridcraft_xlsx::read_xlsx(bytes).map_err(|e| EngineError::Other(format!("We can't open '{name}': {e}")))?;
return Ok((wb, report.warnings));
}
match kind {
Some(FileKind::Json) => {
- let mut wb: Workbook = serde_json::from_slice(bytes).map_err(|e| EngineError::Other(format!("not a SheetCraft JSON workbook: {e}")))?;
+ let mut wb: Workbook = serde_json::from_slice(bytes).map_err(|e| EngineError::Other(format!("not a GridCraft JSON workbook: {e}")))?;
wb.styles.rebuild_index();
if wb.sheets.is_empty() {
wb = Workbook::new();
@@ -50,14 +50,14 @@ pub fn open_bytes(name: &str, bytes: &[u8]) -> Result<(Workbook, Vec)> {
Ok((wb, vec![]))
}
Some(FileKind::Tsv) => {
- let opts = sheetcraft_xlsx::CsvOptions { delimiter: b'\t', ..Default::default() };
- let mut wb = sheetcraft_xlsx::read_csv(bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
+ let opts = gridcraft_xlsx::CsvOptions { delimiter: b'\t', ..Default::default() };
+ let mut wb = gridcraft_xlsx::read_csv(bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
rename_first_sheet(&mut wb, name);
Ok((wb, vec![]))
}
_ => {
- let opts = sheetcraft_xlsx::CsvOptions { delimiter: 0, ..Default::default() };
- let mut wb = sheetcraft_xlsx::read_csv(bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
+ let opts = gridcraft_xlsx::CsvOptions { delimiter: 0, ..Default::default() };
+ let mut wb = gridcraft_xlsx::read_csv(bytes, &opts).map_err(|e| EngineError::Other(e.to_string()))?;
rename_first_sheet(&mut wb, name);
Ok((wb, vec![]))
}
@@ -80,9 +80,9 @@ fn rename_first_sheet(wb: &mut Workbook, path: &str) {
pub fn save_bytes(wb: &Workbook, path: &str) -> Result> {
let sheet = wb.active_sheet;
match FileKind::from_path(path).unwrap_or(FileKind::Xlsx) {
- FileKind::Xlsx => sheetcraft_xlsx::write_xlsx(wb).map_err(|e| EngineError::Other(e.to_string())),
- FileKind::Csv => Ok(wb.sheet(sheet).map(|sh| sheetcraft_xlsx::write_csv(sh, wb, b',')).unwrap_or_default()),
- FileKind::Tsv => Ok(wb.sheet(sheet).map(|sh| sheetcraft_xlsx::write_csv(sh, wb, b'\t')).unwrap_or_default()),
+ FileKind::Xlsx => gridcraft_xlsx::write_xlsx(wb).map_err(|e| EngineError::Other(e.to_string())),
+ FileKind::Csv => Ok(wb.sheet(sheet).map(|sh| gridcraft_xlsx::write_csv(sh, wb, b',')).unwrap_or_default()),
+ FileKind::Tsv => Ok(wb.sheet(sheet).map(|sh| gridcraft_xlsx::write_csv(sh, wb, b'\t')).unwrap_or_default()),
FileKind::Json => serde_json::to_vec_pretty(wb).map_err(|e| EngineError::Other(e.to_string())),
FileKind::Html => Ok(to_html(wb, sheet).into_bytes()),
}
@@ -123,16 +123,16 @@ pub fn to_html(wb: &Workbook, sheet: usize) -> String {
if let Some(h) = st.font.color.hex(&wb.theme) {
let _ = write!(css, "color:{h};");
}
- if st.fill.pattern != sheetcraft_model::PatternType::None
+ if st.fill.pattern != gridcraft_model::PatternType::None
&& let Some(h) = st.fill.fg.hex(&wb.theme)
{
let _ = write!(css, "background:{h};");
}
let v = sh.value(c);
let align = match st.align.h {
- sheetcraft_model::HAlign::Center => "center",
- sheetcraft_model::HAlign::Right => "right",
- sheetcraft_model::HAlign::Left => "left",
+ gridcraft_model::HAlign::Center => "center",
+ gridcraft_model::HAlign::Right => "right",
+ gridcraft_model::HAlign::Left => "left",
_ if v.is_number() => "right",
_ => "left",
};
diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs
index 0871a9b..a13e5ce 100644
--- a/crates/engine/src/lib.rs
+++ b/crates/engine/src/lib.rs
@@ -1,4 +1,4 @@
-//! The SheetCraft engine façade.
+//! The GridCraft engine façade.
//!
//! Every user-visible action is a command with a stable id (`cell.set`, `home.bold`,
//! `data.sortAscending`…) and JSON parameters. The egui UI, the CLI, the control channel and MCP
@@ -19,19 +19,19 @@ pub mod tables;
use std::sync::Arc;
+use gridcraft_calc::Calc;
+use gridcraft_core::{CellRef, RangeRef};
+use gridcraft_model::Workbook;
use serde::Serialize;
use serde_json::Value as Json;
-use sheetcraft_calc::Calc;
-use sheetcraft_core::{CellRef, RangeRef};
-use sheetcraft_model::Workbook;
pub use cmd::{CommandInfo, CommandSpec, command_specs, find_command};
+pub use gridcraft_calc as calc;
+pub use gridcraft_core as core;
+pub use gridcraft_formula as formula;
+pub use gridcraft_model as model;
+pub use gridcraft_numfmt as numfmt;
pub use selection::Selection;
-pub use sheetcraft_calc as calc;
-pub use sheetcraft_core as core;
-pub use sheetcraft_formula as formula;
-pub use sheetcraft_model as model;
-pub use sheetcraft_numfmt as numfmt;
#[derive(Debug, thiserror::Error)]
pub enum EngineError {
@@ -157,9 +157,9 @@ impl Default for Prefs {
Prefs {
enter_direction: "down".into(),
autocomplete: true,
- user_name: "SheetCraft User".into(),
- default_font: sheetcraft_model::DEFAULT_FONT.into(),
- default_font_size: sheetcraft_model::DEFAULT_FONT_SIZE,
+ user_name: "GridCraft User".into(),
+ default_font: gridcraft_model::DEFAULT_FONT.into(),
+ default_font_size: gridcraft_model::DEFAULT_FONT_SIZE,
sheets_in_new_workbook: 1,
r1c1: false,
user_dictionary: vec![],
@@ -248,7 +248,7 @@ impl Session {
let mut wb = Workbook::new();
for _ in 1..self.prefs.sheets_in_new_workbook.clamp(1, 255) {
let name = wb.next_sheet_name();
- wb.sheets.push(Arc::new(sheetcraft_model::Sheet::new(name)));
+ wb.sheets.push(Arc::new(gridcraft_model::Sheet::new(name)));
}
self.add_document(DocState::new(wb, None, format!("Book{n}")))
}
@@ -314,7 +314,7 @@ impl Session {
}
/// Convenience: the active workbook's active sheet.
-pub fn active_sheet(s: &Session) -> Option<&sheetcraft_model::Sheet> {
+pub fn active_sheet(s: &Session) -> Option<&gridcraft_model::Sheet> {
s.active().and_then(|d| d.wb.active())
}
diff --git a/crates/engine/src/pivot.rs b/crates/engine/src/pivot.rs
index 757ee6d..02a77f2 100644
--- a/crates/engine/src/pivot.rs
+++ b/crates/engine/src/pivot.rs
@@ -8,9 +8,9 @@
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
-use sheetcraft_core::date::datetime_from_serial;
-use sheetcraft_core::{CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
-use sheetcraft_model::*;
+use gridcraft_core::date::datetime_from_serial;
+use gridcraft_core::{CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value};
+use gridcraft_model::*;
/// Largest source block read (cells).
pub const MAX_SOURCE_CELLS: u64 = 10_000_000;
@@ -67,7 +67,7 @@ pub fn quote_sheet(name: &str) -> String {
/// Absolute reference text for a range on a sheet: `Sheet1!$A$1:$E$20`.
pub fn range_text(sheet_name: &str, r: RangeRef) -> String {
- let abs = |c: CellRef| format!("${}${}", sheetcraft_core::col_to_letters(c.col), c.row as u64 + 1);
+ let abs = |c: CellRef| format!("${}${}", gridcraft_core::col_to_letters(c.col), c.row as u64 + 1);
format!("{}!{}:{}", quote_sheet(sheet_name), abs(r.start), abs(r.end))
}
diff --git a/crates/engine/src/sample.rs b/crates/engine/src/sample.rs
index a322d21..c2ef603 100644
--- a/crates/engine/src/sample.rs
+++ b/crates/engine/src/sample.rs
@@ -3,8 +3,8 @@
use std::sync::Arc;
+use gridcraft_model::{Sheet, Workbook};
use serde_json::json;
-use sheetcraft_model::{Sheet, Workbook};
use crate::{DocState, Session};
@@ -39,7 +39,7 @@ pub fn build(name: &str) -> Option {
let mut wb = (*s.doc().ok()?.wb).clone();
wb.active_sheet = 0;
if let Some(sh) = wb.sheet_mut(0) {
- sh.view_active = sheetcraft_core::CellRef::default();
+ sh.view_active = gridcraft_core::CellRef::default();
}
Some(wb)
}
@@ -100,7 +100,7 @@ fn sales(s: &mut Session) -> bool {
if let Some(sh) = wb.sheet_mut(0)
&& let Some(c) = sh.charts.last_mut()
{
- c.series = vec![sheetcraft_model::Series {
+ c.series = vec![gridcraft_model::Series {
name: Some("Sales!$G$4".into()),
categories: Some("Sales!$B$5:$B$10".into()),
values: "Sales!$G$5:$G$10".into(),
diff --git a/crates/engine/src/selection.rs b/crates/engine/src/selection.rs
index 4fccfb4..ccace56 100644
--- a/crates/engine/src/selection.rs
+++ b/crates/engine/src/selection.rs
@@ -1,8 +1,8 @@
//! Cell selection: one or more ranges with an active cell.
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
+use gridcraft_model::Sheet;
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
-use sheetcraft_model::Sheet;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Selection {
@@ -212,7 +212,7 @@ pub fn current_region(sheet: &Sheet, c: CellRef) -> RangeRef {
#[cfg(test)]
mod tests {
use super::*;
- use sheetcraft_core::Value;
+ use gridcraft_core::Value;
#[test]
fn jumps() {
diff --git a/crates/engine/src/tables.rs b/crates/engine/src/tables.rs
index b239e70..62cb59c 100644
--- a/crates/engine/src/tables.rs
+++ b/crates/engine/src/tables.rs
@@ -1,8 +1,8 @@
//! Table styles: how a cell inside a table looks (our own palettes, keyed by the familiar
//! `TableStyleLight1…21 / Medium1…28 / Dark1…11` names so files round-trip).
-use sheetcraft_core::CellRef;
-use sheetcraft_model::*;
+use gridcraft_core::CellRef;
+use gridcraft_model::*;
/// (family, number) from a style name, e.g. `TableStyleMedium2` → ("Medium", 2).
pub fn parse_style_name(name: &str) -> (&'static str, u32) {
diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs
index da0bae3..f3e36cd 100644
--- a/crates/engine/src/tests.rs
+++ b/crates/engine/src/tests.rs
@@ -1,5 +1,5 @@
+use gridcraft_core::{CellRef, Value};
use serde_json::json;
-use sheetcraft_core::{CellRef, Value};
use crate::Session;
@@ -182,7 +182,7 @@ fn ink_strokes_and_ink_to_shape() {
s.execute("draw.stroke", json!({"points": pts})).unwrap();
let sh = s.doc().unwrap().wb.active().unwrap().clone();
assert_eq!(sh.shapes.len(), 1);
- assert_eq!(sh.shapes[0].kind, sheetcraft_model::ShapeKind::Ink);
+ assert_eq!(sh.shapes[0].kind, gridcraft_model::ShapeKind::Ink);
let r = s.execute("draw.inkToShape", json!({})).unwrap();
assert_eq!(r["kind"], "Rectangle");
// An open stroke becomes a line.
diff --git a/crates/formula/Cargo.toml b/crates/formula/Cargo.toml
index 2e9692d..e1b0ca7 100644
--- a/crates/formula/Cargo.toml
+++ b/crates/formula/Cargo.toml
@@ -1,14 +1,14 @@
[package]
-name = "sheetcraft-formula"
+name = "gridcraft-formula"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft formula language: lexer, parser, printer and reference adjustment"
+description = "GridCraft formula language: lexer, parser, printer and reference adjustment"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
+gridcraft-core = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
diff --git a/crates/formula/src/adjust.rs b/crates/formula/src/adjust.rs
index d38aa9f..734ff5c 100644
--- a/crates/formula/src/adjust.rs
+++ b/crates/formula/src/adjust.rs
@@ -1,7 +1,7 @@
//! Reference adjustment: copying formulas, inserting/deleting rows and columns, moving cells,
//! renaming and deleting sheets.
-use sheetcraft_core::{CellError, MAX_COLS, MAX_ROWS, RangeRef};
+use gridcraft_core::{CellError, MAX_COLS, MAX_ROWS, RangeRef};
use crate::ast::*;
diff --git a/crates/formula/src/ast.rs b/crates/formula/src/ast.rs
index c8b4592..51e5068 100644
--- a/crates/formula/src/ast.rs
+++ b/crates/formula/src/ast.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
+use gridcraft_core::{CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef};
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{CellError, CellRef, MAX_COLS, MAX_ROWS, RangeRef};
/// One end of a reference, with `$` anchors.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
diff --git a/crates/formula/src/lexer.rs b/crates/formula/src/lexer.rs
index 1e24a25..b6143ba 100644
--- a/crates/formula/src/lexer.rs
+++ b/crates/formula/src/lexer.rs
@@ -2,7 +2,7 @@
//!
//! Token spans are byte offsets into the source, which the editor uses to colour references.
-use sheetcraft_core::CellError;
+use gridcraft_core::CellError;
#[derive(Clone, Debug, PartialEq)]
pub enum Tok {
diff --git a/crates/formula/src/parser.rs b/crates/formula/src/parser.rs
index 00d8663..f501402 100644
--- a/crates/formula/src/parser.rs
+++ b/crates/formula/src/parser.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
-use sheetcraft_core::addr::{letters_to_col, parse_a1_prefix};
-use sheetcraft_core::{CellError, MAX_ROWS};
+use gridcraft_core::addr::{letters_to_col, parse_a1_prefix};
+use gridcraft_core::{CellError, MAX_ROWS};
use crate::ast::*;
use crate::lexer::{Tok, Token, tokenize};
diff --git a/crates/formula/src/printer.rs b/crates/formula/src/printer.rs
index 2ffafa6..0aa223e 100644
--- a/crates/formula/src/printer.rs
+++ b/crates/formula/src/printer.rs
@@ -2,7 +2,7 @@
use std::fmt::Write;
-use sheetcraft_core::{CellRef, col_to_letters, number_to_text};
+use gridcraft_core::{CellRef, col_to_letters, number_to_text};
use crate::ast::*;
@@ -16,7 +16,7 @@ pub fn needs_quotes(name: &str) -> bool {
return true;
}
// Looks like a cell ref (`A1`, `R1C1`) or a boolean.
- sheetcraft_core::CellRef::parse(name).is_some() || name.eq_ignore_ascii_case("TRUE") || name.eq_ignore_ascii_case("FALSE")
+ gridcraft_core::CellRef::parse(name).is_some() || name.eq_ignore_ascii_case("TRUE") || name.eq_ignore_ascii_case("FALSE")
}
pub fn quote_sheet(name: &str) -> String {
diff --git a/crates/formula/src/tests.rs b/crates/formula/src/tests.rs
index 46264f4..ff84a5b 100644
--- a/crates/formula/src/tests.rs
+++ b/crates/formula/src/tests.rs
@@ -122,7 +122,7 @@ fn insert_delete() {
#[test]
fn moves_and_renames() {
- let mv = Edit::Move { from: sheetcraft_core::RangeRef::parse("A1:B2").unwrap(), to_row: 9, to_col: 3 };
+ let mv = Edit::Move { from: gridcraft_core::RangeRef::parse("A1:B2").unwrap(), to_row: 9, to_col: 3 };
let e = parse("A1+B2+C3+SUM(A1:B2)").unwrap();
assert_eq!(print(&adjust(e, "S", "S", &mv)), "D10+E11+C3+SUM(D10:E11)");
let e = parse("Old!A1+'Old'!B2").unwrap();
@@ -132,7 +132,7 @@ fn moves_and_renames() {
#[test]
fn r1c1() {
let e = parse("A1+$B$2+SUM(C:C)").unwrap();
- assert_eq!(print_r1c1(&e, sheetcraft_core::CellRef::new(1, 1)), "R[-1]C[-1]+R2C2+SUM(C[1])");
+ assert_eq!(print_r1c1(&e, gridcraft_core::CellRef::new(1, 1)), "R[-1]C[-1]+R2C2+SUM(C[1])");
}
#[test]
diff --git a/crates/functions/Cargo.toml b/crates/functions/Cargo.toml
index 0f8bbcf..97f51c2 100644
--- a/crates/functions/Cargo.toml
+++ b/crates/functions/Cargo.toml
@@ -1,13 +1,13 @@
[package]
-name = "sheetcraft-functions"
+name = "gridcraft-functions"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft worksheet function library: math, statistics, text, dates, lookup, financial, engineering"
+description = "GridCraft worksheet function library: math, statistics, text, dates, lookup, financial, engineering"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
+gridcraft-core = { workspace = true }
regex = { workspace = true }
diff --git a/crates/functions/src/criteria.rs b/crates/functions/src/criteria.rs
index f3758e7..2563220 100644
--- a/crates/functions/src/criteria.rs
+++ b/crates/functions/src/criteria.rs
@@ -2,8 +2,8 @@
use std::cmp::Ordering;
-use sheetcraft_core::parse::parse_number_text;
-use sheetcraft_core::{CellError, Value, compare_text};
+use gridcraft_core::parse::parse_number_text;
+use gridcraft_core::{CellError, Value, compare_text};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Op {
diff --git a/crates/functions/src/database.rs b/crates/functions/src/database.rs
index 35ae71f..38e3729 100644
--- a/crates/functions/src/database.rs
+++ b/crates/functions/src/database.rs
@@ -8,7 +8,7 @@
use std::borrow::Cow;
-use sheetcraft_core::{Array, CellError, Value, compare_text};
+use gridcraft_core::{Array, CellError, Value, compare_text};
use crate::criteria::Criterion;
use crate::util::{A, R, as_array, has, num_val};
@@ -19,7 +19,7 @@ fn find_column(db: &Array, label: &str) -> Option {
let label = label.trim();
(0..db.cols).find(|&c| match db.get(0, c) {
Some(Value::Text(t)) => compare_text(t.trim(), label).is_eq(),
- Some(Value::Number(n)) => sheetcraft_core::number_to_text(*n) == label,
+ Some(Value::Number(n)) => gridcraft_core::number_to_text(*n) == label,
_ => false,
})
}
@@ -54,7 +54,7 @@ fn cell_criterion(v: &Value) -> Option {
let s: &str = t;
let has_op = s.starts_with(['=', '<', '>']);
let plain_text = !has_op
- && sheetcraft_core::parse::parse_number_text(s).is_none()
+ && gridcraft_core::parse::parse_number_text(s).is_none()
&& !s.eq_ignore_ascii_case("TRUE")
&& !s.eq_ignore_ascii_case("FALSE")
&& CellError::parse(s).is_none();
@@ -75,7 +75,7 @@ fn matching_records(db: &Array, crit: &Array) -> Vec {
let Some(cr) = cell_criterion(cell) else { continue };
let col = match crit.get(0, c) {
Some(Value::Text(t)) => find_column(db, t),
- Some(Value::Number(n)) => find_column(db, &sheetcraft_core::number_to_text(*n)),
+ Some(Value::Number(n)) => find_column(db, &gridcraft_core::number_to_text(*n)),
_ => None,
};
conds.push((col, cr));
@@ -293,7 +293,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
use crate::util::testutil::*;
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
/// The classic orchard table.
fn db() -> Value {
diff --git a/crates/functions/src/date.rs b/crates/functions/src/date.rs
index 11ef89c..f1f1bec 100644
--- a/crates/functions/src/date.rs
+++ b/crates/functions/src/date.rs
@@ -1,8 +1,8 @@
//! Date and time functions.
-use sheetcraft_core::date::{DateTime, datetime_from_serial, days_in_month, is_leap, serial_from_ymd};
-use sheetcraft_core::parse::parse_input;
-use sheetcraft_core::{CellError, DateSystem, Value};
+use gridcraft_core::date::{DateTime, datetime_from_serial, days_in_month, is_leap, serial_from_ymd};
+use gridcraft_core::parse::parse_input;
+use gridcraft_core::{CellError, DateSystem, Value};
use crate::util::{R, S, arg, as_array, has, num, num_val, opt_bool, opt_num, scalar, text, to_int};
use crate::{Arg, Ctx, FnSpec};
@@ -565,7 +565,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
@@ -748,7 +748,7 @@ mod tests {
#[test]
fn system_1904() {
- let mut ctx = TestCtx { sys: sheetcraft_core::DateSystem::D1904, ..TestCtx::default() };
+ let mut ctx = TestCtx { sys: gridcraft_core::DateSystem::D1904, ..TestCtx::default() };
let spec = crate::lookup("DATE").unwrap();
close(crate::call(spec, &[n(2000.0), n(1.0), n(1.0)], &mut ctx), 35064.0);
let spec = crate::lookup("WEEKDAY").unwrap();
diff --git a/crates/functions/src/dynamic.rs b/crates/functions/src/dynamic.rs
index 77b8ea2..3a66ca8 100644
--- a/crates/functions/src/dynamic.rs
+++ b/crates/functions/src/dynamic.rs
@@ -9,7 +9,7 @@
use std::cmp::Ordering;
use std::collections::HashMap;
-use sheetcraft_core::{Array, CellError, Value, sort_compare};
+use gridcraft_core::{Array, CellError, Value, sort_compare};
use crate::util::{A, MAX_CELLS, R, arg, array_val, as_array, has, num, opt_bool, scalar};
use crate::{Arg, Ctx, FnSpec, VAR};
@@ -1030,7 +1030,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
diff --git a/crates/functions/src/engineering.rs b/crates/functions/src/engineering.rs
index 5fc8ea2..f34ce99 100644
--- a/crates/functions/src/engineering.rs
+++ b/crates/functions/src/engineering.rs
@@ -3,7 +3,7 @@
use std::f64::consts::PI;
-use sheetcraft_core::{CellError, Value, number_to_text};
+use gridcraft_core::{CellError, Value, number_to_text};
use crate::special::{erf, erfc};
use crate::util::{A, R, S, flatten, has, num, num_val, opt_num, scalar, text, text_val};
@@ -1123,7 +1123,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use super::parse_complex;
use crate::util::testutil::*;
diff --git a/crates/functions/src/financial.rs b/crates/functions/src/financial.rs
index 5700908..17cc789 100644
--- a/crates/functions/src/financial.rs
+++ b/crates/functions/src/financial.rs
@@ -3,8 +3,8 @@
//! Sign convention follows Excel: money paid out is negative, money received is positive.
//! Date arguments are serial numbers (truncated) in the 1900 date system.
-use sheetcraft_core::date::{datetime_from_serial, days_in_month, serial_from_ymd};
-use sheetcraft_core::{CellError, DateSystem, Value};
+use gridcraft_core::date::{datetime_from_serial, days_in_month, serial_from_ymd};
+use gridcraft_core::{CellError, DateSystem, Value};
use crate::util::{R, S, array_numbers, as_array, flatten, has, num, num_val, numbers, opt_bool, opt_num};
use crate::{Arg, Ctx, FnSpec, VAR};
@@ -1447,7 +1447,7 @@ pub(crate) fn specs() -> Vec {
mod tests {
use crate::Arg;
use crate::util::testutil::*;
- use sheetcraft_core::{CellError, DateSystem, Value, date::serial_from_ymd};
+ use gridcraft_core::{CellError, DateSystem, Value, date::serial_from_ymd};
fn d(y: i64, m: i64, dd: i64) -> Arg {
n(serial_from_ymd(DateSystem::D1900, y, m, dd).unwrap())
diff --git a/crates/functions/src/info.rs b/crates/functions/src/info.rs
index 5885676..84ca72a 100644
--- a/crates/functions/src/info.rs
+++ b/crates/functions/src/info.rs
@@ -2,7 +2,7 @@
use std::cmp::Ordering;
-use sheetcraft_core::{Array, CellError, Value, compare_text};
+use gridcraft_core::{Array, CellError, Value, compare_text};
use crate::util::{A, MAX_CELLS, R, S, has, scalar, text};
use crate::{Arg, Ctx, FnSpec, VAR};
@@ -103,7 +103,7 @@ fn info(a: &[Arg], _c: &mut dyn Ctx) -> R {
"directory" => "/".into(),
"numfile" => Value::Number(1.0),
"origin" => "$A:$A$1".into(),
- "osversion" => "SheetCraft".into(),
+ "osversion" => "GridCraft".into(),
"recalc" => "Automatic".into(),
"release" => "16.0".into(),
"system" => "pcdos".into(),
@@ -487,7 +487,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
diff --git a/crates/functions/src/lib.rs b/crates/functions/src/lib.rs
index 5d1af41..75c0283 100644
--- a/crates/functions/src/lib.rs
+++ b/crates/functions/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft worksheet function library.
+//! GridCraft worksheet function library.
//!
//! Every function is described by a [`FnSpec`] (name, arity, category, which parameters take
//! scalars, a signature and a one-line description) and implemented as a plain Rust function over
@@ -15,7 +15,7 @@
use std::collections::HashMap;
use std::sync::OnceLock;
-use sheetcraft_core::{CellError, DateSystem, Value};
+use gridcraft_core::{CellError, DateSystem, Value};
/// Builds a [`FnSpec`] whose implementation returns `Result`.
///
@@ -33,7 +33,7 @@ macro_rules! f {
signature: $sig,
description: $desc,
imp: {
- fn w(a: &[$crate::Arg], c: &mut dyn $crate::Ctx) -> sheetcraft_core::Value {
+ fn w(a: &[$crate::Arg], c: &mut dyn $crate::Ctx) -> gridcraft_core::Value {
$crate::util::finish(($imp)(a, c))
}
w
@@ -51,7 +51,7 @@ macro_rules! f {
signature: $sig,
description: $desc,
imp: {
- fn w(a: &[$crate::Arg], c: &mut dyn $crate::Ctx) -> sheetcraft_core::Value {
+ fn w(a: &[$crate::Arg], c: &mut dyn $crate::Ctx) -> gridcraft_core::Value {
$crate::util::finish(($imp)(a, c))
}
w
@@ -241,7 +241,7 @@ mod tests {
#[test]
fn hostile_inputs_never_panic() {
use crate::util::testutil::*;
- use sheetcraft_core::{Array, CellError};
+ use gridcraft_core::{Array, CellError};
let mixed = Value::from(
Array::new(2, 3, vec![Value::Number(1.0), Value::from("x"), Value::Empty, Value::Bool(true), Value::Number(-1e300), Value::Number(0.5)])
.unwrap(),
diff --git a/crates/functions/src/lift.rs b/crates/functions/src/lift.rs
index 998b437..cea0067 100644
--- a/crates/functions/src/lift.rs
+++ b/crates/functions/src/lift.rs
@@ -1,7 +1,7 @@
//! Automatic array lifting: a function given an array where it expects a scalar is applied to
//! each element, broadcasting single rows/columns like Excel's dynamic arrays.
-use sheetcraft_core::{Array, CellError, Value};
+use gridcraft_core::{Array, CellError, Value};
use crate::util::MAX_CELLS;
use crate::{Arg, Ctx, FnSpec};
@@ -53,7 +53,7 @@ pub(crate) fn call_lifted(spec: &FnSpec, args: &[Arg], ctx: &mut dyn Ctx) -> Val
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
use crate::util::{R, num};
@@ -89,7 +89,7 @@ mod tests {
is_err(crate::call(&s, &[n(1.0)], &mut TestCtx::default()), CellError::Value);
let huge = crate::call(
&s,
- &[av(sheetcraft_core::Array::filled(1, 100_000, nv(1.0)).into()), av(sheetcraft_core::Array::filled(100_000, 1, nv(1.0)).into())],
+ &[av(gridcraft_core::Array::filled(1, 100_000, nv(1.0)).into()), av(gridcraft_core::Array::filled(100_000, 1, nv(1.0)).into())],
&mut TestCtx::default(),
);
is_err(huge, CellError::Num);
diff --git a/crates/functions/src/lookup.rs b/crates/functions/src/lookup.rs
index 23b4e25..21294d4 100644
--- a/crates/functions/src/lookup.rs
+++ b/crates/functions/src/lookup.rs
@@ -5,7 +5,7 @@
use std::cmp::Ordering;
-use sheetcraft_core::{Array, CellError, Value, col_to_letters, compare};
+use gridcraft_core::{Array, CellError, Value, col_to_letters, compare};
use crate::criteria::{has_wildcards, lookup_equal};
use crate::util::{R, arg, array_val, as_array, has, int, num, opt_bool, opt_int, opt_num, text, text_val};
@@ -512,7 +512,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
diff --git a/crates/functions/src/math.rs b/crates/functions/src/math.rs
index 5f4bab4..3a0bab3 100644
--- a/crates/functions/src/math.rs
+++ b/crates/functions/src/math.rs
@@ -3,7 +3,7 @@
use std::f64::consts::PI;
-use sheetcraft_core::{Array, CellError, Value};
+use gridcraft_core::{Array, CellError, Value};
use crate::criteria::Criterion;
use crate::util::{
diff --git a/crates/functions/src/stat.rs b/crates/functions/src/stat.rs
index 0d2eee1..abdb4e7 100644
--- a/crates/functions/src/stat.rs
+++ b/crates/functions/src/stat.rs
@@ -1,7 +1,7 @@
//! Statistical functions: aggregates, order statistics, regression and probability
//! distributions (with their inverses).
-use sheetcraft_core::{CellError, Value};
+use gridcraft_core::{CellError, Value};
use crate::special::{erf, gamma, invert, ln_beta, ln_gamma, norm_cdf, norm_inv, norm_pdf, reg_gamma_p, reg_gamma_q, reg_inc_beta};
use crate::util::{A, R, S, arg, array_numbers, array_val, as_array, boolean, has, num, num_val, numbers, numbers_a, opt_num};
@@ -247,7 +247,7 @@ fn count_impl(a: &[Arg]) -> f64 {
Value::Number(_) => c += 1,
_ if x.from_ref => {}
Value::Bool(_) => c += 1,
- Value::Text(t) if sheetcraft_core::parse::parse_number_text(t).is_some() => c += 1,
+ Value::Text(t) if gridcraft_core::parse::parse_number_text(t).is_some() => c += 1,
_ => {}
}
}
@@ -2218,7 +2218,7 @@ pub(crate) fn specs() -> Vec {
mod tests {
use super::{aggregate_values, aggregate_values_k};
use crate::util::testutil::*;
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
const D7: f64 = 1e-6;
diff --git a/crates/functions/src/text.rs b/crates/functions/src/text.rs
index d5f7ab8..172c934 100644
--- a/crates/functions/src/text.rs
+++ b/crates/functions/src/text.rs
@@ -1,7 +1,7 @@
//! Text functions.
-use sheetcraft_core::parse::parse_number_text;
-use sheetcraft_core::{CellError, Value, number_to_text};
+use gridcraft_core::parse::parse_number_text;
+use gridcraft_core::{CellError, Value, number_to_text};
use crate::criteria::wildcard_find;
use crate::util::{
@@ -852,7 +852,7 @@ pub(crate) fn specs() -> Vec {
#[cfg(test)]
mod tests {
- use sheetcraft_core::{CellError, Value};
+ use gridcraft_core::{CellError, Value};
use crate::util::testutil::*;
diff --git a/crates/functions/src/util.rs b/crates/functions/src/util.rs
index bb2d31b..fca5dc4 100644
--- a/crates/functions/src/util.rs
+++ b/crates/functions/src/util.rs
@@ -3,7 +3,7 @@
use std::borrow::Cow;
use std::sync::Arc;
-use sheetcraft_core::{Array, CellError, Value};
+use gridcraft_core::{Array, CellError, Value};
use crate::Arg;
@@ -283,7 +283,7 @@ pub(crate) fn to_sig_digits(x: f64, sig: usize) -> f64 {
#[cfg(test)]
pub(crate) mod testutil {
- use sheetcraft_core::{Array, CellError, DateSystem, Value};
+ use gridcraft_core::{Array, CellError, DateSystem, Value};
use crate::{Arg, Ctx};
diff --git a/crates/mcp/Cargo.toml b/crates/mcp/Cargo.toml
index 90d8359..d634166 100644
--- a/crates/mcp/Cargo.toml
+++ b/crates/mcp/Cargo.toml
@@ -1,14 +1,14 @@
[package]
-name = "sheetcraft-mcp"
+name = "gridcraft-mcp"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft MCP server: JSON-RPC 2.0 over stdio, driving a running app or a headless session"
+description = "GridCraft MCP server: JSON-RPC 2.0 over stdio, driving a running app or a headless session"
[lints]
workspace = true
[dependencies]
-sheetcraft-engine = { workspace = true }
+gridcraft-engine = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
diff --git a/crates/mcp/src/backend.rs b/crates/mcp/src/backend.rs
index cd31151..adc866d 100644
--- a/crates/mcp/src/backend.rs
+++ b/crates/mcp/src/backend.rs
@@ -22,7 +22,7 @@ const IO_TIMEOUT: Duration = Duration::from_secs(30);
/// Longest reply line accepted from the app (guards against a runaway peer).
const MAX_REPLY: u64 = 256 * 1024 * 1024;
-/// A running SheetCraft app, reached through its loopback control port.
+/// A running GridCraft app, reached through its loopback control port.
pub struct Remote {
addr: String,
conn: Option<(BufReader, TcpStream)>,
@@ -91,7 +91,7 @@ impl Backend for Remote {
self.conn = None;
self.roundtrip(&line).map_err(|e| {
self.conn = None;
- format!("SheetCraft app at {} is not reachable: {e}", self.addr)
+ format!("GridCraft app at {} is not reachable: {e}", self.addr)
})?
}
};
@@ -108,6 +108,6 @@ impl Backend for Remote {
}
fn describe(&self) -> String {
- format!("connected to the SheetCraft app at {}", self.addr)
+ format!("connected to the GridCraft app at {}", self.addr)
}
}
diff --git a/crates/mcp/src/headless.rs b/crates/mcp/src/headless.rs
index 57fc5be..ec649ea 100644
--- a/crates/mcp/src/headless.rs
+++ b/crates/mcp/src/headless.rs
@@ -1,13 +1,13 @@
//! An in-process engine session that answers the engine-level control-channel methods itself.
+use gridcraft_engine::Session;
use serde_json::{Value, json};
-use sheetcraft_engine::Session;
use crate::backend::Backend;
/// Error for methods that need a window.
-pub(crate) const NO_UI: &str = "no UI in headless mode: start the app with `sheetcraft --control 7979` and run the MCP server \
-with `sheetcraft-cli mcp --connect 7979`";
+pub(crate) const NO_UI: &str = "no UI in headless mode: start the app with `gridcraft --control 7979` and run the MCP server \
+with `gridcraft-cli mcp --connect 7979`";
/// Headless backend: a [`Session`] with one blank workbook to start with.
pub struct Headless {
@@ -79,7 +79,7 @@ impl Backend for Headless {
m if m.starts_with("ui.") || m.starts_with("app.screenshot") => Err(format!("{m}: {NO_UI}")),
// Read-only engine methods (`document.inspect`, `sheet.read`, `cell.get`, …) are
// commands with the same id.
- m if sheetcraft_engine::find_command(m).is_some() => self.execute(m, params),
+ m if gridcraft_engine::find_command(m).is_some() => self.execute(m, params),
other => Err(format!("unknown method `{other}`")),
}
}
diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs
index 74125c1..de3e250 100644
--- a/crates/mcp/src/lib.rs
+++ b/crates/mcp/src/lib.rs
@@ -1,13 +1,13 @@
-//! SheetCraft's MCP server.
+//! GridCraft's MCP server.
//!
//! [Model Context Protocol](https://modelcontextprotocol.io) over stdio: newline-delimited
-//! JSON-RPC 2.0, hand-written (no async runtime). The server exposes SheetCraft as MCP tools and
+//! JSON-RPC 2.0, hand-written (no async runtime). The server exposes GridCraft as MCP tools and
//! resources and forwards everything to a [`Backend`]:
//!
//! - [`Remote`] talks to a running desktop app through its loopback JSON-lines control channel
-//! (`sheetcraft --control 7979`): one `{"id","method","params"}` line in, one
+//! (`gridcraft --control 7979`): one `{"id","method","params"}` line in, one
//! `{"id","ok","result"|"error"}` line out (see `docs/control-protocol.md`).
-//! - [`Headless`] hosts an in-process [`sheetcraft_engine::Session`] and implements the
+//! - [`Headless`] hosts an in-process [`gridcraft_engine::Session`] and implements the
//! engine-level control-channel methods itself, so agents can build workbooks without a window.
//!
//! Entry points: [`Server::serve`] (stdio loop) and [`Server::handle_line`] (one message).
diff --git a/crates/mcp/src/server.rs b/crates/mcp/src/server.rs
index e26787b..75b82d8 100644
--- a/crates/mcp/src/server.rs
+++ b/crates/mcp/src/server.rs
@@ -22,7 +22,7 @@ const RESOURCE_NOT_FOUND: i64 = -32002;
/// Longest accepted message line (16 MB): bigger payloads belong in files.
const MAX_LINE: usize = 16 * 1024 * 1024;
-const INSTRUCTIONS: &str = "SheetCraft is a spreadsheet app (an Excel clone). Cells use A1 notation (\"B2\", \"A1:D10\", \
+const INSTRUCTIONS: &str = "GridCraft is a spreadsheet app (an Excel clone). Cells use A1 notation (\"B2\", \"A1:D10\", \
\"Sheet2!A1:B3\"). Every action is a command: find ids and parameters with list_commands and run any of them with \
execute_command. Convenience tools cover the common flow: write_range / set_cell to enter values and formulas (strings \
starting with = are formulas), read_range / get_cell to read computed values back, format_range, create_table, \
@@ -31,9 +31,9 @@ open_workbook / save_workbook. inspect_workbook summarizes sheets, used ranges,
list_functions lists the worksheet functions.";
/// Resource URIs.
-pub const WORKBOOK_URI: &str = "sheetcraft://workbook";
-pub const COMMANDS_URI: &str = "sheetcraft://commands";
-pub const FUNCTIONS_URI: &str = "sheetcraft://functions";
+pub const WORKBOOK_URI: &str = "gridcraft://workbook";
+pub const COMMANDS_URI: &str = "gridcraft://commands";
+pub const FUNCTIONS_URI: &str = "gridcraft://functions";
/// An MCP server bound to one backend.
pub struct Server {
@@ -154,7 +154,7 @@ impl Server {
Ok(json!({
"protocolVersion": version,
"capabilities": {"tools": {"listChanged": false}, "resources": {"listChanged": false, "subscribe": false}, "prompts": {"listChanged": false}},
- "serverInfo": {"name": "sheetcraft", "title": "SheetCraft", "version": env!("CARGO_PKG_VERSION")},
+ "serverInfo": {"name": "gridcraft", "title": "GridCraft", "version": env!("CARGO_PKG_VERSION")},
"instructions": format!("{INSTRUCTIONS} Backend: {}.", self.backend.describe()),
}))
}
diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs
index 28d8c9a..8e37a21 100644
--- a/crates/mcp/src/tests.rs
+++ b/crates/mcp/src/tests.rs
@@ -48,7 +48,7 @@ fn err_code(v: &Value) -> i64 {
}
fn tmp(name: &str) -> std::path::PathBuf {
- let dir = std::env::temp_dir().join(format!("sheetcraft-mcp-test-{}", std::process::id()));
+ let dir = std::env::temp_dir().join(format!("gridcraft-mcp-test-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir.join(name)
}
@@ -62,7 +62,7 @@ fn initialize_and_version_negotiation() {
rpc(&mut s, 1, "initialize", json!({"protocolVersion": PROTOCOL_VERSION, "capabilities": {}, "clientInfo": {"name": "t", "version": "1"}}));
let r = &v["result"];
assert_eq!(r["protocolVersion"], PROTOCOL_VERSION);
- assert_eq!(r["serverInfo"]["name"], "sheetcraft");
+ assert_eq!(r["serverInfo"]["name"], "gridcraft");
assert!(r["capabilities"]["tools"].is_object());
assert!(r["capabilities"]["resources"].is_object());
assert!(r["instructions"].as_str().unwrap().contains("headless"));
@@ -143,7 +143,7 @@ fn malformed_input_never_panics() {
assert_eq!(err_code(&rpc(&mut s, 7, "tools/call", json!({"name": "insert_chart", "arguments": {"range": "A1", "type": "piechart"}}))), -32602);
assert_eq!(err_code(&rpc(&mut s, 8, "tools/call", json!({"name": "get_cell", "arguments": {"cell": "A1", "bogus": 1}}))), -32602);
assert_eq!(err_code(&rpc(&mut s, 9, "resources/read", json!({}))), -32602);
- assert_eq!(err_code(&rpc(&mut s, 10, "resources/read", json!({"uri": "sheetcraft://nope"}))), -32002);
+ assert_eq!(err_code(&rpc(&mut s, 10, "resources/read", json!({"uri": "gridcraft://nope"}))), -32002);
// Responses and unknown notifications are ignored.
assert_eq!(s.handle_line(r#"{"jsonrpc":"2.0","id":7,"result":{}}"#), None);
assert_eq!(s.handle_line(r#"{"jsonrpc":"2.0","method":"notifications/whatever"}"#), None);
@@ -180,14 +180,14 @@ fn resources() {
let mut s = server();
let v = rpc(&mut s, 1, "resources/list", json!({}));
let uris: Vec<&str> = v["result"]["resources"].as_array().unwrap().iter().map(|r| r["uri"].as_str().unwrap()).collect();
- assert_eq!(uris, ["sheetcraft://workbook", "sheetcraft://commands", "sheetcraft://functions"]);
+ assert_eq!(uris, ["gridcraft://workbook", "gridcraft://commands", "gridcraft://functions"]);
for uri in uris {
let v = rpc(&mut s, 2, "resources/read", json!({"uri": uri}));
let text = v["result"]["contents"][0]["text"].as_str().unwrap();
let parsed: Value = serde_json::from_str(text).unwrap();
assert!(!parsed.is_null(), "{uri}");
}
- let v = rpc(&mut s, 3, "resources/read", json!({"uri": "sheetcraft://functions"}));
+ let v = rpc(&mut s, 3, "resources/read", json!({"uri": "gridcraft://functions"}));
assert!(v["result"]["contents"][0]["text"].as_str().unwrap().contains("VLOOKUP"));
}
diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs
index 8921dc0..0fb6ecd 100644
--- a/crates/mcp/src/tools.rs
+++ b/crates/mcp/src/tools.rs
@@ -114,7 +114,7 @@ pub fn tool_definitions() -> Vec {
tool(
"execute_command",
"Execute command",
- "Run ANY SheetCraft command by id with JSON params — every ribbon/menu action is a command. Find ids and parameter \
+ "Run ANY GridCraft command by id with JSON params — every ribbon/menu action is a command. Find ids and parameter \
docs with list_commands. Examples: {\"command\":\"cell.set\",\"params\":{\"cell\":\"B2\",\"input\":\"=SUM(A1:A9)\"}}; \
{\"command\":\"home.bold\",\"params\":{\"range\":\"A1:C1\"}}; {\"command\":\"sheet.insert\"}; \
{\"command\":\"data.removeDuplicates\",\"params\":{\"range\":\"A1:C20\"}}. Most commands act on the current selection \
@@ -128,7 +128,7 @@ pub fn tool_definitions() -> Vec {
tool(
"list_commands",
"List commands",
- "List SheetCraft commands: id, label, ribbon path, shortcut, a parameter description (`params`) and whether it can run \
+ "List GridCraft commands: id, label, ribbon path, shortcut, a parameter description (`params`) and whether it can run \
right now. Use it to discover what execute_command can do (e.g. search \"sort\", \"border\", \"chart\", \"sheet\").",
obj(json!({"search": string("Case-insensitive substring matched against id, label and ribbon path")}), &[]),
true,
@@ -359,7 +359,7 @@ pub fn tool_definitions() -> Vec {
tool(
"screenshot",
"Screenshot",
- "Save a PNG screenshot of the SheetCraft window to `path`. Desktop app only (`sheetcraft-cli mcp --connect 7979`).",
+ "Save a PNG screenshot of the GridCraft window to `path`. Desktop app only (`gridcraft-cli mcp --connect 7979`).",
obj(json!({"path": string("Output PNG path")}), &["path"]),
true,
),
diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml
index 4138705..ff65691 100644
--- a/crates/model/Cargo.toml
+++ b/crates/model/Cargo.toml
@@ -1,16 +1,16 @@
[package]
-name = "sheetcraft-model"
+name = "gridcraft-model"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft document model: workbooks, sheets, cells, styles, tables, charts"
+description = "GridCraft document model: workbooks, sheets, cells, styles, tables, charts"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
-sheetcraft-formula = { workspace = true }
+gridcraft-core = { workspace = true }
+gridcraft-formula = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
diff --git a/crates/model/src/cell.rs b/crates/model/src/cell.rs
index 5a0bf2e..cd5a635 100644
--- a/crates/model/src/cell.rs
+++ b/crates/model/src/cell.rs
@@ -2,9 +2,9 @@
use std::sync::Arc;
+use gridcraft_core::{RangeRef, Value};
+use gridcraft_formula::Expr;
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{RangeRef, Value};
-use sheetcraft_formula::Expr;
use crate::style::StyleId;
@@ -24,21 +24,21 @@ impl Formula {
/// `#NAME?`.
pub fn new(text: &str) -> Formula {
let body = text.strip_prefix('=').unwrap_or(text);
- match sheetcraft_formula::parse(body) {
- Ok(e) => Formula { text: sheetcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None },
+ match gridcraft_formula::parse(body) {
+ Ok(e) => Formula { text: gridcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None },
Err(_) => Formula { text: body.to_string(), expr: None, array: None },
}
}
pub fn from_expr(e: Expr) -> Formula {
- Formula { text: sheetcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None }
+ Formula { text: gridcraft_formula::print(&e), expr: Some(Arc::new(e)), array: None }
}
/// The parsed expression, re-parsing after deserialization.
pub fn expr(&self) -> Option {
- self.expr.as_deref().cloned().or_else(|| sheetcraft_formula::parse(&self.text).ok())
+ self.expr.as_deref().cloned().or_else(|| gridcraft_formula::parse(&self.text).ok())
}
/// The parsed expression without copying it (re-parses after deserialization).
pub fn expr_arc(&self) -> Option> {
- self.expr.clone().or_else(|| sheetcraft_formula::parse(&self.text).ok().map(Arc::new))
+ self.expr.clone().or_else(|| gridcraft_formula::parse(&self.text).ok().map(Arc::new))
}
}
diff --git a/crates/model/src/features.rs b/crates/model/src/features.rs
index 3a99e8c..cd358a4 100644
--- a/crates/model/src/features.rs
+++ b/crates/model/src/features.rs
@@ -1,8 +1,8 @@
//! Sheet features beyond cells: tables, conditional formatting, data validation, comments,
//! hyperlinks, charts, images, autofilter, print settings.
+use gridcraft_core::{CellRef, RangeRef};
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{CellRef, RangeRef};
use crate::style::{Color, Style};
@@ -393,7 +393,7 @@ pub enum ShapeKind {
Line,
Arrow,
TextBox,
- /// An icon from SheetCraft's own icon set; `Shape::text` holds the icon name.
+ /// An icon from GridCraft's own icon set; `Shape::text` holds the icon name.
Icon,
/// A freehand ink stroke; `Shape::text` holds the points as `x,y;x,y;…` relative to the
/// anchor (sheet points), `Shape::line` the ink colour and `fill` unused.
diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs
index f454f8f..ba0f32c 100644
--- a/crates/model/src/lib.rs
+++ b/crates/model/src/lib.rs
@@ -1,6 +1,6 @@
-//! The SheetCraft document model: workbooks, sheets, a copy-on-write cell store, interned
+//! The GridCraft document model: workbooks, sheets, a copy-on-write cell store, interned
//! styles, defined names, tables, conditional formats, validation, comments, charts and print
-//! settings. Pure data; calculation lives in `sheetcraft-calc`.
+//! settings. Pure data; calculation lives in `gridcraft-calc`.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
diff --git a/crates/model/src/sheet.rs b/crates/model/src/sheet.rs
index cf542b7..4b4200d 100644
--- a/crates/model/src/sheet.rs
+++ b/crates/model/src/sheet.rs
@@ -2,8 +2,8 @@
use std::collections::BTreeMap;
+use gridcraft_core::{CellRef, RangeRef, Value};
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{CellRef, RangeRef, Value};
use crate::cell::Cell;
use crate::features::*;
@@ -184,10 +184,10 @@ impl Sheet {
}
/// Row containing y (points from the top), clamped.
pub fn row_at(&self, y: f64) -> u32 {
- line_at(y, self.default_row_height as f64, &self.rows, sheetcraft_core::MAX_ROWS)
+ line_at(y, self.default_row_height as f64, &self.rows, gridcraft_core::MAX_ROWS)
}
pub fn col_at(&self, x: f64) -> u32 {
- line_at(x, self.default_col_width as f64, &self.cols, sheetcraft_core::MAX_COLS)
+ line_at(x, self.default_col_width as f64, &self.cols, gridcraft_core::MAX_COLS)
}
/// The merged range containing `c`, if any.
@@ -310,7 +310,7 @@ mod tests {
for r in (0..5000).step_by(3) {
s.rows.insert(r, LineInfo { size: Some(10.0 + (r % 7) as f32), hidden: r % 11 == 0, ..Default::default() });
}
- let idx = LineIndex::new(&s.rows, s.default_row_height, sheetcraft_core::MAX_ROWS);
+ let idx = LineIndex::new(&s.rows, s.default_row_height, gridcraft_core::MAX_ROWS);
for r in [0u32, 1, 2, 3, 4, 100, 999, 4998, 5000, 6000] {
assert!((idx.start(r) - s.row_top(r)).abs() < 1e-6, "start {r}");
}
@@ -334,7 +334,7 @@ mod tests {
assert_eq!(s.row_at(59.0), 1);
assert_eq!(s.row_at(61.0), 3);
assert_eq!(s.row_at(85.0), 4);
- assert_eq!(s.col_at(1e12), sheetcraft_core::MAX_COLS - 1);
+ assert_eq!(s.col_at(1e12), gridcraft_core::MAX_COLS - 1);
assert_eq!(s.col_at(-5.0), 0);
assert_eq!(s.col_left(2), 128.0);
}
diff --git a/crates/model/src/store.rs b/crates/model/src/store.rs
index 08b866a..1652501 100644
--- a/crates/model/src/store.rs
+++ b/crates/model/src/store.rs
@@ -6,8 +6,8 @@
use std::collections::BTreeMap;
use std::sync::Arc;
+use gridcraft_core::{CellRef, RangeRef};
use serde::{Deserialize, Serialize};
-use sheetcraft_core::{CellRef, RangeRef};
use crate::cell::Cell;
@@ -134,7 +134,7 @@ impl CellStore {
};
for (c, cell) in moved {
let nr = c.row as i64 + delta;
- if (0..sheetcraft_core::MAX_ROWS as i64).contains(&nr) {
+ if (0..gridcraft_core::MAX_ROWS as i64).contains(&nr) {
self.set(CellRef::new(nr as u32, c.col), cell);
}
}
@@ -144,7 +144,7 @@ impl CellStore {
let moved: Vec<(CellRef, Cell)> = keys.into_iter().filter_map(|c| self.remove(c).map(|cell| (c, cell))).collect();
for (c, cell) in moved {
let nc = c.col as i64 + delta;
- if (0..sheetcraft_core::MAX_COLS as i64).contains(&nc) {
+ if (0..gridcraft_core::MAX_COLS as i64).contains(&nc) {
self.set(CellRef::new(c.row, nc as u32), cell);
}
}
@@ -152,22 +152,22 @@ impl CellStore {
/// Shifts cells within rows `r0..=r1` and columns `>= from` horizontally (Insert/Delete Cells
/// with shift right/left).
pub fn shift_cols_in_rows(&mut self, r0: u32, r1: u32, from: u32, delta: i64) {
- let range = RangeRef::new(CellRef::new(r0, from), CellRef::new(r1, sheetcraft_core::MAX_COLS - 1));
+ let range = RangeRef::new(CellRef::new(r0, from), CellRef::new(r1, gridcraft_core::MAX_COLS - 1));
let moved = self.take_range(range);
for (c, cell) in moved {
let nc = c.col as i64 + delta;
- if (0..sheetcraft_core::MAX_COLS as i64).contains(&nc) {
+ if (0..gridcraft_core::MAX_COLS as i64).contains(&nc) {
self.set(CellRef::new(c.row, nc as u32), cell);
}
}
}
/// Shifts cells within columns `c0..=c1` and rows `>= from` vertically.
pub fn shift_rows_in_cols(&mut self, c0: u32, c1: u32, from: u32, delta: i64) {
- let range = RangeRef::new(CellRef::new(from, c0), CellRef::new(sheetcraft_core::MAX_ROWS - 1, c1));
+ let range = RangeRef::new(CellRef::new(from, c0), CellRef::new(gridcraft_core::MAX_ROWS - 1, c1));
let moved = self.take_range(range);
for (c, cell) in moved {
let nr = c.row as i64 + delta;
- if (0..sheetcraft_core::MAX_ROWS as i64).contains(&nr) {
+ if (0..gridcraft_core::MAX_ROWS as i64).contains(&nr) {
self.set(CellRef::new(nr as u32, c.col), cell);
}
}
@@ -177,7 +177,7 @@ impl CellStore {
#[cfg(test)]
mod tests {
use super::*;
- use sheetcraft_core::Value;
+ use gridcraft_core::Value;
fn c(r: u32, col: u32) -> CellRef {
CellRef::new(r, col)
diff --git a/crates/model/src/style.rs b/crates/model/src/style.rs
index 447946c..126727e 100644
--- a/crates/model/src/style.rs
+++ b/crates/model/src/style.rs
@@ -122,7 +122,7 @@ pub struct Theme {
}
impl Default for Theme {
- /// SheetCraft's own default theme ("Craft"): original palette.
+ /// GridCraft's own default theme ("Craft"): original palette.
fn default() -> Self {
Theme {
name: "Craft".into(),
diff --git a/crates/model/src/workbook.rs b/crates/model/src/workbook.rs
index 21b2cd6..8652b20 100644
--- a/crates/model/src/workbook.rs
+++ b/crates/model/src/workbook.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
+use gridcraft_core::DateSystem;
use serde::{Deserialize, Serialize};
-use sheetcraft_core::DateSystem;
use crate::sheet::Sheet;
use crate::style::{StyleTable, Theme};
diff --git a/crates/numfmt/Cargo.toml b/crates/numfmt/Cargo.toml
index 15b1f8a..01a059d 100644
--- a/crates/numfmt/Cargo.toml
+++ b/crates/numfmt/Cargo.toml
@@ -1,12 +1,12 @@
[package]
-name = "sheetcraft-numfmt"
+name = "gridcraft-numfmt"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft number formats: Excel format codes, General display, TEXT()"
+description = "GridCraft number formats: Excel format codes, General display, TEXT()"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
+gridcraft-core = { workspace = true }
diff --git a/crates/numfmt/src/lib.rs b/crates/numfmt/src/lib.rs
index 35c49ab..dcc53d1 100644
--- a/crates/numfmt/src/lib.rs
+++ b/crates/numfmt/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft number formats: Excel format codes (`#,##0.00;[Red](#,##0.00)`, dates, fractions,
+//! GridCraft number formats: Excel format codes (`#,##0.00;[Red](#,##0.00)`, dates, fractions,
//! scientific…), the General display format and the `TEXT()` worksheet function. en-US only.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
@@ -10,7 +10,7 @@ mod render;
use std::sync::Arc;
-use sheetcraft_core::{CellError, DateSystem, Value};
+use gridcraft_core::{CellError, DateSystem, Value};
pub use builtin::{builtin_format, builtin_id};
pub use decimal::format_general_fit;
@@ -304,7 +304,7 @@ pub fn text_function(v: &Value, code: &str, sys: DateSystem) -> Result 0.0,
Value::Number(n) => n,
Value::Bool(b) => return Ok(if b { "TRUE" } else { "FALSE" }.into()),
- Value::Text(t) => match sheetcraft_core::parse::parse_number_text(&t) {
+ Value::Text(t) => match gridcraft_core::parse::parse_number_text(&t) {
Some(n) => n,
None => return Ok(fmt.format_text(&t).text),
},
diff --git a/crates/numfmt/src/render.rs b/crates/numfmt/src/render.rs
index 2249210..363dec1 100644
--- a/crates/numfmt/src/render.rs
+++ b/crates/numfmt/src/render.rs
@@ -1,7 +1,7 @@
//! Rendering a value with a parsed section.
-use sheetcraft_core::DateSystem;
-use sheetcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial};
+use gridcraft_core::DateSystem;
+use gridcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial};
use crate::decimal::{decimal_digits, fixed, fixed_from_digits, format_general_fit};
use crate::parse::{DatePart, Section, Tok};
diff --git a/crates/pdf/Cargo.toml b/crates/pdf/Cargo.toml
index 69826cc..6cb0d2e 100644
--- a/crates/pdf/Cargo.toml
+++ b/crates/pdf/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "sheetcraft-pdf"
+name = "gridcraft-pdf"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft's tiny self-contained PDF 1.7 writer (standard 14 fonts, vector drawing, RGBA images)"
+description = "GridCraft's tiny self-contained PDF 1.7 writer (standard 14 fonts, vector drawing, RGBA images)"
[lints]
workspace = true
diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs
index d02db19..51c2df1 100644
--- a/crates/pdf/src/lib.rs
+++ b/crates/pdf/src/lib.rs
@@ -1,4 +1,4 @@
-//! A tiny, self-contained PDF 1.7 writer for SheetCraft's print and PDF export.
+//! A tiny, self-contained PDF 1.7 writer for GridCraft's print and PDF export.
//!
//! - The 14 standard fonts (no embedding) with WinAnsiEncoding, plus approximate width tables
//! ([`text_width`]) so callers can measure, align and wrap text.
@@ -641,8 +641,8 @@ impl PdfDoc {
objects.push((1, b"<< /Type /Catalog /Pages 2 0 R >>".to_vec()));
let kids_s: Vec = kids.iter().map(|k| format!("{k} 0 R")).collect();
objects.push((2, format!("<< /Type /Pages /Kids [{}] /Count {} >>", kids_s.join(" "), kids.len()).into_bytes()));
- let creator = if self.info.creator.is_empty() { "SheetCraft" } else { &self.info.creator };
- let mut info = format!("<< /Producer {} /Creator {}", pdf_text_string("SheetCraft PDF"), pdf_text_string(creator));
+ let creator = if self.info.creator.is_empty() { "GridCraft" } else { &self.info.creator };
+ let mut info = format!("<< /Producer {} /Creator {}", pdf_text_string("GridCraft PDF"), pdf_text_string(creator));
if !self.info.title.is_empty() {
let _ = write!(info, " /Title {}", pdf_text_string(&self.info.title));
}
diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml
index 51dd19d..2735635 100644
--- a/crates/ui-egui/Cargo.toml
+++ b/crates/ui-egui/Cargo.toml
@@ -1,16 +1,16 @@
[package]
-name = "sheetcraft-ui-egui"
+name = "gridcraft-ui-egui"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft egui frontend (Excel-style UI); swappable — nothing below depends on it"
+description = "GridCraft egui frontend (Excel-style UI); swappable — nothing below depends on it"
[lints]
workspace = true
[dependencies]
-sheetcraft-engine = { workspace = true }
-sheetcraft-chart = { workspace = true }
+gridcraft-engine = { workspace = true }
+gridcraft-chart = { workspace = true }
egui = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/crates/ui-egui/examples/snapshot.rs b/crates/ui-egui/examples/snapshot.rs
index 65a4a84..4d31bd6 100644
--- a/crates/ui-egui/examples/snapshot.rs
+++ b/crates/ui-egui/examples/snapshot.rs
@@ -1,11 +1,11 @@
-//! Renders the whole SheetCraft window offscreen (wgpu, no window) to a PNG.
+//! Renders the whole GridCraft window offscreen (wgpu, no window) to a PNG.
//!
-//! `cargo run --release -p sheetcraft-ui-egui --example snapshot -- [--sample sales] [--in file.xlsx]
+//! `cargo run --release -p gridcraft-ui-egui --example snapshot -- [--sample sales] [--in file.xlsx]
//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] out.png`
+use gridcraft_engine::Session;
+use gridcraft_ui_egui::SheetApp;
use serde_json::json;
-use sheetcraft_engine::Session;
-use sheetcraft_ui_egui::SheetApp;
fn main() {
let args: Vec = std::env::args().skip(1).collect();
diff --git a/crates/ui-egui/src/chartview.rs b/crates/ui-egui/src/chartview.rs
index 81d20ca..082be11 100644
--- a/crates/ui-egui/src/chartview.rs
+++ b/crates/ui-egui/src/chartview.rs
@@ -1,4 +1,4 @@
-//! Floating objects on the sheet: charts (rendered by `sheetcraft-chart` into primitives that
+//! Floating objects on the sheet: charts (rendered by `gridcraft-chart` into primitives that
//! we paint with egui), pictures and shapes.
use std::collections::HashMap;
@@ -6,8 +6,8 @@ use std::sync::Mutex;
use egui::epaint::{PathShape, PathStroke};
use egui::{Align2, Color32, FontId, Painter, Pos2, Rect, Stroke, StrokeKind, pos2, vec2};
-use sheetcraft_chart::{HAlign, Prim, VAlign};
-use sheetcraft_engine::model::{Anchor, ShapeKind, Sheet, Workbook};
+use gridcraft_chart::{HAlign, Prim, VAlign};
+use gridcraft_engine::model::{Anchor, ShapeKind, Sheet, Workbook};
use crate::SheetApp;
use crate::grid::Geo;
@@ -22,7 +22,7 @@ struct EguiMeasure<'a> {
z: f32,
}
-impl sheetcraft_chart::Measure for EguiMeasure<'_> {
+impl gridcraft_chart::Measure for EguiMeasure<'_> {
fn text_width(&self, text: &str, size: f32, bold: bool) -> f32 {
let font = if bold { theme::ui_bold(size * self.z) } else { theme::ui_font(size * self.z) };
self.painter.layout_no_wrap(text.to_string(), font, Color32::BLACK).size().x / self.z.max(0.01)
@@ -218,9 +218,9 @@ pub fn paint_objects(app: &SheetApp, p: &Painter, geo: &Geo, wb: &Workbook, si:
p.add(PathShape::convex_polygon(vec![pos2(r.center().x, r.top()), r.right_bottom(), r.left_bottom()], fill, Stroke::new(1.0, line)));
}
ShapeKind::Ink => {
- let w = sheetcraft_engine::cmd::draw::ink_width(sp) * geo.z;
+ let w = gridcraft_engine::cmd::draw::ink_width(sp) * geo.z;
let pts: Vec =
- sheetcraft_engine::cmd::draw::ink_points(sp).iter().map(|q| pos2(r.left() + q[0] * geo.z, r.top() + q[1] * geo.z)).collect();
+ gridcraft_engine::cmd::draw::ink_points(sp).iter().map(|q| pos2(r.left() + q[0] * geo.z, r.top() + q[1] * geo.z)).collect();
if pts.len() >= 2 {
let col = if w > 8.0 { line.gamma_multiply(0.45) } else { line };
p.add(PathShape::line(pts, PathStroke::new(w, col)));
@@ -288,15 +288,15 @@ pub fn paint_objects(app: &SheetApp, p: &Painter, geo: &Geo, wb: &Workbook, si:
// Soft shadow + white card.
p.rect_filled(r.translate(vec2(0.0, 1.5)), 2.0, Color32::from_black_alpha(18));
p.rect_filled(r, 0.0, Color32::WHITE);
- let data = sheetcraft_chart::resolve(wb, si, ch);
+ let data = gridcraft_chart::resolve(wb, si, ch);
let m = EguiMeasure { painter: p, z: geo.z };
- let prims = sheetcraft_chart::render(ch, &data, ch.anchor.width, ch.anchor.height, &m);
+ let prims = gridcraft_chart::render(ch, &data, ch.anchor.width, ch.anchor.height, &m);
paint_prims(&p.with_clip_rect(r.intersect(p.clip_rect())), r.min, &prims, geo.z);
if app.selected_chart == Some(ch.id) {
selection_frame(p, r);
// Highlight the source data like Excel does.
for s in &ch.series {
- if let Ok(sheetcraft_engine::formula::Expr::Ref(rf)) = sheetcraft_engine::formula::parse(&s.values)
+ if let Ok(gridcraft_engine::formula::Expr::Ref(rf)) = gridcraft_engine::formula::parse(&s.values)
&& rf.sheet_name().is_none_or(|n| n.eq_ignore_ascii_case(&sh.name))
{
let rr = geo.range_rect(sh, rf.range());
diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs
index 159126b..a80448a 100644
--- a/crates/ui-egui/src/control.rs
+++ b/crates/ui-egui/src/control.rs
@@ -18,8 +18,8 @@
use std::sync::mpsc::Sender;
+use gridcraft_engine::core::CellRef;
use serde_json::{Value as Json, json};
-use sheetcraft_engine::core::CellRef;
use crate::SheetApp;
@@ -330,7 +330,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
ok(Json::Null)
}
// Anything else that names a command runs it.
- other if sheetcraft_engine::find_command(other).is_some() => wrap(app.run(other, p.clone())),
+ other if gridcraft_engine::find_command(other).is_some() => wrap(app.run(other, p.clone())),
other => err(format!("unknown method `{other}`")),
};
ctx.request_repaint();
@@ -403,7 +403,7 @@ pub fn collect_screenshots(app: &mut SheetApp, ctx: &egui::Context) {
if now < *deadline {
return true;
}
- let _ = reply.send(json!({"ok": false, "error": "no frame was presented (screen locked or window hidden); use `sheetcraft-cli snapshot`"}));
+ let _ = reply.send(json!({"ok": false, "error": "no frame was presented (screen locked or window hidden); use `gridcraft-cli snapshot`"}));
false
});
}
@@ -425,6 +425,6 @@ pub fn save_image(image: &egui::ColorImage, path: Option<&str>) -> Json {
Ok(()) => json!({"ok": true, "result": {"path": p, "width": w, "height": h}}),
Err(e) => json!({"ok": false, "error": e.to_string()}),
},
- None => json!({"ok": true, "result": {"base64": sheetcraft_engine::io::base64_encode(&png), "width": w, "height": h}}),
+ None => json!({"ok": true, "result": {"base64": gridcraft_engine::io::base64_encode(&png), "width": w, "height": h}}),
}
}
diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs
index 3c7cc8a..8979fc0 100644
--- a/crates/ui-egui/src/dialogs.rs
+++ b/crates/ui-egui/src/dialogs.rs
@@ -106,7 +106,7 @@ impl Dialog {
"Column Width",
"home.columnWidth",
vec![Number { key: "chars", label: "Column width (characters):" }],
- json!({"chars": app.session.active().and_then(|d| d.wb.active().map(|s| sheetcraft_engine::cmd::format::points_to_chars(s.col_width(d.selection.active.col) as f64))).unwrap_or(8.43)}),
+ json!({"chars": app.session.active().and_then(|d| d.wb.active().map(|s| gridcraft_engine::cmd::format::points_to_chars(s.col_width(d.selection.active.col) as f64))).unwrap_or(8.43)}),
),
"defaultWidth" => Dialog::form(
"defaultWidth",
@@ -357,7 +357,7 @@ impl Dialog {
Text { key: "comment", label: "Comment:" },
Text { key: "refersTo", label: "Refers to:" },
],
- json!({"scope": "Workbook", "refersTo": app.session.active().and_then(|d| d.wb.active().map(|s| format!("={}!{}", sheetcraft_engine::formula::quote_sheet(&s.name), abs(&d.selection.current()))))}),
+ json!({"scope": "Workbook", "refersTo": app.session.active().and_then(|d| d.wb.active().map(|s| format!("={}!{}", gridcraft_engine::formula::quote_sheet(&s.name), abs(&d.selection.current()))))}),
),
"dataValidation" => Dialog::form(
"dataValidation",
@@ -544,7 +544,7 @@ impl Dialog {
"pickList" => Dialog::custom("pickList", "Pick From List", json!({})),
"saveCopy" => Dialog::custom("saveCopy", "Save a Copy", json!({"format": p("format")})),
"saveChanges" => Dialog::custom("saveChanges", "Save Changes?", json!({"title": p("title")})),
- "start" => Dialog::custom("start", "SheetCraft", json!({})),
+ "start" => Dialog::custom("start", "GridCraft", json!({})),
"comments" => {
app.grid.pane = Some("comments".into());
return None;
@@ -566,8 +566,8 @@ impl Dialog {
}
}
-fn abs(r: &sheetcraft_engine::core::RangeRef) -> String {
- let a = |c: sheetcraft_engine::core::CellRef| format!("${}${}", sheetcraft_engine::core::col_to_letters(c.col), c.row + 1);
+fn abs(r: &gridcraft_engine::core::RangeRef) -> String {
+ let a = |c: gridcraft_engine::core::CellRef| format!("${}${}", gridcraft_engine::core::col_to_letters(c.col), c.row + 1);
if r.is_single() { a(r.start) } else { format!("{}:{}", a(r.start), a(r.end)) }
}
@@ -653,11 +653,11 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
"goalSeek" => goal_seek(app, ui, &mut d, &mut confirm),
"spelling" => spelling(app, ui, &mut d, &mut open),
"agents" => {
- ui.label("SheetCraft is fully drivable by agents. Every menu item, button and gesture is a command:");
+ ui.label("GridCraft is fully drivable by agents. Every menu item, button and gesture is a command:");
ui.add_space(4.0);
- ui.monospace("sheetcraft --control 7979 # JSON-lines control channel\nsheetcraft-cli mcp --connect 7979 # MCP bridged to this window\nsheetcraft-cli mcp # headless MCP server");
+ ui.monospace("gridcraft --control 7979 # JSON-lines control channel\ngridcraft-cli mcp --connect 7979 # MCP bridged to this window\ngridcraft-cli mcp # headless MCP server");
ui.add_space(4.0);
- ui.label(format!("{} engine commands are available. See docs/mcp.md and docs/control-protocol.md.", sheetcraft_engine::command_specs().len()));
+ ui.label(format!("{} engine commands are available. See docs/mcp.md and docs/control-protocol.md.", gridcraft_engine::command_specs().len()));
}
"journal" => {
egui::ScrollArea::vertical().max_height(360.0).show(ui, |ui| {
@@ -701,9 +701,9 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
let a = doc.selection.active;
let mut seen = std::collections::BTreeSet::new();
let mut r = a.row;
- while r > 0 && !sh.value(sheetcraft_engine::core::CellRef::new(r - 1, a.col)).is_empty() {
+ while r > 0 && !sh.value(gridcraft_engine::core::CellRef::new(r - 1, a.col)).is_empty() {
r -= 1;
- seen.insert(sh.value(sheetcraft_engine::core::CellRef::new(r, a.col)).display());
+ seen.insert(sh.value(gridcraft_engine::core::CellRef::new(r, a.col)).display());
}
Some(seen.into_iter().collect())
})
@@ -760,7 +760,7 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
app.run_or_alert("file.new", json!({}));
open = false;
}
- for (n, t) in sheetcraft_engine::sample::SAMPLES {
+ for (n, t) in gridcraft_engine::sample::SAMPLES {
if ui.button(*t).clicked() {
app.run_or_alert("file.new", json!({"sample": n}));
open = false;
@@ -881,7 +881,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
}
});
ui.separator();
- let mut st: sheetcraft_engine::model::Style = d.values.get("style").and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default();
+ let mut st: gridcraft_engine::model::Style = d.values.get("style").and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default();
let sample = app.session.active().and_then(|doc| doc.wb.active().map(|s| s.value(doc.selection.active))).unwrap_or_default();
match d.tab.as_str() {
"Number" => {
@@ -891,13 +891,13 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
for name in
["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"]
{
- let c = sheetcraft_engine::cmd::format::format_code_for(name).to_string();
+ let c = gridcraft_engine::cmd::format::format_code_for(name).to_string();
if cols[0].selectable_label(code == c, name).clicked() {
code = c;
}
}
cols[1].label(egui::RichText::new("Sample").strong());
- let preview = app.session.active().map(|doc| sheetcraft_engine::display::format(&sample, &code, &doc.wb).text).unwrap_or_default();
+ let preview = app.session.active().map(|doc| gridcraft_engine::display::format(&sample, &code, &doc.wb).text).unwrap_or_default();
cols[1].label(egui::RichText::new(preview).font(theme::ui_font(15.0)));
cols[1].add_space(8.0);
cols[1].label("Type (custom format code):");
@@ -925,10 +925,10 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
}
}
});
- st.num_fmt = sheetcraft_engine::model::NumFmt::new(&code);
+ st.num_fmt = gridcraft_engine::model::NumFmt::new(&code);
}
"Alignment" => {
- use sheetcraft_engine::model::{HAlign, VAlign};
+ use gridcraft_engine::model::{HAlign, VAlign};
ui.label(egui::RichText::new("Horizontal").strong());
ui.horizontal_wrapped(|ui| {
for (h, n) in [
@@ -977,7 +977,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
ui.checkbox(&mut st.font.italic, "Italic");
ui.checkbox(&mut st.font.strike, "Strikethrough");
});
- use sheetcraft_engine::model::{Underline, VertAlign};
+ use gridcraft_engine::model::{Underline, VertAlign};
ui.horizontal(|ui| {
for (u, n) in [
(Underline::None, "None"),
@@ -997,16 +997,13 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
ui.label("Color:");
let colors = app.session.active().map(|doc| doc.wb.theme.colors).unwrap_or_default();
if let Some(c) = crate::widgets::color_palette(ui, &colors, "Automatic") {
- st.font.color = if c == "none" {
- sheetcraft_engine::model::Color::Auto
- } else {
- sheetcraft_engine::model::Color::from_hex(&c).unwrap_or_default()
- };
+ st.font.color =
+ if c == "none" { gridcraft_engine::model::Color::Auto } else { gridcraft_engine::model::Color::from_hex(&c).unwrap_or_default() };
}
}
"Border" => {
- use sheetcraft_engine::model::{BorderLine, BorderStyle};
- let line = BorderLine { style: BorderStyle::Thin, color: sheetcraft_engine::model::Color::Auto };
+ use gridcraft_engine::model::{BorderLine, BorderStyle};
+ let line = BorderLine { style: BorderStyle::Thin, color: gridcraft_engine::model::Color::Auto };
ui.horizontal(|ui| {
if ui.button("None").clicked() {
st.borders = Default::default();
@@ -1051,7 +1048,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
st.fill = if c == "none" {
Default::default()
} else {
- sheetcraft_engine::model::Fill::solid(sheetcraft_engine::model::Color::from_hex(&c).unwrap_or_default())
+ gridcraft_engine::model::Fill::solid(gridcraft_engine::model::Color::from_hex(&c).unwrap_or_default())
};
}
}
@@ -1085,7 +1082,7 @@ fn insert_function(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confir
let r = ui.add(egui::TextEdit::singleline(&mut d.search).desired_width(300.0).hint_text("e.g. lookup, average, date"));
r.request_focus();
});
- let list = sheetcraft_engine::cmd::formulas::function_list();
+ let list = gridcraft_engine::cmd::formulas::function_list();
let q = d.search.to_ascii_lowercase();
let hits: Vec<&Json> = list
.iter()
diff --git a/crates/ui-egui/src/editor.rs b/crates/ui-egui/src/editor.rs
index f698136..aa4c64f 100644
--- a/crates/ui-egui/src/editor.rs
+++ b/crates/ui-egui/src/editor.rs
@@ -2,7 +2,7 @@
//! autocomplete and argument hints.
use egui::Color32;
-use sheetcraft_engine::core::{CellRef, RangeRef};
+use gridcraft_engine::core::{CellRef, RangeRef};
/// Colours for references in a formula being edited (our own palette).
pub const REF_COLORS: [Color32; 8] = [
@@ -194,12 +194,12 @@ impl EditState {
pub fn formula_refs(text: &str) -> Vec<(usize, usize, RangeRef, Option, usize)> {
let body = text.strip_prefix('=').or_else(|| text.strip_prefix('+')).unwrap_or(text);
let offset = text.len() - body.len();
- let Ok(toks) = sheetcraft_engine::formula::lexer::tokenize(body) else { return vec![] };
+ let Ok(toks) = gridcraft_engine::formula::lexer::tokenize(body) else { return vec![] };
let mut out: Vec<(usize, usize, RangeRef, Option, usize)> = Vec::new();
let mut seen: Vec = Vec::new();
let mut i = 0;
while i < toks.len() {
- use sheetcraft_engine::formula::lexer::Tok;
+ use gridcraft_engine::formula::lexer::Tok;
let (sheet, start, j) = match &toks[i].tok {
Tok::Sheet(a, _) => (Some(a.clone()), toks[i].start, i + 1),
_ => (None, toks[i].start, i),
@@ -236,7 +236,7 @@ pub fn formula_refs(text: &str) -> Vec<(usize, usize, RangeRef, Option,
/// Excel-style AutoComplete: the one distinct text entry in the same column (contiguous data
/// above and below) that starts with `typed` (case-insensitive). `None` when ambiguous.
-pub fn column_completion(sheet: &sheetcraft_engine::model::Sheet, at: CellRef, typed: &str) -> Option {
+pub fn column_completion(sheet: &gridcraft_engine::model::Sheet, at: CellRef, typed: &str) -> Option {
if typed.is_empty() || typed.starts_with('=') || typed.chars().all(|c| c.is_ascii_digit() || c == '.' || c == '-') {
return None;
}
@@ -245,8 +245,8 @@ pub fn column_completion(sheet: &sheetcraft_engine::model::Sheet, at: CellRef, t
let mut ambiguous = false;
let mut scan = |row: u32| -> bool {
match sheet.value(CellRef::new(row, at.col)) {
- sheetcraft_engine::core::Value::Empty => false,
- sheetcraft_engine::core::Value::Text(t) => {
+ gridcraft_engine::core::Value::Empty => false,
+ gridcraft_engine::core::Value::Text(t) => {
if t.to_lowercase().starts_with(&lower) && t.len() > typed.len() {
match &found {
Some(f) if !f.eq_ignore_ascii_case(&t) => ambiguous = true,
diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs
index 66cb684..753585f 100644
--- a/crates/ui-egui/src/formula_bar.rs
+++ b/crates/ui-egui/src/formula_bar.rs
@@ -38,7 +38,7 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) {
ui.painter().rect_filled(field, 4.0, t.input_bg);
ui.painter().rect_stroke(field, 4.0, Stroke::new(1.0, t.input_border), StrokeKind::Inside);
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(field.shrink2(vec2(6.0, 3.0))));
- let id = egui::Id::new("sheetcraft.formula_bar");
+ let id = egui::Id::new("gridcraft.formula_bar");
let editing_here = app.editor.as_ref().is_some_and(|e| e.from_formula_bar);
if app.editor.is_some() {
editor_widget(app, &mut child, id, theme::ui_font(13.0), expanded, field.width() - 12.0);
@@ -84,7 +84,7 @@ fn active_input(app: &SheetApp) -> String {
}
fn name_box(app: &mut SheetApp, ui: &mut egui::Ui, t: &Tokens) {
- let id = egui::Id::new("sheetcraft.name_box");
+ let id = egui::Id::new("gridcraft.name_box");
let shown = app.name_box_text();
let mut text = app.name_box.clone().unwrap_or(shown.clone());
let rect = Rect::from_min_size(ui.cursor().min, vec2(118.0, 24.0));
@@ -103,7 +103,7 @@ fn name_box(app: &mut SheetApp, ui: &mut egui::Ui, t: &Tokens) {
let target = text.trim().to_string();
if !target.is_empty() && target != shown {
// A new name for the selection, or a place to go.
- let is_ref = sheetcraft_engine::core::RangeRef::parse(target.split('!').next_back().unwrap_or("")).is_some();
+ let is_ref = gridcraft_engine::core::RangeRef::parse(target.split('!').next_back().unwrap_or("")).is_some();
let known = app.session.active().is_some_and(|d| d.wb.name(&target, d.wb.active_sheet).is_some() || d.wb.table(&target).is_some());
let r = if is_ref || known {
app.run("edit.goTo", json!({"reference": target}))
@@ -111,7 +111,7 @@ fn name_box(app: &mut SheetApp, ui: &mut egui::Ui, t: &Tokens) {
app.run("formulas.defineName", json!({"name": target}))
};
if let Err(e) = r {
- app.message = Some(("SheetCraft".into(), crate::clean_error(&e)));
+ app.message = Some(("GridCraft".into(), crate::clean_error(&e)));
}
app.grid.ensure_visible = true;
}
@@ -141,7 +141,7 @@ fn name_box(app: &mut SheetApp, ui: &mut egui::Ui, t: &Tokens) {
/// The text editor used in the cell and in the formula bar (they share `app.editor`).
pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font: FontId, multiline: bool, width: f32) {
let Some(mut ed) = app.editor.take() else { return };
- let mine = ed.from_formula_bar == (id == egui::Id::new("sheetcraft.formula_bar"));
+ let mine = ed.from_formula_bar == (id == egui::Id::new("gridcraft.formula_bar"));
// Keys the editor handles itself (before the TextEdit sees them), only where it has focus.
let focused = ui.ctx().memory(|m| m.focused());
let has_focus = focused == Some(id) || (mine && (ed.request_focus || focused.is_none()));
@@ -234,7 +234,7 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
// The text widget.
let refs = formula_refs(&ed.text);
let text_color = Tokens::get(ui.ctx()).text;
- let cell_text_color = if id == egui::Id::new("sheetcraft.cell_editor") { Color32::BLACK } else { text_color };
+ let cell_text_color = if id == egui::Id::new("gridcraft.cell_editor") { Color32::BLACK } else { text_color };
let font2 = font.clone();
let mut layouter = move |ui: &egui::Ui, buf: &dyn egui::TextBuffer, wrap_width: f32| {
let s = buf.as_str();
@@ -277,9 +277,9 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
output.state.store(ui.ctx(), id);
} else if resp.clicked() || resp.gained_focus() {
// Clicking into the other editor moves editing there.
- ed.from_formula_bar = id == egui::Id::new("sheetcraft.formula_bar");
+ ed.from_formula_bar = id == egui::Id::new("gridcraft.formula_bar");
ed.enter_mode = false;
- app.session.mode = sheetcraft_engine::Mode::Edit;
+ app.session.mode = gridcraft_engine::Mode::Edit;
}
if !synced && let Some(r) = output.cursor_range {
ed.caret = r.primary.index.into();
@@ -296,11 +296,11 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
let names: Vec = function_names();
ed.update_autocomplete(&names);
app.session.mode = if ed.can_point() {
- sheetcraft_engine::Mode::Point
+ gridcraft_engine::Mode::Point
} else if ed.enter_mode {
- sheetcraft_engine::Mode::Enter
+ gridcraft_engine::Mode::Enter
} else {
- sheetcraft_engine::Mode::Edit
+ gridcraft_engine::Mode::Edit
};
}
// Column AutoComplete: the rest of the suggested entry, shown selected after the caret.
@@ -416,13 +416,13 @@ fn cycle_anchor(ed: &mut crate::editor::EditState) {
fn function_names() -> Vec {
static NAMES: std::sync::OnceLock> = std::sync::OnceLock::new();
NAMES
- .get_or_init(|| sheetcraft_engine::cmd::formulas::function_list().iter().filter_map(|f| f["name"].as_str().map(str::to_string)).collect())
+ .get_or_init(|| gridcraft_engine::cmd::formulas::function_list().iter().filter_map(|f| f["name"].as_str().map(str::to_string)).collect())
.clone()
}
fn function_info(name: &str) -> Option {
static LIST: std::sync::OnceLock> = std::sync::OnceLock::new();
- LIST.get_or_init(sheetcraft_engine::cmd::formulas::function_list)
+ LIST.get_or_init(gridcraft_engine::cmd::formulas::function_list)
.iter()
.find(|f| f["name"].as_str().is_some_and(|n| n.eq_ignore_ascii_case(name)))
.cloned()
diff --git a/crates/ui-egui/src/grid.rs b/crates/ui-egui/src/grid.rs
index 3688c5f..4b326cc 100644
--- a/crates/ui-egui/src/grid.rs
+++ b/crates/ui-egui/src/grid.rs
@@ -6,10 +6,10 @@
use std::sync::Arc;
use egui::{Align2, Color32, CursorIcon, FontId, Painter, Pos2, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
+use gridcraft_engine::cf::{CfCache, CfLook};
+use gridcraft_engine::core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value, col_to_letters};
+use gridcraft_engine::model::{BorderStyle, Color, HAlign, LineIndex, PatternType, Sheet, Style, Underline, VAlign, Workbook};
use serde_json::json;
-use sheetcraft_engine::cf::{CfCache, CfLook};
-use sheetcraft_engine::core::{CellRef, MAX_COLS, MAX_ROWS, RangeRef, Value, col_to_letters};
-use sheetcraft_engine::model::{BorderStyle, Color, HAlign, LineIndex, PatternType, Sheet, Style, Underline, VAlign, Workbook};
use crate::SheetApp;
use crate::editor::{EditState, REF_COLORS};
@@ -199,8 +199,8 @@ fn col32(c: Color, wb: &Workbook, default: Color32) -> Color32 {
c.resolve(&wb.theme).map(theme::color32).unwrap_or(default)
}
-fn numfmt_color(c: sheetcraft_engine::numfmt::FormatColor) -> Color32 {
- use sheetcraft_engine::numfmt::FormatColor as F;
+fn numfmt_color(c: gridcraft_engine::numfmt::FormatColor) -> Color32 {
+ use gridcraft_engine::numfmt::FormatColor as F;
match c {
F::Black => Color32::BLACK,
F::Blue => Color32::from_rgb(0, 0, 255),
@@ -217,7 +217,7 @@ fn numfmt_color(c: sheetcraft_engine::numfmt::FormatColor) -> Color32 {
fn look(wb: &Workbook, si: usize, sh: &Sheet, c: CellRef, cf: &mut CfCache) -> (Look, Option) {
let mut style = wb.styles.get(sh.style_id(c)).clone();
if let Some(t) = sh.table_at(c) {
- style = sheetcraft_engine::tables::table_cell_style(wb, si, t, c);
+ style = gridcraft_engine::tables::table_cell_style(wb, si, t, c);
}
let cfl = cf.look(wb, si, c);
if let Some(l) = &cfl
@@ -301,12 +301,12 @@ fn display_text(
&& let Value::Number(n) = v
{
let max_chars = ((width_px - 4.0) / char_w).floor().max(1.0) as usize;
- return match sheetcraft_engine::numfmt::format_general_fit(*n, max_chars.min(11)) {
+ return match gridcraft_engine::numfmt::format_general_fit(*n, max_chars.min(11)) {
Some(t) => (t, None, true, None),
None => ("#".repeat(max_chars.max(1)), None, true, None),
};
}
- let f = sheetcraft_engine::display::format(v, code, wb);
+ let f = gridcraft_engine::display::format(v, code, wb);
(f.text, f.color.map(numfmt_color), f.numeric, f.fill)
}
@@ -729,12 +729,12 @@ fn paint_quadrant(
continue;
}
let rect = geo.cell_rect(sh, sp.cell).shrink(3.0);
- let vals: Vec> = match sheetcraft_engine::calc::evaluate(wb, si, sp.cell, &sp.source) {
+ let vals: Vec > = match gridcraft_engine::calc::evaluate(wb, si, sp.cell, &sp.source) {
Value::Array(a) => a.data.iter().map(Value::as_f64).collect(),
v => vec![v.as_f64()],
};
let col = sp.color.resolve(&wb.theme).unwrap_or([0x2B, 0x7C, 0xD3]);
- let prims = sheetcraft_chart::render_sparkline(sp.kind, &vals, [col[0], col[1], col[2], 255], sp.markers, rect.width(), rect.height());
+ let prims = gridcraft_chart::render_sparkline(sp.kind, &vals, [col[0], col[1], col[2], 255], sp.markers, rect.width(), rect.height());
crate::chartview::paint_prims(p, rect.min, &prims, 1.0);
}
// Filter buttons.
@@ -798,7 +798,7 @@ fn paint_border(p: &Painter, pts: [Pos2; 2], style: BorderStyle, col: Color32) {
}
fn paint_cf_icon(p: &Painter, r: Rect, set: &str, idx: usize) {
- let n = sheetcraft_engine::cf::icon_count(set);
+ let n = gridcraft_engine::cf::icon_count(set);
let palette3 = [Color32::from_rgb(0xD1, 0x3B, 0x2F), Color32::from_rgb(0xF2, 0xB8, 0x2E), Color32::from_rgb(0x2E, 0x9E, 0x4F)];
let col = if n <= 3 {
palette3.get(idx).copied().unwrap_or(Color32::GRAY)
@@ -835,7 +835,7 @@ fn paint_cf_icon(p: &Painter, r: Rect, set: &str, idx: usize) {
}
}
-fn paint_selection(p: &Painter, geo: &Geo, sh: &Sheet, sel: &sheetcraft_engine::Selection, t: &Tokens, app: &SheetApp) {
+fn paint_selection(p: &Painter, geo: &Geo, sh: &Sheet, sel: &gridcraft_engine::Selection, t: &Tokens, app: &SheetApp) {
// Fill all areas except the active cell.
for r in &sel.ranges {
if r.is_single() && sel.ranges.len() == 1 {
@@ -907,7 +907,7 @@ fn paint_selection(p: &Painter, geo: &Geo, sh: &Sheet, sel: &sheetcraft_engine::
}
}
-fn paint_headers(p: &Painter, geo: &Geo, sh: &Sheet, sel: &sheetcraft_engine::Selection, t: &Tokens, quads: &[((u32, u32), (u32, u32), Rect)]) {
+fn paint_headers(p: &Painter, geo: &Geo, sh: &Sheet, sel: &gridcraft_engine::Selection, t: &Tokens, quads: &[((u32, u32), (u32, u32), Rect)]) {
let font = theme::ui_font((11.5 * (geo.z / DISPLAY_SCALE).max(0.75)).min(16.0));
let r = geo.rect;
let top = Rect::from_min_max(pos2(geo.cells.left(), r.top()), pos2(r.right(), geo.cells.top()));
@@ -1051,7 +1051,7 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
if app.session.draw_tool == "eraser" && (resp.clicked() || resp.dragged()) {
if let Some(p) = pos
&& let Some(("shape", id, _)) = crate::chartview::hit(app, geo, sh, p)
- && sh.shapes.iter().any(|s| s.id == id && s.kind == sheetcraft_engine::model::ShapeKind::Ink)
+ && sh.shapes.iter().any(|s| s.id == id && s.kind == gridcraft_engine::model::ShapeKind::Ink)
{
let _ = app.run("object.delete", json!({"kind": "shape", "id": id}));
}
@@ -1132,7 +1132,7 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
{
let sheet_name = (ed.sheet != wb.active_sheet).then(|| sh.name.clone());
let r = match sheet_name {
- Some(n) => format!("{}!{}", sheetcraft_engine::formula::quote_sheet(&n), c.a1()),
+ Some(n) => format!("{}!{}", gridcraft_engine::formula::quote_sheet(&n), c.a1()),
None => c.a1(),
};
ed.insert_ref(&r);
@@ -1257,10 +1257,8 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
}
Drag::ResizeCol { col, start, orig } => {
let w = (orig + (p.x - start) / geo.z).max(0.0);
- app.toast = Some((
- format!("Width: {:.2} ({} pixels)", sheetcraft_engine::cmd::format::points_to_chars(w as f64), w.round()),
- crate::now_ms(),
- ));
+ app.toast =
+ Some((format!("Width: {:.2} ({} pixels)", gridcraft_engine::cmd::format::points_to_chars(w as f64), w.round()), crate::now_ms()));
let cols = if sel.ranges.iter().any(|r| r.is_full_cols() && col >= r.start.col && col <= r.end.col) {
sel.current().a1()
} else {
@@ -1320,7 +1318,7 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: &
if t != src {
let mode = if mods.command { "copy" } else { "series" };
if let Err(e) = app.run("edit.autoFill", json!({"source": src.a1(), "target": t.a1(), "mode": mode})) {
- app.message = Some(("SheetCraft".into(), crate::clean_error(&e)));
+ app.message = Some(("GridCraft".into(), crate::clean_error(&e)));
}
}
}
@@ -1475,7 +1473,7 @@ fn is_filter_button(sh: &Sheet, geo: &Geo, p: Pos2) -> bool {
header && filter_button_rect(geo, sh, c).contains(p)
}
-fn object_anchor(sh: &Sheet, kind: &str, id: u32) -> Option {
+fn object_anchor(sh: &Sheet, kind: &str, id: u32) -> Option {
match kind {
"chart" => sh.charts.iter().find(|c| c.id == id).map(|c| c.anchor),
"image" => sh.images.iter().find(|c| c.id == id).map(|c| c.anchor),
@@ -1505,7 +1503,7 @@ fn select_ranges(app: &mut SheetApp, r: RangeRef, add: bool, active: CellRef) {
/// Grid keyboard handling when no text field has focus.
fn keyboard(app: &mut SheetApp, ctx: &egui::Context, resp: &egui::Response, geo: &Geo, sh: &Sheet) {
// Focus left on an editor that no longer exists goes back to the grid.
- let stale = [egui::Id::new("sheetcraft.cell_editor"), egui::Id::new("sheetcraft.formula_bar")];
+ let stale = [egui::Id::new("gridcraft.cell_editor"), egui::Id::new("gridcraft.formula_bar")];
if app.editor.is_none() && ctx.memory(|m| m.focused()).is_some_and(|f| stale.contains(&f)) {
resp.request_focus();
}
@@ -1640,7 +1638,7 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet,
};
ui.painter().rect_filled(erect, 0.0, fill);
ui.painter().rect_stroke(erect.expand(1.0), 0.0, Stroke::new(2.0, Tokens::get(ui.ctx()).sel_border), StrokeKind::Middle);
- let id = egui::Id::new("sheetcraft.cell_editor");
+ let id = egui::Id::new("gridcraft.cell_editor");
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(erect.shrink2(vec2(2.0, 1.0))));
crate::formula_bar::editor_widget(app, &mut child, id, font, false, erect.width() - 4.0);
}
@@ -1671,13 +1669,13 @@ fn filter_menu(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo) {
if let Some(d) = d {
let wbm = Arc::make_mut(&mut d.wb);
if let Some(s) = wbm.sheet_mut(si) {
- s.autofilter = Some(sheetcraft_engine::model::AutoFilter { range: r, criteria: vec![] });
+ s.autofilter = Some(gridcraft_engine::model::AutoFilter { range: r, criteria: vec![] });
}
}
}
}
let wb = app.session.active().map(|d| d.wb.clone()).unwrap_or(wb);
- let values = sheetcraft_engine::cmd::data::filter_values(&wb, si, col);
+ let values = gridcraft_engine::cmd::data::filter_values(&wb, si, col);
let mut close = false;
let area = egui::Area::new(egui::Id::new("filter_menu")).fixed_pos(at + vec2(-180.0, 10.0)).order(egui::Order::Foreground);
area.show(ui.ctx(), |ui| {
@@ -1783,7 +1781,7 @@ fn context_menu(app: &mut SheetApp, ui: &mut egui::Ui) {
"ui:filterValue" => {
if let Some(d) = app.session.active() {
let a = d.selection.active;
- let text = d.wb.active().map(|sh| sheetcraft_engine::display::cell_text(&d.wb, sh, a)).unwrap_or_default();
+ let text = d.wb.active().map(|sh| gridcraft_engine::display::cell_text(&d.wb, sh, a)).unwrap_or_default();
let has = d.wb.active().is_some_and(|sh| sh.autofilter.is_some());
if !has {
let _ = app.run("data.filter", json!({}));
@@ -1835,7 +1833,7 @@ pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, exte
/// The dropdown arrow beside a cell with an in-cell list validation.
pub fn validation_arrow(sh: &Sheet, geo: &Geo, c: CellRef) -> Option {
let dv = sh.validations.iter().find(|d| d.ranges.iter().any(|r| r.contains(c)))?;
- if dv.kind != sheetcraft_engine::model::ValidationKind::List || !dv.in_cell_dropdown {
+ if dv.kind != gridcraft_engine::model::ValidationKind::List || !dv.in_cell_dropdown {
return None;
}
let r = geo.cell_rect(sh, c);
@@ -1852,7 +1850,7 @@ fn paint_overlays(
wb: &Workbook,
si: usize,
sh: &Sheet,
- sel: &sheetcraft_engine::Selection,
+ sel: &gridcraft_engine::Selection,
) {
// Validation dropdown arrow for the active cell.
if app.editor.is_none()
@@ -1951,7 +1949,7 @@ fn list_picker(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo) {
app.grid.list_picker = None;
return;
};
- let items = sheetcraft_engine::cmd::data::list_items(&wb, si, &dv);
+ let items = gridcraft_engine::cmd::data::list_items(&wb, si, &dv);
let r = geo.cell_rect(sh, c);
let mut close = false;
egui::Area::new(egui::Id::new("dv_list")).fixed_pos(r.left_bottom()).order(egui::Order::Foreground).show(ui.ctx(), |ui| {
diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs
index 9e0f786..b2079c8 100644
--- a/crates/ui-egui/src/icons.rs
+++ b/crates/ui-egui/src/icons.rs
@@ -1,4 +1,4 @@
-//! SheetCraft's icon set, drawn in code on a 16×16 design grid (original artwork; see
+//! GridCraft's icon set, drawn in code on a 16×16 design grid (original artwork; see
//! ATTRIBUTION.md). Large ribbon icons use the same drawings at 32×32 with accent colours.
use egui::epaint::{CubicBezierShape, PathShape, PathStroke};
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
index 29af239..fa333a0 100644
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -1,5 +1,5 @@
-//! SheetCraft's egui frontend: an Excel-style window (title bar with Quick Access Toolbar,
-//! ribbon, formula bar, grid, sheet tabs, status bar) over `sheetcraft-engine`.
+//! GridCraft's egui frontend: an Excel-style window (title bar with Quick Access Toolbar,
+//! ribbon, formula bar, grid, sheet tabs, status bar) over `gridcraft-engine`.
//!
//! The UI is thin: it reads engine state and acts through [`SheetApp::run`] (engine commands by
//! id). Everything it can do is also reachable over the control channel ([`control`]).
@@ -22,10 +22,10 @@ pub mod widgets;
use std::collections::HashMap;
+use gridcraft_engine::core::{CellRef, RangeRef};
+use gridcraft_engine::{Session, UiRequest};
use serde::{Deserialize, Serialize};
use serde_json::{Value as Json, json};
-use sheetcraft_engine::core::{CellRef, RangeRef};
-use sheetcraft_engine::{Session, UiRequest};
pub use control::{ControlRequest, ControlResponse};
@@ -161,7 +161,7 @@ impl SheetApp {
if e.contains("is not available right now") {
self.toast = Some((e, now_ms()));
} else {
- self.message = Some(("SheetCraft".into(), clean_error(&e)));
+ self.message = Some(("GridCraft".into(), clean_error(&e)));
}
}
}
@@ -205,7 +205,7 @@ impl SheetApp {
for req in self.session.take_ui_requests() {
match req {
UiRequest::Dialog(name, params) => self.open_dialog(&name, params),
- UiRequest::Message(m) => self.message = Some(("SheetCraft".into(), m)),
+ UiRequest::Message(m) => self.message = Some(("GridCraft".into(), m)),
UiRequest::EditCell(text) => self.begin_edit(text, false),
UiRequest::OpenUrl(u) => {
if let Some(f) = &self.services.open_url {
@@ -236,7 +236,7 @@ impl SheetApp {
}
} else if let Some(dl) = &self.services.download
&& let Ok(r) = self.session.run("file.saveBytes", json!({"format": "xlsx"}))
- && let Some(b) = r.get("base64").and_then(Json::as_str).and_then(sheetcraft_engine::io::base64_decode)
+ && let Some(b) = r.get("base64").and_then(Json::as_str).and_then(gridcraft_engine::io::base64_decode)
{
let name = r.get("name").and_then(Json::as_str).unwrap_or("Book.xlsx").to_string();
dl(&name, &b);
@@ -253,7 +253,7 @@ impl SheetApp {
self.ui.recent.insert(0, path.to_string());
self.ui.recent.truncate(20);
}
- Err(e) => self.message = Some(("SheetCraft".into(), clean_error(&e))),
+ Err(e) => self.message = Some(("GridCraft".into(), clean_error(&e))),
}
self.after_engine();
}
@@ -286,26 +286,26 @@ impl SheetApp {
ed.completion = editor::column_completion(sh, at, &ed.text);
}
self.editor = Some(ed);
- self.session.mode = if replace { sheetcraft_engine::Mode::Enter } else { sheetcraft_engine::Mode::Edit };
+ self.session.mode = if replace { gridcraft_engine::Mode::Enter } else { gridcraft_engine::Mode::Edit };
}
/// Commits the edit; `then` moves the selection (dr, dc) afterwards.
pub fn commit_edit(&mut self, dr: i64, dc: i64, array: bool, fill_selection: bool) -> bool {
let Some(ed) = self.editor.take() else { return true };
- self.session.mode = sheetcraft_engine::Mode::Ready;
+ self.session.mode = gridcraft_engine::Mode::Ready;
let text = match &ed.completion {
Some(full) if full.to_lowercase().starts_with(&ed.text.to_lowercase()) => full.clone(),
_ => ed.text.clone(),
};
// Data validation.
if let Some(d) = self.session.active()
- && let Some((dv, msg)) = sheetcraft_engine::cmd::data::check_validation(&d.wb, ed.sheet, ed.cell, &text)
+ && let Some((dv, msg)) = gridcraft_engine::cmd::data::check_validation(&d.wb, ed.sheet, ed.cell, &text)
{
- if dv.error_style == sheetcraft_engine::model::ErrorStyle::Stop {
- let title = if dv.error_title.is_empty() { "SheetCraft".to_string() } else { dv.error_title.clone() };
+ if dv.error_style == gridcraft_engine::model::ErrorStyle::Stop {
+ let title = if dv.error_title.is_empty() { "GridCraft".to_string() } else { dv.error_title.clone() };
self.message = Some((title, msg));
self.editor = Some(ed);
- self.session.mode = sheetcraft_engine::Mode::Edit;
+ self.session.mode = gridcraft_engine::Mode::Edit;
return false;
}
self.toast = Some((msg, now_ms()));
@@ -324,9 +324,9 @@ impl SheetApp {
true
}
Err(e) => {
- self.message = Some(("SheetCraft".into(), clean_error(&e)));
+ self.message = Some(("GridCraft".into(), clean_error(&e)));
self.editor = Some(ed);
- self.session.mode = sheetcraft_engine::Mode::Edit;
+ self.session.mode = gridcraft_engine::Mode::Edit;
false
}
}
@@ -334,7 +334,7 @@ impl SheetApp {
pub fn cancel_edit(&mut self) {
self.editor = None;
- self.session.mode = sheetcraft_engine::Mode::Ready;
+ self.session.mode = gridcraft_engine::Mode::Ready;
}
/// Enter/Tab move inside a multi-cell selection, otherwise to the next cell.
@@ -373,9 +373,9 @@ impl SheetApp {
.map(|i| std::mem::take(&mut *i.lock().unwrap_or_else(std::sync::PoisonError::into_inner)))
.unwrap_or_default();
for (name, bytes) in arrived {
- let b64 = sheetcraft_engine::io::base64_encode(&bytes);
+ let b64 = gridcraft_engine::io::base64_encode(&bytes);
if let Err(e) = self.session.run("file.open", json!({"name": name, "base64": b64})) {
- self.message = Some(("SheetCraft".into(), clean_error(&e)));
+ self.message = Some(("GridCraft".into(), clean_error(&e)));
}
self.after_engine();
}
diff --git a/crates/ui-egui/src/panes.rs b/crates/ui-egui/src/panes.rs
index 0142a56..fb8a9d7 100644
--- a/crates/ui-egui/src/panes.rs
+++ b/crates/ui-egui/src/panes.rs
@@ -39,7 +39,7 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) {
fn comments(app: &mut SheetApp, ui: &mut Ui) {
let Some(d) = app.session.active() else { return };
let Some(sh) = d.wb.active() else { return };
- let list: Vec<(String, sheetcraft_engine::model::Comment)> = sh.comments.iter().map(|(c, m)| (c.a1(), m.clone())).collect();
+ let list: Vec<(String, gridcraft_engine::model::Comment)> = sh.comments.iter().map(|(c, m)| (c.a1(), m.clone())).collect();
if list.is_empty() {
ui.label(egui::RichText::new("No comments on this sheet yet. Select a cell and choose New Comment.").italics());
}
@@ -91,7 +91,7 @@ fn watch(app: &mut SheetApp, ui: &mut Ui) {
&& let Some(d) = app.session.active()
&& let Some(sh) = d.wb.active()
{
- let r = format!("{}!{}", sheetcraft_engine::formula::quote_sheet(&sh.name), d.selection.active.a1());
+ let r = format!("{}!{}", gridcraft_engine::formula::quote_sheet(&sh.name), d.selection.active.a1());
let _ = app.session.run("formulas.watchWindow", json!({"add": r}));
}
let list = app.session.run("formulas.watchWindow", json!({})).unwrap_or(Json::Null);
@@ -211,7 +211,7 @@ fn format_chart(app: &mut SheetApp, ui: &mut Ui) {
});
});
ui.collapsing("Series", |ui| {
- let data = app.session.active().map(|d| sheetcraft_chart::resolve(&d.wb, d.wb.active_sheet, &chart));
+ let data = app.session.active().map(|d| gridcraft_chart::resolve(&d.wb, d.wb.active_sheet, &chart));
for (i, s) in chart.series.iter().enumerate() {
let name = data.as_ref().and_then(|d| d.series.get(i)).map(|x| x.name.clone()).unwrap_or_else(|| format!("Series {}", i + 1));
let col =
diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs
index 52d986a..4020a17 100644
--- a/crates/ui-egui/src/ribbon.rs
+++ b/crates/ui-egui/src/ribbon.rs
@@ -1,8 +1,8 @@
//! Title bar (Quick Access Toolbar), ribbon tabs and ribbon groups, and keyboard shortcuts.
use egui::{Align2, Color32, Key, Modifiers, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
+use gridcraft_engine::model::{HAlign, Style, VAlign};
use serde_json::json;
-use sheetcraft_engine::model::{HAlign, Style, VAlign};
use crate::SheetApp;
use crate::icons::{self, Icon};
@@ -234,7 +234,7 @@ fn active_style(app: &SheetApp) -> Style {
}
fn theme_colors(app: &SheetApp) -> [u32; 12] {
- app.session.active().map(|d| d.wb.theme.colors).unwrap_or(sheetcraft_engine::model::Theme::default().colors)
+ app.session.active().map(|d| d.wb.theme.colors).unwrap_or(gridcraft_engine::model::Theme::default().colors)
}
/// Runs a command from a ribbon click (menu-style: no params may open a dialog).
@@ -380,7 +380,7 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
}
});
let fill = app.grid.last_fill.clone();
- let fill_c = sheetcraft_engine::model::Color::from_hex(&fill).and_then(|c| c.resolve(&Default::default())).map(theme::color32);
+ let fill_c = gridcraft_engine::model::Color::from_hex(&fill).and_then(|c| c.resolve(&Default::default())).map(theme::color32);
let (f, fa) = split_button(ui, Icon::Fill, fill_c, "Fill Color");
if f {
act(app, "home.fillColor", json!({"color": fill}));
@@ -395,7 +395,7 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
}
});
let fc = app.grid.last_font_color.clone();
- let fc_c = sheetcraft_engine::model::Color::from_hex(&fc).and_then(|c| c.resolve(&Default::default())).map(theme::color32);
+ let fc_c = gridcraft_engine::model::Color::from_hex(&fc).and_then(|c| c.resolve(&Default::default())).map(theme::color32);
let (c, ca) = split_button(ui, Icon::FontColor, fc_c, "Font Color");
if c {
act(app, "home.fontColor", json!({"color": fc}));
@@ -734,7 +734,7 @@ fn size_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
fn number_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
let code = st.num_fmt.as_str().to_string();
- let kind = sheetcraft_engine::display::number_format(&code).kind();
+ let kind = gridcraft_engine::display::number_format(&code).kind();
let current = format!("{kind:?}");
let sample = app.session.active().and_then(|d| d.wb.active().map(|sh| sh.value(d.selection.active))).unwrap_or_default();
let mut picked: Option<&str> = None;
@@ -744,8 +744,8 @@ fn number_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
for name in
["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"]
{
- let code = sheetcraft_engine::cmd::format::format_code_for(name);
- let preview = app.session.active().map(|d| sheetcraft_engine::display::format(&sample, code, &d.wb).text).unwrap_or_default();
+ let code = gridcraft_engine::cmd::format::format_code_for(name);
+ let preview = app.session.active().map(|d| gridcraft_engine::display::format(&sample, code, &d.wb).text).unwrap_or_default();
let r = ui.add(egui::Button::selectable(current == name, format!("{name:<12} {preview}")).min_size(vec2(240.0, 22.0)));
if r.clicked() {
picked = Some(name);
@@ -877,9 +877,9 @@ fn table_gallery(app: &mut SheetApp, ui: &mut Ui, cmd: &str) {
}
}
-fn paint_table_swatch(ui: &Ui, r: Rect, wb: &sheetcraft_engine::model::Workbook, style: &str) {
- let lk = sheetcraft_engine::tables::look(style);
- let res = |c: Option| c.and_then(|c| c.resolve(&wb.theme)).map(theme::color32);
+fn paint_table_swatch(ui: &Ui, r: Rect, wb: &gridcraft_engine::model::Workbook, style: &str) {
+ let lk = gridcraft_engine::tables::look(style);
+ let res = |c: Option| c.and_then(|c| c.resolve(&wb.theme)).map(theme::color32);
let p = ui.painter();
p.rect_filled(r, 0.0, Color32::WHITE);
let rows = 5;
@@ -950,13 +950,13 @@ fn cell_style_gallery(app: &mut SheetApp, ui: &mut Ui) {
ui.label(egui::RichText::new(*g).strong());
egui::Grid::new(("csg", *g)).spacing(vec2(4.0, 4.0)).show(ui, |ui| {
for (i, n) in names.iter().enumerate() {
- let st = sheetcraft_engine::cmd::format::builtin_cell_style(n, &wb.theme).unwrap_or_default();
+ let st = gridcraft_engine::cmd::format::builtin_cell_style(n, &wb.theme).unwrap_or_default();
let (r, resp) = ui.allocate_exact_size(vec2(82.0, 24.0), Sense::click());
let fill = st
.fill
.fg
.resolve(&wb.theme)
- .filter(|_| st.fill.pattern != sheetcraft_engine::model::PatternType::None)
+ .filter(|_| st.fill.pattern != gridcraft_engine::model::PatternType::None)
.map(theme::color32)
.unwrap_or(Color32::WHITE);
ui.painter().rect_filled(r, 0.0, fill);
@@ -1189,7 +1189,7 @@ fn draw(app: &mut SheetApp, ui: &mut Ui) {
fn page_layout(app: &mut SheetApp, ui: &mut Ui) {
let th = big_button(ui, Icon::Theme, "Themes", "Themes", true);
egui::Popup::menu(&th).show(|ui| {
- for (name, colors) in sheetcraft_engine::cmd::view::themes() {
+ for (name, colors) in gridcraft_engine::cmd::view::themes() {
ui.horizontal(|ui| {
for c in colors.iter().skip(4).take(6) {
let (r, _) = ui.allocate_exact_size(vec2(12.0, 12.0), Sense::hover());
@@ -1308,7 +1308,7 @@ fn formulas(app: &mut SheetApp, ui: &mut Ui) {
egui::Popup::menu(&r).show(|ui| {
ui.set_max_height(420.0);
egui::ScrollArea::vertical().show(ui, |ui| {
- for f in sheetcraft_engine::cmd::formulas::function_list() {
+ for f in gridcraft_engine::cmd::formulas::function_list() {
if f["category"].as_str() == Some(cat)
&& let Some(n) = f["name"].as_str()
{
@@ -1577,7 +1577,7 @@ fn automate(app: &mut SheetApp, ui: &mut Ui) {
if big_button(ui, Icon::Script, "Command\nPalette", "Search and run any command", false).clicked() {
app.open_dialog("commandSearch", json!({}));
}
- if big_button(ui, Icon::Script, "Agent\nControl", "How agents drive SheetCraft (MCP / control channel)", false).clicked() {
+ if big_button(ui, Icon::Script, "Agent\nControl", "How agents drive GridCraft (MCP / control channel)", false).clicked() {
app.open_dialog("agents", json!({}));
}
if big_button(ui, Icon::Script, "Action\nJournal", "Every command run in this session (replayable)", false).clicked() {
@@ -1750,11 +1750,11 @@ pub fn shortcut(app: &mut SheetApp, key: Key, m: Modifiers) {
}
Key::Semicolon => {
// Insert today's date.
- let today = sheetcraft_engine::calc::now_serial().floor();
+ let today = gridcraft_engine::calc::now_serial().floor();
let text = app
.session
.active()
- .map(|d| sheetcraft_engine::display::format(&sheetcraft_engine::core::Value::Number(today), "m/d/yyyy", &d.wb).text)
+ .map(|d| gridcraft_engine::display::format(&gridcraft_engine::core::Value::Number(today), "m/d/yyyy", &d.wb).text)
.unwrap_or_default();
app.begin_edit(Some(text), false);
None
diff --git a/crates/ui-egui/src/tabs.rs b/crates/ui-egui/src/tabs.rs
index 1e27c7b..0258169 100644
--- a/crates/ui-egui/src/tabs.rs
+++ b/crates/ui-egui/src/tabs.rs
@@ -1,8 +1,8 @@
//! Sheet tabs and the status bar.
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
+use gridcraft_engine::model::Visibility;
use serde_json::json;
-use sheetcraft_engine::model::Visibility;
use crate::SheetApp;
use crate::icons::{self, Icon};
@@ -185,14 +185,14 @@ pub fn status_bar(app: &mut SheetApp, ui: &mut Ui) {
|ui| {
ui.horizontal_centered(|ui| {
let mode = match app.session.mode {
- sheetcraft_engine::Mode::Ready => "Ready",
- sheetcraft_engine::Mode::Enter => "Enter",
- sheetcraft_engine::Mode::Edit => "Edit",
- sheetcraft_engine::Mode::Point => "Point",
+ gridcraft_engine::Mode::Ready => "Ready",
+ gridcraft_engine::Mode::Enter => "Enter",
+ gridcraft_engine::Mode::Edit => "Edit",
+ gridcraft_engine::Mode::Point => "Point",
};
ui.label(egui::RichText::new(mode).font(theme::ui_font(12.5)).color(t.text_dim));
if let Some(d) = app.session.active() {
- if d.wb.calc.mode == sheetcraft_engine::model::CalcMode::Manual {
+ if d.wb.calc.mode == gridcraft_engine::model::CalcMode::Manual {
ui.add_space(12.0);
ui.label(egui::RichText::new("Calculate").font(theme::ui_font(12.5)).color(t.text_dim));
}
@@ -256,11 +256,11 @@ pub fn status_bar(app: &mut SheetApp, ui: &mut Ui) {
&& let Some(sh) = d.wb.active()
&& !d.selection.is_single_cell()
{
- let st = sheetcraft_engine::display::stats(sh, &d.selection.ranges);
+ let st = gridcraft_engine::display::stats(sh, &d.selection.ranges);
let fmt = |v: f64| {
let code = d.wb.styles.get(sh.style_id(d.selection.active)).num_fmt.as_str().to_string();
let code = if code == "General" { "#,##0.##########".to_string() } else { code };
- sheetcraft_engine::display::format(&sheetcraft_engine::core::Value::Number(v), &code, &d.wb)
+ gridcraft_engine::display::format(&gridcraft_engine::core::Value::Number(v), &code, &d.wb)
.text
.trim_end_matches('.')
.to_string()
diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs
index 80fa14e..139256f 100644
--- a/crates/ui-egui/src/widgets.rs
+++ b/crates/ui-egui/src/widgets.rs
@@ -121,7 +121,7 @@ pub fn color_palette(ui: &mut Ui, theme_colors: &[u32; 12], none_label: &str) ->
for &i in &order {
let base = theme_colors.get(i).copied().unwrap_or(0);
let rgb = [(base >> 16) as u8, (base >> 8) as u8, base as u8];
- let c = sheetcraft_engine::model::style::apply_tint(rgb, tint);
+ let c = gridcraft_engine::model::style::apply_tint(rgb, tint);
let col = Color32::from_rgb(c[0], c[1], c[2]);
let (r, resp) = ui.allocate_exact_size(vec2(16.0, 14.0), Sense::click());
ui.painter().rect_filled(r, 1.0, col);
@@ -140,7 +140,7 @@ pub fn color_palette(ui: &mut Ui, theme_colors: &[u32; 12], none_label: &str) ->
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0;
for hex in ["#C00000", "#FF0000", "#FFC000", "#FFFF00", "#92D050", "#00B050", "#00B0F0", "#0070C0", "#002060", "#7030A0"] {
- let col = sheetcraft_engine::model::Color::from_hex(hex)
+ let col = gridcraft_engine::model::Color::from_hex(hex)
.and_then(|c| c.resolve(&Default::default()))
.map(theme::color32)
.unwrap_or(Color32::BLACK);
diff --git a/crates/ui-egui/tests/interaction.rs b/crates/ui-egui/tests/interaction.rs
index d5cba8b..38b254d 100644
--- a/crates/ui-egui/tests/interaction.rs
+++ b/crates/ui-egui/tests/interaction.rs
@@ -1,10 +1,10 @@
//! UI interaction tests: real egui input through the whole app (no GPU needed).
use egui::{Event, Key, Modifiers};
+use gridcraft_engine::Session;
+use gridcraft_engine::core::{CellRef, Value};
+use gridcraft_ui_egui::SheetApp;
use serde_json::json;
-use sheetcraft_engine::Session;
-use sheetcraft_engine::core::{CellRef, Value};
-use sheetcraft_ui_egui::SheetApp;
fn harness(session: Session) -> egui_kittest::Harness<'static, SheetApp> {
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
diff --git a/crates/xlsx/Cargo.toml b/crates/xlsx/Cargo.toml
index 58b4871..8f79447 100644
--- a/crates/xlsx/Cargo.toml
+++ b/crates/xlsx/Cargo.toml
@@ -1,17 +1,17 @@
[package]
-name = "sheetcraft-xlsx"
+name = "gridcraft-xlsx"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SheetCraft file formats: XLSX (SpreadsheetML) and CSV import/export"
+description = "GridCraft file formats: XLSX (SpreadsheetML) and CSV import/export"
[lints]
workspace = true
[dependencies]
-sheetcraft-core = { workspace = true }
-sheetcraft-formula = { workspace = true }
-sheetcraft-model = { workspace = true }
+gridcraft-core = { workspace = true }
+gridcraft-formula = { workspace = true }
+gridcraft-model = { workspace = true }
thiserror = { workspace = true }
quick-xml = { workspace = true }
zip = { workspace = true }
diff --git a/crates/xlsx/src/chart.rs b/crates/xlsx/src/chart.rs
index 8aa167a..eac9ef2 100644
--- a/crates/xlsx/src/chart.rs
+++ b/crates/xlsx/src/chart.rs
@@ -3,9 +3,9 @@
use std::fmt::Write as _;
-use sheetcraft_core::Value;
-use sheetcraft_formula::{Expr, SheetSel};
-use sheetcraft_model::{Chart, ChartKind, Color, LegendPos, Series, Workbook};
+use gridcraft_core::Value;
+use gridcraft_formula::{Expr, SheetSel};
+use gridcraft_model::{Chart, ChartKind, Color, LegendPos, Series, Workbook};
use crate::drawing::{dml_color, srgb};
use crate::xml::{El, esc, num};
@@ -227,7 +227,7 @@ const MAX_CACHE: u64 = 100_000;
/// Values of a same-workbook reference (`Sheet1!$B$2:$B$9`), row-major.
fn ref_values(wb: &Workbook, sheet_idx: usize, f: &str) -> Option> {
- let e = sheetcraft_formula::parse(f).ok()?;
+ let e = gridcraft_formula::parse(f).ok()?;
let Expr::Ref(r) = e else { return None };
let si = match &r.sheet {
SheetSel::Current => sheet_idx,
@@ -247,7 +247,7 @@ fn ref_values(wb: &Workbook, sheet_idx: usize, f: &str) -> Option> {
}
fn is_ref(f: &str) -> bool {
- matches!(sheetcraft_formula::parse(f), Ok(Expr::Ref(_)))
+ matches!(gridcraft_formula::parse(f), Ok(Expr::Ref(_)))
}
fn num_data(tag: &str, f: Option<&str>, wb: &Workbook, si: usize) -> String {
diff --git a/crates/xlsx/src/csv.rs b/crates/xlsx/src/csv.rs
index 0182095..b6221a0 100644
--- a/crates/xlsx/src/csv.rs
+++ b/crates/xlsx/src/csv.rs
@@ -2,9 +2,9 @@
use std::sync::Arc;
-use sheetcraft_core::parse::parse_input;
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS, Value, number_to_text};
-use sheetcraft_model::{Cell, NumFmt, Sheet, StyleId, Workbook};
+use gridcraft_core::parse::parse_input;
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS, Value, number_to_text};
+use gridcraft_model::{Cell, NumFmt, Sheet, StyleId, Workbook};
use crate::{CsvOptions, IoError};
diff --git a/crates/xlsx/src/drawing.rs b/crates/xlsx/src/drawing.rs
index 45aef38..e65a21b 100644
--- a/crates/xlsx/src/drawing.rs
+++ b/crates/xlsx/src/drawing.rs
@@ -3,8 +3,8 @@
use std::fmt::Write as _;
-use sheetcraft_core::{CellRef, MAX_COLS, MAX_ROWS};
-use sheetcraft_model::{Anchor, Color, Image, Shape, ShapeKind, Sheet, Theme};
+use gridcraft_core::{CellRef, MAX_COLS, MAX_ROWS};
+use gridcraft_model::{Anchor, Color, Image, Shape, ShapeKind, Sheet, Theme};
use crate::IoError;
use crate::read::Ctx;
@@ -233,7 +233,7 @@ pub fn srgb(c: &Color, theme: &Theme) -> Option {
/// One drawing object to write.
pub enum Obj<'a> {
Image(&'a Image, String),
- Chart(&'a sheetcraft_model::Chart, String),
+ Chart(&'a gridcraft_model::Chart, String),
Shape(&'a Shape),
}
diff --git a/crates/xlsx/src/fmla.rs b/crates/xlsx/src/fmla.rs
index 46c39da..eee516b 100644
--- a/crates/xlsx/src/fmla.rs
+++ b/crates/xlsx/src/fmla.rs
@@ -1,8 +1,8 @@
//! Formula text conversion between the model (canonical text, no `=`) and files (`_xlfn.`
//! prefixes for newer functions).
-use sheetcraft_formula::Expr;
-use sheetcraft_model::Formula;
+use gridcraft_formula::Expr;
+use gridcraft_model::Formula;
/// Formula text for a file.
pub fn to_file(f: &Formula) -> String {
@@ -15,7 +15,7 @@ pub fn to_file(f: &Formula) -> String {
/// Arbitrary formula text (names, CF, validation) for a file. Unparseable text is kept.
pub fn text_to_file(text: &str) -> String {
let body = text.strip_prefix('=').unwrap_or(text);
- match sheetcraft_formula::parse(body) {
+ match gridcraft_formula::parse(body) {
Ok(e) => expr_to_file(e),
Err(_) => body.to_string(),
}
@@ -29,14 +29,14 @@ fn expr_to_file(e: Expr) -> String {
},
other => other,
});
- sheetcraft_formula::print(&e)
+ gridcraft_formula::print(&e)
}
/// Formula text read from a file → model text (strips prefixes when it parses).
pub fn from_file(text: &str) -> String {
let body = text.strip_prefix('=').unwrap_or(text);
- match sheetcraft_formula::parse(body) {
- Ok(e) => sheetcraft_formula::print(&e),
+ match gridcraft_formula::parse(body) {
+ Ok(e) => gridcraft_formula::print(&e),
Err(_) => body.to_string(),
}
}
diff --git a/crates/xlsx/src/lib.rs b/crates/xlsx/src/lib.rs
index a17f7f4..7c72476 100644
--- a/crates/xlsx/src/lib.rs
+++ b/crates/xlsx/src/lib.rs
@@ -1,4 +1,4 @@
-//! SheetCraft file formats: XLSX (Office Open XML SpreadsheetML, transitional and strict) and CSV.
+//! GridCraft file formats: XLSX (Office Open XML SpreadsheetML, transitional and strict) and CSV.
//!
//! Clean-room implementation from ECMA-376 / ISO/IEC 29500 and observed behaviour. All entry
//! points work on byte slices (no file system access), so the crate builds for WebAssembly.
@@ -28,8 +28,8 @@ mod xml;
#[cfg(test)]
mod tests;
-use sheetcraft_core::DateSystem;
-use sheetcraft_model::Workbook;
+use gridcraft_core::DateSystem;
+use gridcraft_model::Workbook;
pub use csv::{read_csv, write_csv};
diff --git a/crates/xlsx/src/pivot.rs b/crates/xlsx/src/pivot.rs
index 892bfbf..656af93 100644
--- a/crates/xlsx/src/pivot.rs
+++ b/crates/xlsx/src/pivot.rs
@@ -14,10 +14,10 @@
use std::collections::HashMap;
use std::fmt::Write as _;
-use sheetcraft_core::date::datetime_from_serial;
-use sheetcraft_core::{CellRef, DateSystem, RangeRef, Value, number_to_text};
-use sheetcraft_formula::quote_sheet;
-use sheetcraft_model::{PivotDateGroup, PivotField, PivotFilter, PivotFunc, PivotLayout, PivotShowAs, PivotSort, PivotTable, PivotValue, Workbook};
+use gridcraft_core::date::datetime_from_serial;
+use gridcraft_core::{CellRef, DateSystem, RangeRef, Value, number_to_text};
+use gridcraft_formula::quote_sheet;
+use gridcraft_model::{PivotDateGroup, PivotField, PivotFilter, PivotFunc, PivotLayout, PivotShowAs, PivotSort, PivotTable, PivotValue, Workbook};
use crate::IoError;
use crate::read::Ctx;
@@ -654,7 +654,7 @@ fn cache_xml(c: &Cache, records_rid: Option<&str>, sys: DateSystem) -> String {
}
_ => s.push_str(" saveData=\"0\""),
}
- s.push_str(" refreshOnLoad=\"1\" refreshedBy=\"SheetCraft\" createdVersion=\"6\" refreshedVersion=\"6\" minRefreshableVersion=\"3\">");
+ s.push_str(" refreshOnLoad=\"1\" refreshedBy=\"GridCraft\" createdVersion=\"6\" refreshedVersion=\"6\" minRefreshableVersion=\"3\">");
let _ = write!(s, "{} ", c.source_xml);
let _ = write!(s, "", c.fields.len());
for f in &c.fields {
diff --git a/crates/xlsx/src/read.rs b/crates/xlsx/src/read.rs
index 5a0ae11..915d8e4 100644
--- a/crates/xlsx/src/read.rs
+++ b/crates/xlsx/src/read.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
-use sheetcraft_core::{DateSystem, RangeRef};
-use sheetcraft_model::{CalcMode, DefinedName, Sheet, Style, StyleId, StyleTable, Visibility, Workbook};
+use gridcraft_core::{DateSystem, RangeRef};
+use gridcraft_model::{CalcMode, DefinedName, Sheet, Style, StyleId, StyleTable, Visibility, Workbook};
use crate::package::{Package, Rel};
use crate::styles::{StylesIn, read_styles};
@@ -315,10 +315,10 @@ pub fn parse_print_titles(text: &str) -> (Option<(u32, u32)>, Option<(u32, u32)>
let p = strip_sheet(part.trim()).replace('$', "");
let Some((a, b)) = p.split_once(':') else { continue };
if let (Ok(r0), Ok(r1)) = (a.parse::(), b.parse::()) {
- if r0 >= 1 && r1 >= r0 && r1 <= sheetcraft_core::MAX_ROWS {
+ if r0 >= 1 && r1 >= r0 && r1 <= gridcraft_core::MAX_ROWS {
rows = Some((r0 - 1, r1 - 1));
}
- } else if let (Some(c0), Some(c1)) = (sheetcraft_core::letters_to_col(a), sheetcraft_core::letters_to_col(b))
+ } else if let (Some(c0), Some(c1)) = (gridcraft_core::letters_to_col(a), gridcraft_core::letters_to_col(b))
&& c1 >= c0
{
cols = Some((c0, c1));
diff --git a/crates/xlsx/src/sheet_read.rs b/crates/xlsx/src/sheet_read.rs
index be998cb..9f382b1 100644
--- a/crates/xlsx/src/sheet_read.rs
+++ b/crates/xlsx/src/sheet_read.rs
@@ -3,10 +3,10 @@
use std::collections::HashMap;
use std::sync::Arc;
-use sheetcraft_core::date::{serial_from_ymd, time_fraction};
-use sheetcraft_core::{CellError, CellRef, DateSystem, MAX_COLS, MAX_ROWS, RangeRef, Value};
-use sheetcraft_formula::Expr;
-use sheetcraft_model::{
+use gridcraft_core::date::{serial_from_ymd, time_fraction};
+use gridcraft_core::{CellError, CellRef, DateSystem, MAX_COLS, MAX_ROWS, RangeRef, Value};
+use gridcraft_formula::Expr;
+use gridcraft_model::{
AutoFilter, Cell, CfOperator, CfRule, CfValueKind, Comment, CondFormat, ErrorStyle, FilterCriterion, Formula, Hyperlink, LineInfo, Orientation,
Sheet, SheetProtection, Sparkline, SparklineKind, Table, TableColumn, TotalsFn, Validation, ValidationKind,
};
@@ -251,7 +251,7 @@ fn read_cols(cx: &Ctx<'_>, e: &El, sheet: &mut Sheet) {
info.collapsed = c.flag("collapsed", false);
if let Some(s) = c.attr_u32("style") {
let id = cx.style(Some(s));
- if id != sheetcraft_model::StyleId::DEFAULT {
+ if id != gridcraft_model::StyleId::DEFAULT {
info.style = Some(id);
}
}
@@ -374,7 +374,7 @@ fn read_formula(cx: &mut Ctx<'_>, st: &mut RowState, f: &El, pos: CellRef, dynam
"shared" => {
let si = f.attr_u32("si")?;
if !text.is_empty() {
- let expr = sheetcraft_formula::parse(text).ok();
+ let expr = gridcraft_formula::parse(text).ok();
st.shared.insert(si, Shared { anchor: pos, expr, text: text.to_string() });
Some(Formula::new(text))
} else {
@@ -386,7 +386,7 @@ fn read_formula(cx: &mut Ctx<'_>, st: &mut RowState, f: &El, pos: CellRef, dynam
Some(e) => {
let dr = pos.row as i64 - sh.anchor.row as i64;
let dc = pos.col as i64 - sh.anchor.col as i64;
- Formula::from_expr(sheetcraft_formula::adjust::shift_relative(e.clone(), dr, dc))
+ Formula::from_expr(gridcraft_formula::adjust::shift_relative(e.clone(), dr, dc))
}
None => Formula::new(&sh.text),
})
diff --git a/crates/xlsx/src/sheet_write.rs b/crates/xlsx/src/sheet_write.rs
index 41d01cd..94d9b18 100644
--- a/crates/xlsx/src/sheet_write.rs
+++ b/crates/xlsx/src/sheet_write.rs
@@ -3,8 +3,8 @@
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fmt::Write as _;
-use sheetcraft_core::{CellError, CellRef, RangeRef, Value};
-use sheetcraft_model::{
+use gridcraft_core::{CellError, CellRef, RangeRef, Value};
+use gridcraft_model::{
AutoFilter, Cell, CfOperator, CfRule, CfValueKind, Color, CondFormat, ErrorStyle, FilterCriterion, LineInfo, Orientation, Sheet, SparklineKind,
Style, Table, TotalsFn, Validation, ValidationKind, Workbook,
};
@@ -36,7 +36,7 @@ fn cf_op_name(op: CfOperator) -> &'static str {
/// Valid table name: letters, digits, `_` and `.`, starting with a letter or `_`.
fn table_name(name: &str, used: &mut HashSet, n: u32) -> String {
let mut s: String = name.chars().map(|c| if c.is_alphanumeric() || c == '_' || c == '.' { c } else { '_' }).collect();
- if s.is_empty() || !s.starts_with(|c: char| c.is_alphabetic() || c == '_') || sheetcraft_core::CellRef::parse(&s).is_some() {
+ if s.is_empty() || !s.starts_with(|c: char| c.is_alphabetic() || c == '_') || gridcraft_core::CellRef::parse(&s).is_some() {
s = format!("Table{n}");
}
let mut out = s.clone();
@@ -75,7 +75,7 @@ pub fn write_sheet(wb: &Workbook, si: usize, selected: bool, out: &mut Out) -> (
let mut rels = Rels::default();
let Some(sheet) = wb.sheet(si) else { return (String::new(), rels) };
let nstyles = wb.styles.len() as u32;
- let sid = |s: sheetcraft_model::StyleId| if s.0 < nstyles { s.0 } else { 0 };
+ let sid = |s: gridcraft_model::StyleId| if s.0 < nstyles { s.0 } else { 0 };
// Table header cells must hold the column names.
let mut overrides: BTreeMap = BTreeMap::new();
@@ -176,7 +176,7 @@ pub fn write_sheet(wb: &Workbook, si: usize, selected: bool, out: &mut Out) -> (
let _ = write!(
s,
" topLeftCell=\"{}\" activePane=\"{pane}\" state=\"frozen\"/>",
- CellRef::new(rows.min(sheetcraft_core::MAX_ROWS - 1), cols.min(sheetcraft_core::MAX_COLS - 1)).a1()
+ CellRef::new(rows.min(gridcraft_core::MAX_ROWS - 1), cols.min(gridcraft_core::MAX_COLS - 1)).a1()
);
let _ = write!(s, "");
}
@@ -190,11 +190,11 @@ pub fn write_sheet(wb: &Workbook, si: usize, selected: bool, out: &mut Out) -> (
let max_row_outline = sheet.rows.values().map(|r| r.outline).max().unwrap_or(0);
let max_col_outline = sheet.cols.values().map(|r| r.outline).max().unwrap_or(0);
s.push_str(" 0.01 {
+ if (sheet.default_col_width - gridcraft_model::DEFAULT_COL_WIDTH).abs() > 0.01 {
let _ = write!(s, " defaultColWidth=\"{}\"", num(px_to_col_width(sheet.default_col_width)));
}
let _ = write!(s, " defaultRowHeight=\"{}\"", num(px_to_pt(sheet.default_row_height)));
- if (sheet.default_row_height - sheetcraft_model::DEFAULT_ROW_HEIGHT).abs() > 0.01 {
+ if (sheet.default_row_height - gridcraft_model::DEFAULT_ROW_HEIGHT).abs() > 0.01 {
s.push_str(" customHeight=\"1\"");
}
if max_row_outline > 0 {
@@ -231,7 +231,7 @@ pub fn write_sheet(wb: &Workbook, si: usize, selected: bool, out: &mut Out) -> (
s.push_str("/>");
};
for (&c, info) in &sheet.cols {
- if c >= sheetcraft_core::MAX_COLS {
+ if c >= gridcraft_core::MAX_COLS {
continue;
}
run = match run {
@@ -516,7 +516,7 @@ pub fn write_sheet(wb: &Workbook, si: usize, selected: bool, out: &mut Out) -> (
SparklineKind::WinLoss => " type=\"stacked\"",
};
let src =
- if sp.source.contains('!') { sp.source.clone() } else { format!("{}!{}", sheetcraft_formula::quote_sheet(&sheet.name), sp.source) };
+ if sp.source.contains('!') { sp.source.clone() } else { format!("{}!{}", gridcraft_formula::quote_sheet(&sheet.name), sp.source) };
let color = if sp.color == Color::Auto { "rgb=\"FF376092\"".to_string() } else { color_attrs(&sp.color) };
let _ = write!(
s,
@@ -539,7 +539,7 @@ fn write_cell(
ov: Option<&String>,
extra: Option<&Value>,
dynamic: Option<&RangeRef>,
- sid: &dyn Fn(sheetcraft_model::StyleId) -> u32,
+ sid: &dyn Fn(gridcraft_model::StyleId) -> u32,
out: &mut Out,
) {
let style = cell.map(|x| sid(x.style)).unwrap_or(0);
@@ -853,7 +853,7 @@ fn autofilter_xml(af: &AutoFilter, out: &mut Out) -> String {
let _ = write!(s, "", if *above { "aboveAverage" } else { "belowAverage" });
}
FilterCriterion::FillColor(c) => {
- let st = Style { fill: sheetcraft_model::Fill::solid(*c), ..Default::default() };
+ let st = Style { fill: gridcraft_model::Fill::solid(*c), ..Default::default() };
let _ = write!(s, "", dxf_id(&mut out.dxfs, &st));
}
FilterCriterion::FontColor(c) => {
diff --git a/crates/xlsx/src/styles.rs b/crates/xlsx/src/styles.rs
index 1dbb55d..75df762 100644
--- a/crates/xlsx/src/styles.rs
+++ b/crates/xlsx/src/styles.rs
@@ -4,8 +4,8 @@
use std::collections::HashMap;
use std::fmt::Write as _;
-use sheetcraft_model::style::Protection;
-use sheetcraft_model::{
+use gridcraft_model::style::Protection;
+use gridcraft_model::{
Alignment, BorderLine, BorderStyle, Borders, Color, Fill, Font, HAlign, NumFmt, PatternType, Style, Underline, VAlign, VertAlign, Workbook,
};
@@ -579,7 +579,7 @@ pub fn write_styles(wb: &Workbook, dxfs: &[Style], extra_fmts: &[String]) -> (St
let mut borders: Vec = vec![Borders::default()];
let mut border_map: HashMap = HashMap::from([(Borders::default(), 0)]);
// The default style's font is font 0.
- intern(&mut fonts, &mut font_map, &wb.styles.get(sheetcraft_model::StyleId::DEFAULT).font);
+ intern(&mut fonts, &mut font_map, &wb.styles.get(gridcraft_model::StyleId::DEFAULT).font);
let mut xfs = String::new();
let mut count = 0;
diff --git a/crates/xlsx/src/tests/csv_tests.rs b/crates/xlsx/src/tests/csv_tests.rs
index bcd633b..451801f 100644
--- a/crates/xlsx/src/tests/csv_tests.rs
+++ b/crates/xlsx/src/tests/csv_tests.rs
@@ -1,5 +1,5 @@
-use sheetcraft_core::{CellRef, DateSystem, Value};
-use sheetcraft_model::{Sheet, Workbook};
+use gridcraft_core::{CellRef, DateSystem, Value};
+use gridcraft_model::{Sheet, Workbook};
use crate::csv::{decode_text, sniff_delimiter};
use crate::{CsvOptions, Format, read_csv, sniff, write_csv};
@@ -88,7 +88,7 @@ fn writing() {
s.set_value(CellRef::new(2, 1), Value::Number(0.1));
s.set_value(CellRef::new(2, 2), Value::Bool(false));
s.set_value(CellRef::new(3, 3), Value::text("x\ny"));
- s.set_value(CellRef::new(3, 1), Value::Error(sheetcraft_core::CellError::Div0));
+ s.set_value(CellRef::new(3, 1), Value::Error(gridcraft_core::CellError::Div0));
wb.sheets = vec![std::sync::Arc::new(s)];
let out = String::from_utf8(write_csv(wb.sheet(0).unwrap(), &wb, b',')).unwrap();
assert_eq!(out, "\"a,b\",\"q\"\"t\",\r\n0.1,FALSE,\r\n#DIV/0!,,\"x\ny\"\r\n");
diff --git a/crates/xlsx/src/tests/fixtures.rs b/crates/xlsx/src/tests/fixtures.rs
index d3416e3..c843323 100644
--- a/crates/xlsx/src/tests/fixtures.rs
+++ b/crates/xlsx/src/tests/fixtures.rs
@@ -1,7 +1,7 @@
//! Hand-written packages exercising specific reader paths.
-use sheetcraft_core::{CellError, CellRef, DateSystem, RangeRef, Value};
-use sheetcraft_model::{CalcMode, CfRule, Color, HAlign, PatternType, StyleId, Underline, Visibility};
+use gridcraft_core::{CellError, CellRef, DateSystem, RangeRef, Value};
+use gridcraft_model::{CalcMode, CfRule, Color, HAlign, PatternType, StyleId, Underline, Visibility};
use super::minimal;
use crate::read_xlsx;
@@ -246,7 +246,7 @@ Check this | | "#;
assert_eq!(t.name, "Sales");
assert!(t.totals_row && t.header_row && t.filter_button && t.last_col && t.banded_rows);
assert_eq!(t.columns[0].totals_label.as_deref(), Some("Total"));
- assert_eq!(t.columns[1].totals, sheetcraft_model::TotalsFn::Sum);
+ assert_eq!(t.columns[1].totals, gridcraft_model::TotalsFn::Sum);
assert_eq!(t.columns[2].totals_label.as_deref(), Some("SUBTOTAL(109,[Tax])"));
assert!(t.columns[2].formula.is_some());
assert_eq!(t.style, "TableStyleMedium9");
@@ -256,7 +256,7 @@ Check this "#;
let v2 = &s.validations[1];
assert!(!v2.in_cell_dropdown);
assert_eq!(v2.f2.as_deref(), Some("10"));
- assert_eq!(v2.error_style, sheetcraft_model::ErrorStyle::Warning);
+ assert_eq!(v2.error_style, gridcraft_model::ErrorStyle::Warning);
assert_eq!(s.validations[2].f1, "=Other!$A$1:$A$3");
let af = s.autofilter.as_ref().unwrap();
assert_eq!(af.criteria.len(), 2);
@@ -268,7 +268,7 @@ Check this "#;
assert!(p.format_cells && p.sort && !p.insert_rows && p.select_locked);
assert!(rep.warnings.iter().any(|w| w.contains("password")));
assert_eq!(s.sparklines.len(), 1);
- assert_eq!(s.sparklines[0].kind, sheetcraft_model::SparklineKind::Column);
+ assert_eq!(s.sparklines[0].kind, gridcraft_model::SparklineKind::Column);
}
#[test]
@@ -323,7 +323,7 @@ fn drawing_with_picture_and_chart() {
assert_eq!(img.anchor.width, 118.0);
assert_eq!(img.anchor.height, 100.0);
let ch = &s.charts[0];
- assert_eq!(ch.kind, sheetcraft_model::ChartKind::Combo);
+ assert_eq!(ch.kind, gridcraft_model::ChartKind::Combo);
assert_eq!(ch.title.as_deref(), Some("Sales"));
assert_eq!(ch.anchor.width, 480.0);
assert_eq!(ch.series.len(), 2);
@@ -332,8 +332,8 @@ fn drawing_with_picture_and_chart() {
assert_eq!(ch.series[0].color, Some(Color::Rgb(0xFF0000)));
assert_eq!(ch.series[1].name.as_deref(), Some("Lit"));
assert!(ch.series[1].secondary);
- assert_eq!(ch.series[1].kind, Some(sheetcraft_model::ChartKind::LineMarkers));
+ assert_eq!(ch.series[1].kind, Some(gridcraft_model::ChartKind::LineMarkers));
assert!(ch.data_labels && ch.gridlines);
- assert_eq!(ch.legend, sheetcraft_model::LegendPos::Top);
+ assert_eq!(ch.legend, gridcraft_model::LegendPos::Top);
assert_eq!(ch.x_title.as_deref(), Some("Region"));
}
diff --git a/crates/xlsx/src/tests/malformed.rs b/crates/xlsx/src/tests/malformed.rs
index 299c97d..dc33683 100644
--- a/crates/xlsx/src/tests/malformed.rs
+++ b/crates/xlsx/src/tests/malformed.rs
@@ -10,7 +10,7 @@ fn garbage_and_truncation() {
assert!(read_xlsx(b"").is_err());
assert!(read_xlsx(b"hello world").is_err());
assert!(read_xlsx(b"PK\x03\x04garbage").is_err());
- let good = write_xlsx(&sheetcraft_model::Workbook::new()).unwrap();
+ let good = write_xlsx(&gridcraft_model::Workbook::new()).unwrap();
for n in [10, 100, good.len() / 2, good.len() - 10] {
let _ = read_xlsx(&good[..n]);
}
@@ -57,7 +57,7 @@ fn huge_coordinates_and_dimensions() {
"#;
let (wb, rep) = read_xlsx(&minimal(body, &[], "", "")).unwrap();
let s = wb.sheet(0).unwrap();
- assert_eq!(s.value(sheetcraft_core::CellRef::parse("XFD3").unwrap()), sheetcraft_core::Value::Number(7.0));
+ assert_eq!(s.value(gridcraft_core::CellRef::parse("XFD3").unwrap()), gridcraft_core::Value::Number(7.0));
assert!(s.cells.len() <= 4);
assert!(s.hyperlinks.len() <= 10_000);
assert_eq!(s.zoom, 400);
@@ -78,7 +78,7 @@ fn deeply_nested_and_broken_xml() {
assert!(rep.warnings.iter().any(|w| w.contains("could not be read") || w.contains("damaged")));
// Truncated sheet XML keeps earlier rows.
let (wb, _) = read_xlsx(&minimal(r#"5 |
"#;
let (_, rep) = read_xlsx(&minimal(
diff --git a/crates/xlsx/src/tests/pivot_tests.rs b/crates/xlsx/src/tests/pivot_tests.rs
index 5d15d98..2c83b87 100644
--- a/crates/xlsx/src/tests/pivot_tests.rs
+++ b/crates/xlsx/src/tests/pivot_tests.rs
@@ -4,9 +4,9 @@ use std::collections::{HashMap, HashSet};
use std::io::Read;
use std::sync::Arc;
-use sheetcraft_core::date::serial_from_ymd;
-use sheetcraft_core::{CellRef, RangeRef, Value};
-use sheetcraft_model::*;
+use gridcraft_core::date::serial_from_ymd;
+use gridcraft_core::{CellRef, RangeRef, Value};
+use gridcraft_model::*;
use super::minimal;
use crate::xml;
@@ -20,7 +20,7 @@ fn rr(a: &str) -> RangeRef {
}
fn date(y: i64, m: i64, d: i64) -> f64 {
- serial_from_ymd(sheetcraft_core::DateSystem::D1900, y, m, d).unwrap()
+ serial_from_ymd(gridcraft_core::DateSystem::D1900, y, m, d).unwrap()
}
fn field(name: &str) -> PivotField {
diff --git a/crates/xlsx/src/tests/roundtrip.rs b/crates/xlsx/src/tests/roundtrip.rs
index d3c79cd..bfc83b9 100644
--- a/crates/xlsx/src/tests/roundtrip.rs
+++ b/crates/xlsx/src/tests/roundtrip.rs
@@ -4,8 +4,8 @@ use std::collections::BTreeMap;
use std::io::Read;
use std::sync::Arc;
-use sheetcraft_core::{CellError, CellRef, DateSystem, RangeRef, Value};
-use sheetcraft_model::*;
+use gridcraft_core::{CellError, CellRef, DateSystem, RangeRef, Value};
+use gridcraft_model::*;
use crate::{read_xlsx, write_xlsx};
diff --git a/crates/xlsx/src/theme.rs b/crates/xlsx/src/theme.rs
index ece5c6d..68e25bd 100644
--- a/crates/xlsx/src/theme.rs
+++ b/crates/xlsx/src/theme.rs
@@ -5,7 +5,7 @@
use std::fmt::Write as _;
-use sheetcraft_model::Theme;
+use gridcraft_model::Theme;
use crate::xml::{El, esc_attr};
diff --git a/crates/xlsx/src/write.rs b/crates/xlsx/src/write.rs
index 31c63d1..073dd36 100644
--- a/crates/xlsx/src/write.rs
+++ b/crates/xlsx/src/write.rs
@@ -3,9 +3,9 @@
use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _;
-use sheetcraft_core::RangeRef;
-use sheetcraft_formula::quote_sheet;
-use sheetcraft_model::{CalcMode, Style, Visibility, Workbook};
+use gridcraft_core::RangeRef;
+use gridcraft_formula::quote_sheet;
+use gridcraft_model::{CalcMode, Style, Visibility, Workbook};
use crate::IoError;
use crate::package::ZipOut;
@@ -113,7 +113,7 @@ impl Out {
/// `$A$1:$D$20` (or `$1:$3` / `$A:$C`).
pub fn abs_range(r: &RangeRef) -> String {
- let c = |col: u32| sheetcraft_core::col_to_letters(col);
+ let c = |col: u32| gridcraft_core::col_to_letters(col);
if r.is_full_rows() && !r.is_full_cols() {
return format!("${}:${}", r.start.row + 1, r.end.row + 1);
}
@@ -220,7 +220,7 @@ pub fn write_xlsx(wb: &Workbook) -> Result, IoError> {
}
let mut w = format!("{XML_DECL}");
- w.push_str(if wb.date_system == sheetcraft_core::DateSystem::D1904 { "" } else { " " });
+ w.push_str(if wb.date_system == gridcraft_core::DateSystem::D1904 { "" } else { " " });
if wb.protected_structure {
w.push_str("");
}
@@ -295,7 +295,7 @@ pub fn write_xlsx(wb: &Workbook) -> Result, IoError> {
core.push_str("");
out.part("docProps/core.xml", Some("application/vnd.openxmlformats-package.core-properties+xml"), core.into_bytes());
let mut app = String::from(XML_DECL);
- app.push_str("SheetCraft ");
+ app.push_str("GridCraft ");
if !p.company.is_empty() {
let _ = write!(app, "{} ", esc(&p.company));
}
diff --git a/docs/brand/LICENSE-brand.txt b/docs/brand/LICENSE-brand.txt
index 8b9f1f0..823591b 100644
--- a/docs/brand/LICENSE-brand.txt
+++ b/docs/brand/LICENSE-brand.txt
@@ -2,7 +2,7 @@ ArtCraft brand assets: license
The ArtCraft name, wordmark and mark (the "ArtCraft Marks"), including every file in this
directory in every color, size and format, are owned by the ArtCraft Team. They are trademarks
-and are NOT open source. They are not covered by SheetCraft's MIT OR Apache-2.0 license, and nothing in
+and are NOT open source. They are not covered by GridCraft's MIT OR Apache-2.0 license, and nothing in
that license, in NOTICE or in any other file of this repository grants any right to them.
Permission
@@ -11,7 +11,7 @@ The ArtCraft Team grants a limited, non-exclusive, non-transferable, revocable,
permission to use the ArtCraft Marks only:
1. as part of copies of this repository; and
- 2. within SheetCraft itself, its documentation and its official builds, to identify SheetCraft as made
+ 2. within GridCraft itself, its documentation and its official builds, to identify GridCraft as made
by the ArtCraft team and community and to link to getartcraft.com and its community.
Conditions
@@ -21,17 +21,17 @@ The permission holds only while every one of these is true:
- The marks are used exactly as supplied in this directory. Do not redraw, recolor, crop,
stretch, rotate, animate, outline or add effects to them, extract parts of them, or combine
them with any other name, mark or artwork.
- - They are used only in the context of SheetCraft, and only to say that the ArtCraft team makes it.
+ - They are used only in the context of GridCraft, and only to say that the ArtCraft team makes it.
They must never suggest that the ArtCraft Team made, sponsors, endorses, certifies or supports
any other product, service, fork, modified version, plug-in, organization or person.
- Neither the marks nor the ArtCraft name appear in the name, logo, icon, domain, social media
- handle, app-store listing, advertising or marketing of anything other than SheetCraft.
+ handle, app-store listing, advertising or marketing of anything other than GridCraft.
Forks and modified versions
-If you distribute or publish a modified version of SheetCraft, or any work derived from it, you must
+If you distribute or publish a modified version of GridCraft, or any work derived from it, you must
remove the ArtCraft Marks from it (or replace them with your own) and must not call it ArtCraft or
-present it as an ArtCraft product. You may say, in plain text, that your work is based on SheetCraft by
+present it as an ArtCraft product. You may say, in plain text, that your work is based on GridCraft by
the ArtCraft team. A fork kept only to propose changes back to this repository may keep the marks
unchanged.
diff --git a/docs/cli.md b/docs/cli.md
index 54518b0..2cf5985 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -1,29 +1,29 @@
-# sheetcraft-cli
+# gridcraft-cli
-Headless SheetCraft: inspect, convert, evaluate, script and serve MCP without a window. Errors go to stderr with a
+Headless GridCraft: inspect, convert, evaluate, script and serve MCP without a window. Errors go to stderr with a
non-zero exit code.
```sh
-cargo install --path apps/sheetcraft-cli # or: cargo run -p sheetcraft-cli --
+cargo install --path apps/gridcraft-cli # or: cargo run -p gridcraft-cli --
```
| Subcommand | Example |
|---|---|
-| `info [--json]` | `sheetcraft-cli info book.xlsx` — sheets, used ranges, cell counts, tables, charts, names. |
-| `convert [--sheet NAME]` | `sheetcraft-cli convert book.xlsx data.csv --sheet Sales` — output format from the extension: `.xlsx`, `.csv`, `.tsv`, `.json`, `.html`. |
-| `eval [--in FILE] [--sheet S] [--cell A1] [--json]` | `sheetcraft-cli eval '=PMT(5%/12,360,-300000)'`; `sheetcraft-cli eval '=SUM(B2:B9)' --in book.xlsx` |
-| `cat [--range R] [--sheet S] [--formulas] [--csv]` | `sheetcraft-cli cat book.xlsx --range A1:F20` — aligned table of displayed values. |
+| `info [--json]` | `gridcraft-cli info book.xlsx` — sheets, used ranges, cell counts, tables, charts, names. |
+| `convert [--sheet NAME]` | `gridcraft-cli convert book.xlsx data.csv --sheet Sales` — output format from the extension: `.xlsx`, `.csv`, `.tsv`, `.json`, `.html`. |
+| `eval [--in FILE] [--sheet S] [--cell A1] [--json]` | `gridcraft-cli eval '=PMT(5%/12,360,-300000)'`; `gridcraft-cli eval '=SUM(B2:B9)' --in book.xlsx` |
+| `cat [--range R] [--sheet S] [--formulas] [--csv]` | `gridcraft-cli cat book.xlsx --range A1:F20` — aligned table of displayed values. |
| `run …` | see below |
-| `commands [--json] [--search X]` | `sheetcraft-cli commands --search border` — every command with its parameter docs. |
-| `functions [--json] [--search X] [--category C]` | `sheetcraft-cli functions --search lookup` |
+| `commands [--json] [--search X]` | `gridcraft-cli commands --search border` — every command with its parameter docs. |
+| `functions [--json] [--search X] [--category C]` | `gridcraft-cli functions --search lookup` |
| `mcp [--connect PORT] [--in FILE \| --sample NAME]` | MCP server on stdio ([mcp.md](mcp.md)). |
-| `send [json]` | `sheetcraft-cli send 7979 engine.execute '{"command":"home.bold","params":{"range":"A1:C1"}}'` — one control-channel request to a running app (`sheetcraft --control 7979`). |
-| `version` | Prints `sheetcraft-cli `. |
+| `send [json]` | `gridcraft-cli send 7979 engine.execute '{"command":"home.bold","params":{"range":"A1:C1"}}'` — one control-channel request to a running app (`gridcraft --control 7979`). |
+| `version` | Prints `gridcraft-cli `. |
## `run`
```sh
-sheetcraft-cli run [--in FILE | --sample budget|sales|grades] \
+gridcraft-cli run [--in FILE | --sample budget|sales|grades] \
[--cmd 'id={json}']... [--script steps.jsonl] \
[--out FILE] [--print RANGE|used] [--sheet S] [--csv] [--formulas] [--quiet]
```
@@ -36,11 +36,11 @@ A `--cmd` is `id`, `id={json}` or `id {json}`. A script has one command per line
`{"command": "cell.set", "params": {...}}`; blank lines and `#` comments are skipped.
```sh
-sheetcraft-cli run --in book.xlsx \
+gridcraft-cli run --in book.xlsx \
--cmd 'home.bold={"range":"A1:C1"}' \
--cmd 'cell.set={"cell":"D2","input":"=B2*C2"}' \
--cmd 'edit.fillDown={"range":"D2:D20"}' \
--out book.xlsx --print A1:D5
```
-Command ids and parameters: `sheetcraft-cli commands`.
+Command ids and parameters: `gridcraft-cli commands`.
diff --git a/docs/control-protocol.md b/docs/control-protocol.md
index 5990c57..638ec21 100644
--- a/docs/control-protocol.md
+++ b/docs/control-protocol.md
@@ -1,9 +1,9 @@
-# SheetCraft control protocol
+# GridCraft control protocol
Start the desktop app with a control port:
```sh
-sheetcraft --control 7979 # or SHEETCRAFT_CONTROL_PORT=7979
+gridcraft --control 7979 # or GRIDCRAFT_CONTROL_PORT=7979
```
Then send newline-delimited JSON to `127.0.0.1:7979`. Every request line gets one reply line:
@@ -19,7 +19,7 @@ Errors come back as `{"id": …, "ok": false, "error": "…"}`.
| Method | Params | What it does |
|---|---|---|
-| `engine.execute` | `{command, params}` | Runs any engine command (see `engine.commands` or `sheetcraft-cli commands`) |
+| `engine.execute` | `{command, params}` | Runs any engine command (see `engine.commands` or `gridcraft-cli commands`) |
| `engine.commands` | | Every command: id, label, ribbon path, shortcut, params doc, enabled |
| `engine.journal` | | Commands run so far (replayable) |
| `document.inspect` | | Workbook summary: sheets, used ranges, tables, charts, names, selection, undo labels |
@@ -39,5 +39,5 @@ Errors come back as `{"id": …, "ok": false, "error": "…"}`.
Any other method name that is a command id (e.g. `home.bold`) runs that command.
-For a headless equivalent (no window), use `sheetcraft-cli mcp` or `sheetcraft-cli run`;
+For a headless equivalent (no window), use `gridcraft-cli mcp` or `gridcraft-cli run`;
see [`mcp.md`](mcp.md) and [`cli.md`](cli.md).
diff --git a/docs/mcp.md b/docs/mcp.md
index 53f6a74..3e6e7bc 100644
--- a/docs/mcp.md
+++ b/docs/mcp.md
@@ -1,6 +1,6 @@
-# SheetCraft MCP server
+# GridCraft MCP server
-`sheetcraft-cli mcp` exposes SheetCraft to AI agents over the [Model Context Protocol](https://modelcontextprotocol.io):
+`gridcraft-cli mcp` exposes GridCraft to AI agents over the [Model Context Protocol](https://modelcontextprotocol.io):
newline-delimited JSON-RPC 2.0 on stdin/stdout (protocol `2025-06-18`; `2025-03-26` and `2024-11-05` clients are
answered in their own version). Logs go to stderr.
@@ -8,15 +8,15 @@ Two backends:
| Mode | Command | What it drives |
|---|---|---|
-| Headless (default) | `sheetcraft-cli mcp [--in book.xlsx \| --sample budget]` | An in-process engine session — no window. |
-| Remote | `sheetcraft-cli mcp --connect 7979` | A running app started with `sheetcraft --control 7979` (TCP JSON lines on `127.0.0.1`). |
+| Headless (default) | `gridcraft-cli mcp [--in book.xlsx \| --sample budget]` | An in-process engine session — no window. |
+| Remote | `gridcraft-cli mcp --connect 7979` | A running app started with `gridcraft --control 7979` (TCP JSON lines on `127.0.0.1`). |
## Claude Code
```sh
-claude mcp add sheetcraft -- sheetcraft-cli mcp
+claude mcp add gridcraft -- gridcraft-cli mcp
# or drive the desktop app you are looking at:
-claude mcp add sheetcraft-app -- sheetcraft-cli mcp --connect 7979
+claude mcp add gridcraft-app -- gridcraft-cli mcp --connect 7979
```
JSON config (`.mcp.json`, Claude Desktop, other clients):
@@ -24,8 +24,8 @@ JSON config (`.mcp.json`, Claude Desktop, other clients):
```json
{
"mcpServers": {
- "sheetcraft": {
- "command": "sheetcraft-cli",
+ "gridcraft": {
+ "command": "gridcraft-cli",
"args": ["mcp"]
}
}
@@ -63,9 +63,9 @@ methods `-32601`.
## Resources
-- `sheetcraft://workbook` — `document.inspect` of the active workbook.
-- `sheetcraft://commands` — every command with its parameter docs.
-- `sheetcraft://functions` — every worksheet function.
+- `gridcraft://workbook` — `document.inspect` of the active workbook.
+- `gridcraft://commands` — every command with its parameter docs.
+- `gridcraft://functions` — every worksheet function.
`prompts/list` returns an empty list.
@@ -81,7 +81,7 @@ methods `-32601`.
## Embedding
-`sheetcraft-mcp` is a library: `Server::new(Box::new(Headless::new()))` or
+`gridcraft-mcp` is a library: `Server::new(Box::new(Headless::new()))` or
`Server::new(Box::new(Remote::connect(&control_addr("7979"))?))`, then `Server::serve(stdin, stdout)` or
`Server::handle_line(line) -> Option`. Implement the `Backend` trait (`call(method, params)`) to drive another
host.
diff --git a/docs/parity.md b/docs/parity.md
index 3a0b31f..0a88314 100644
--- a/docs/parity.md
+++ b/docs/parity.md
@@ -2,16 +2,16 @@
-Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when SheetCraft registers a command with the same id.
+Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when GridCraft registers a command with the same id.
-**Overall: 254 / 290 commands implemented (87.6%).**
+**Overall: 258 / 290 commands implemented (89.0%).**
| Tab | Implemented | Total | Parity |
|---|---|---|---|
| File | 10 | 11 | 91% |
| Home | 94 | 94 | 100% |
| Insert | 15 | 25 | 60% |
-| Draw | 0 | 4 | 0% |
+| Draw | 4 | 4 | 100% |
| Page Layout | 23 | 23 | 100% |
| Formulas | 23 | 23 | 100% |
| Data | 20 | 28 | 71% |
@@ -175,9 +175,9 @@ Missing:
- `insert.object` Object (Text)
- `insert.equation` Equation (Symbols)
-## Draw (0 / 4, 0%)
+## Draw (4 / 4, 100%)
-Missing:
+Implemented:
- `draw.pen` Pen (Tools)
- `draw.eraser` Eraser (Tools)
diff --git a/docs/release-playbook.md b/docs/release-playbook.md
index 95e9c7f..b563a24 100644
--- a/docs/release-playbook.md
+++ b/docs/release-playbook.md
@@ -5,5 +5,5 @@ The release playbook shared by every crafting app lives in craftrules:
(`storytold/craftrules`, `release/playbook.md`). It is the canonical copy; don't duplicate it
here. The signing runbook is next to it in `release/signing-setup.md`.
-SheetCraft's copy was taken from DesignCraft (`.github/workflows/release.yml`, `packaging/`,
-`cargo xtask version`). SheetCraft-specific details are in [releasing.md](releasing.md).
+GridCraft's copy was taken from DesignCraft (`.github/workflows/release.yml`, `packaging/`,
+`cargo xtask version`). GridCraft-specific details are in [releasing.md](releasing.md).
diff --git a/docs/releasing.md b/docs/releasing.md
index a1b2126..42c6ceb 100644
--- a/docs/releasing.md
+++ b/docs/releasing.md
@@ -1,40 +1,40 @@
-# Releasing SheetCraft
+# Releasing GridCraft
Every push to the `release` branch runs `.github/workflows/release.yml`. The workflow builds
signed installers for macOS, Windows, Linux, FreeBSD and the web, then creates or updates a
-**draft** GitHub Release named `SheetCraft v`. Nobody sees a draft until a maintainer
+**draft** GitHub Release named `GridCraft v`. Nobody sees a draft until a maintainer
publishes it.
-This is SheetCraft's implementation of the shared [release playbook](release-playbook.md)
+This is GridCraft's implementation of the shared [release playbook](release-playbook.md)
(craftrules `release/playbook.md`), copied from DesignCraft and renamed. User-facing names say
-**SheetCraft**. Files, binaries and ids stay lowercase
-(`sheetcraft---.`, `ai.storyteller.sheetcraft`).
+**GridCraft**. Files, binaries and ids stay lowercase
+(`gridcraft---.`, `ai.storyteller.gridcraft`).
| App-specific value | Where |
|---|---|
-| Display name `SheetCraft`, binaries `sheetcraft` + `sheetcraft-cli` | every script and workflow |
-| Bundle / app id `ai.storyteller.sheetcraft` | `packaging/macos/Info.plist.in`, `packaging/linux/*`, flatpak manifest |
-| WiX UpgradeCode `5511327B-F305-41BE-92D0-EB84944E7C4D` (fixed forever) | `packaging/windows/sheetcraft.wxs` |
-| File types `.xlsx` `.xlsm` `.csv` `.tsv` | Info.plist (`CFBundleDocumentTypes`), WiX (ProgIds/OpenWithProgids), `.desktop` `MimeType=`, `ai.storyteller.sheetcraft.mime.xml`, metainfo |
-| Web app dir `apps/sheetcraft-web` | `packaging/web/package.sh` |
+| Display name `GridCraft`, binaries `gridcraft` + `gridcraft-cli` | every script and workflow |
+| Bundle / app id `ai.storyteller.gridcraft` | `packaging/macos/Info.plist.in`, `packaging/linux/*`, flatpak manifest |
+| WiX UpgradeCode `5511327B-F305-41BE-92D0-EB84944E7C4D` (fixed forever) | `packaging/windows/gridcraft.wxs` |
+| File types `.xlsx` `.xlsm` `.csv` `.tsv` | Info.plist (`CFBundleDocumentTypes`), WiX (ProgIds/OpenWithProgids), `.desktop` `MimeType=`, `ai.storyteller.gridcraft.mime.xml`, metainfo |
+| Web app dir `apps/gridcraft-web` | `packaging/web/package.sh` |
| App colour `#1f9d55` (ink `#147a40`), **proposed** | `assets/app-icon/README.md`, metainfo `` |
## Prerequisites still owned by the app crates
The pipeline references crates that don't exist yet; the first release run fails until they do:
-- **`apps/sheetcraft`** (the GUI binary `sheetcraft`) and **`apps/sheetcraft-web`** (trunk
- site) must be workspace members. `apps/sheetcraft-cli` exists and supports `--version`.
-- **Windows resources:** move `packaging/windows/app-build.rs.in` to `apps/sheetcraft/build.rs`
+- **`apps/gridcraft`** (the GUI binary `gridcraft`) and **`apps/gridcraft-web`** (trunk
+ site) must be workspace members. `apps/gridcraft-cli` exists and supports `--version`.
+- **Windows resources:** move `packaging/windows/app-build.rs.in` to `apps/gridcraft/build.rs`
and add `winresource = "0.1"` under `[build-dependencies]` (instructions at the top of the
- file). `package.ps1` sets `SHEETCRAFT_REQUIRE_WINRES=1`, so a release build without the icon
- fails, and it checks that `sheetcraft.exe` is a GUI-subsystem binary, so `main.rs` needs
+ file). `package.ps1` sets `GRIDCRAFT_REQUIRE_WINRES=1`, so a release build without the icon
+ fails, and it checks that `gridcraft.exe` is a GUI-subsystem binary, so `main.rs` needs
`#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]`.
-- **Build info:** CI exports `SHEETCRAFT_BUILD_SHA` and `SHEETCRAFT_BUILD_DATE`; read them
- with `option_env!` (fallback: "dev build") for `--version` and *Help › About SheetCraft*.
-- **Runtime icon:** embed `assets/app-icon/sheetcraft-macos-512.png` (macOS Dock) and
- `hicolor/256x256/apps/ai.storyteller.sheetcraft.png` (Windows/Linux) as the window icon, and
- use `ai.storyteller.sheetcraft` as the Wayland app id / `StartupWMClass`.
+- **Build info:** CI exports `GRIDCRAFT_BUILD_SHA` and `GRIDCRAFT_BUILD_DATE`; read them
+ with `option_env!` (fallback: "dev build") for `--version` and *Help › About GridCraft*.
+- **Runtime icon:** embed `assets/app-icon/gridcraft-macos-512.png` (macOS Dock) and
+ `hicolor/256x256/apps/ai.storyteller.gridcraft.png` (Windows/Linux) as the window icon, and
+ use `ai.storyteller.gridcraft` as the Wayland app id / `StartupWMClass`.
- **`cargo xtask` alias:** the docs use `cargo xtask …`, which needs
`.cargo/config.toml` with `[alias] xtask = "run --package xtask --"`. Until it exists, use
`cargo run -p xtask -- …`. The workflows already call `cargo run -p xtask` directly.
@@ -52,7 +52,7 @@ The pipeline references crates that don't exist yet; the first release run fails
Commit the change (`Cargo.toml` + `Cargo.lock`) through the normal review flow.
2. **Merge `main` into `release`** (or fast-forward it) and push. The workflow starts by itself.
3. **Wait for the draft.** Notarization and the FreeBSD VM are the slow parts. The Releases
- page then has a draft `SheetCraft v0.2.0`, tagged `v0.2.0` on the pushed commit, with every
+ page then has a draft `GridCraft v0.2.0`, tagged `v0.2.0` on the pushed commit, with every
artifact and `SHA256SUMS.txt`. The notes are generated from the merged PRs.
4. **Check it.** Download an installer or two and read the job summaries. Any `::warning::`
there means a signing secret was missing and that artifact is unsigned.
@@ -74,51 +74,51 @@ that branch in the dialog.
| Platform | Artifacts | Built on |
|---|---|---|
-| macOS 11+ (universal: Apple silicon + Intel) | `sheetcraft--macos-universal.dmg`, `sheetcraft-cli--macos-universal.zip` | `macos-15` |
-| Windows 10+ x64 | `sheetcraft--windows-x64.msi`, `sheetcraft--windows-x64-portable.zip` | `windows-latest` |
-| Windows 10+ x86 (32-bit) | `sheetcraft--windows-x86.msi`, `sheetcraft--windows-x86-portable.zip` | `windows-latest` |
-| Windows 11 ARM64 | `sheetcraft--windows-arm64.msi`, `…-portable.zip` (cross-compiled, signed like x64) | `windows-latest`; installed and run on `windows-11-arm` by `windows-arm64.yml` |
-| Linux x86_64 | `sheetcraft--linux-x86_64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04` |
-| Linux aarch64 | `sheetcraft--linux-aarch64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04-arm` |
-| FreeBSD 14 x86_64 | `sheetcraft--freebsd-x86_64.tar.gz` | FreeBSD 14.3 VM (`freebsd.yml`, called by `release.yml`) |
-| Web | `sheetcraft-web-.zip` (static site; see [`packaging/web/README.md`](../packaging/web/README.md)) | `ubuntu-latest` |
+| macOS 11+ (universal: Apple silicon + Intel) | `gridcraft--macos-universal.dmg`, `gridcraft-cli--macos-universal.zip` | `macos-15` |
+| Windows 10+ x64 | `gridcraft--windows-x64.msi`, `gridcraft--windows-x64-portable.zip` | `windows-latest` |
+| Windows 10+ x86 (32-bit) | `gridcraft--windows-x86.msi`, `gridcraft--windows-x86-portable.zip` | `windows-latest` |
+| Windows 11 ARM64 | `gridcraft--windows-arm64.msi`, `…-portable.zip` (cross-compiled, signed like x64) | `windows-latest`; installed and run on `windows-11-arm` by `windows-arm64.yml` |
+| Linux x86_64 | `gridcraft--linux-x86_64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04` |
+| Linux aarch64 | `gridcraft--linux-aarch64.{AppImage,deb,rpm,tar.gz}` | `ubuntu-22.04-arm` |
+| FreeBSD 14 x86_64 | `gridcraft--freebsd-x86_64.tar.gz` | FreeBSD 14.3 VM (`freebsd.yml`, called by `release.yml`) |
+| Web | `gridcraft-web-.zip` (static site; see [`packaging/web/README.md`](../packaging/web/README.md)) | `ubuntu-latest` |
### macOS
`packaging/macos/package.sh` builds `aarch64-apple-darwin` and `x86_64-apple-darwin` with
-`MACOSX_DEPLOYMENT_TARGET=11.0`, joins them with `lipo`, and assembles `SheetCraft.app`:
+`MACOSX_DEPLOYMENT_TARGET=11.0`, joins them with `lipo`, and assembles `GridCraft.app`:
-- `Info.plist` comes from `Info.plist.in`: bundle id `ai.storyteller.sheetcraft`,
+- `Info.plist` comes from `Info.plist.in`: bundle id `ai.storyteller.gridcraft`,
`LSMinimumSystemVersion` 11.0, `NSHighResolutionCapable`, category productivity, and
`CFBundleDocumentTypes` for `.xlsx`, `.xlsm`, `.csv` and `.tsv` with rank **Alternate**, so
- SheetCraft is offered in *Open With* without taking over Numbers' (or another app's)
+ GridCraft is offered in *Open With* without taking over Numbers' (or another app's)
defaults. `.xlsm` gets an imported UTI in case macOS doesn't know it.
- **Signing** goes inside-out with the hardened runtime and a secure timestamp; no `--deep` on
the final signature; `entitlements.plist` is deliberately empty.
- **Notarization:** the app is zipped and sent with `xcrun notarytool submit --wait`, then
stapled. The DMG (with an `Applications` link) is signed, notarized and stapled too, and
checked with `codesign --verify --strict`, `stapler validate` and `spctl -a -vvv`.
-- **CLI:** the universal `sheetcraft-cli` is signed, zipped and notarized.
+- **CLI:** the universal `gridcraft-cli` is signed, zipped and notarized.
Locally, without certificates, the script signs ad-hoc (`codesign -s -`) and skips notarization:
```sh
packaging/macos/package.sh # universal; needs both rustup targets
packaging/macos/package.sh --arch aarch64 # quicker, host-only
-open dist/release/sheetcraft-*-macos-*.dmg
+open dist/release/gridcraft-*-macos-*.dmg
```
### Windows
`packaging/windows/package.ps1 -Arch x64|x86|arm64` builds with `-C target-feature=+crt-static`
-(no VC++ redistributable needed), checks both PE headers (machine matches `-Arch`; `sheetcraft.exe`
-is GUI, `sheetcraft-cli.exe` console), signs both executables, builds the MSI with WiX v5, signs
+(no VC++ redistributable needed), checks both PE headers (machine matches `-Arch`; `gridcraft.exe`
+is GUI, `gridcraft-cli.exe` console), signs both executables, builds the MSI with WiX v5, signs
the MSI, and zips a portable build.
-- `sheetcraft.wxs`: per-machine install into Program Files, advertised Start Menu shortcut, App
- Paths (Win+R `sheetcraft`). File types: ProgIds `SheetCraft.Workbook` (`.xlsx`, `.xlsm`) and
- `SheetCraft.TextData` (`.csv`, `.tsv`), listed under each extension's `OpenWithProgids` and
- in `Capabilities` + `RegisteredApplications`, so SheetCraft appears in *Open with* and
+- `gridcraft.wxs`: per-machine install into Program Files, advertised Start Menu shortcut, App
+ Paths (Win+R `gridcraft`). File types: ProgIds `GridCraft.Workbook` (`.xlsx`, `.xlsm`) and
+ `GridCraft.TextData` (`.csv`, `.tsv`), listed under each extension's `OpenWithProgids` and
+ in `Capabilities` + `RegisteredApplications`, so GridCraft appears in *Open with* and
*Settings › Default apps* but never silently becomes the default. The MSI version is the
numeric `X.Y.Z`; same-version upgrades let release candidates replace each other.
- **Signing:** `packaging/windows/sign.ps1` uses `signtool` with SHA-256 and an RFC 3161
@@ -131,13 +131,13 @@ Locally on Windows: `dotnet tool install -g wix --version 5.0.2`, then
### Linux
`packaging/linux/package.sh` stages one FHS tree (both binaries,
-`ai.storyteller.sheetcraft.desktop` with `MimeType=` for the four types, hicolor icons 16–512 px
+`ai.storyteller.gridcraft.desktop` with `MimeType=` for the four types, hicolor icons 16–512 px
+ scalable SVG, AppStream metainfo, shared-mime-info) and builds the AppImage (appimagetool),
`.deb` and `.rpm` (nfpm, `nfpm.yaml`) and `.tar.gz` from it. Built on Ubuntu 22.04, so the
binaries need glibc ≥ 2.35. X11/Wayland/xkbcommon/Vulkan/EGL are loaded at runtime; the
packages declare them (see `nfpm.yaml`).
-Flatpak: `packaging/linux/flatpak/ai.storyteller.sheetcraft.yml` builds from source and is ready
+Flatpak: `packaging/linux/flatpak/ai.storyteller.gridcraft.yml` builds from source and is ready
for a Flathub submission (freedesktop 25.08; Wayland + X11 fallback, `dri`, IPC; Documents and
Downloads; everything else through portals). CI validates it but doesn't build it; build by hand
with the commands in its header.
@@ -150,14 +150,14 @@ Locally (on Linux): install [nfpm](https://nfpm.goreleaser.com/install/), then
GitHub has no FreeBSD runners. `.github/workflows/freebsd.yml` boots a FreeBSD 14.3 VM
(`vmactions/freebsd-vm`). On `main` pushes and PRs it builds and tests the workspace; called
from `release.yml` with `package: true` it runs `packaging/freebsd/package.sh`, which builds the
-release binaries and writes `sheetcraft--freebsd-x86_64.tar.gz` (`bin/` + `share/`, extract
+release binaries and writes `gridcraft--freebsd-x86_64.tar.gz` (`bin/` + `share/`, extract
under `/usr/local`). The target dir lives outside the synced checkout so only `dist/release`
comes back from the VM. No signing (checksums are in `SHA256SUMS.txt`).
### Web
`packaging/web/package.sh` runs `trunk build --release --dist dist/web --public-url ./` in
-`apps/sheetcraft-web` and zips the site with sample `_headers` / `.htaccess` and the hosting guide
+`apps/gridcraft-web` and zips the site with sample `_headers` / `.htaccess` and the hosting guide
([`packaging/web/README.md`](../packaging/web/README.md)). Relative URLs only, so it works under
any path and in an iframe; the script fails on root-absolute URLs.
@@ -188,7 +188,7 @@ The exact commands (from craftrules `release/signing-setup.md`, steps 1–3). **
admin; not done by the setup agent.**
```sh
-REPO=storytold/sheetcraft
+REPO=storytold/gridcraft
RMTEAM_ID=19837241 # release-managers team; `gh api orgs/storytold/teams/release-managers --jq .id`
# 1a. release branch from the default branch head
@@ -232,8 +232,8 @@ the publisher.
## Icons
-`assets/app-icon/sheetcraft.svg` is a **placeholder** (an original ledger-page drawing on
-SheetCraft green) until the owner makes SheetCraft's engraved creature portrait (craftrules
+`assets/app-icon/gridcraft.svg` is a **placeholder** (an original ledger-page drawing on
+GridCraft green) until the owner makes GridCraft's engraved creature portrait (craftrules
`standards/icon-design.md`). `packaging/icons.sh` regenerates the 1024 px PNG, the macOS 512 px
PNG, the `.icns` (iconutil), the `.ico` (`cargo xtask ico`) and the hicolor PNGs from it; it
needs `resvg`. The outputs are committed, so packaging never needs those tools. Every file has a
diff --git a/packaging/env.sh b/packaging/env.sh
index e9877c1..bacb167 100644
--- a/packaging/env.sh
+++ b/packaging/env.sh
@@ -3,10 +3,10 @@
#
# Exports:
# ROOT workspace root
-# VERSION [workspace.package] version from Cargo.toml (override: SHEETCRAFT_VERSION)
+# VERSION [workspace.package] version from Cargo.toml (override: GRIDCRAFT_VERSION)
# DIST output directory for release artifacts (default: $ROOT/dist/release)
-# SHEETCRAFT_BUILD_SHA git commit baked into the binaries (see crates/engine/src/build_info.rs)
-# SHEETCRAFT_BUILD_DATE UTC build date, YYYY-MM-DD
+# GRIDCRAFT_BUILD_SHA git commit baked into the binaries (see crates/engine/src/build_info.rs)
+# GRIDCRAFT_BUILD_DATE UTC build date, YYYY-MM-DD
# CARGO_TARGET_DIR cargo's target dir (default: $ROOT/target)
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@@ -21,7 +21,7 @@ workspace_version() {
' "$ROOT/Cargo.toml"
}
-VERSION="${SHEETCRAFT_VERSION:-$(workspace_version)}"
+VERSION="${GRIDCRAFT_VERSION:-$(workspace_version)}"
if [ -z "$VERSION" ]; then
echo "error: could not read [workspace.package] version from $ROOT/Cargo.toml" >&2
exit 1
@@ -32,11 +32,11 @@ DIST="${DIST:-$ROOT/dist/release}"
mkdir -p "$DIST"
export DIST
-if [ -z "${SHEETCRAFT_BUILD_SHA:-}" ]; then
- SHEETCRAFT_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)"
+if [ -z "${GRIDCRAFT_BUILD_SHA:-}" ]; then
+ GRIDCRAFT_BUILD_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || true)"
fi
-export SHEETCRAFT_BUILD_SHA
-export SHEETCRAFT_BUILD_DATE="${SHEETCRAFT_BUILD_DATE:-$(date -u +%Y-%m-%d)}"
+export GRIDCRAFT_BUILD_SHA
+export GRIDCRAFT_BUILD_DATE="${GRIDCRAFT_BUILD_DATE:-$(date -u +%Y-%m-%d)}"
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$ROOT/target}"
# Emit a GitHub Actions warning (plain stderr outside Actions).
diff --git a/packaging/freebsd/package.sh b/packaging/freebsd/package.sh
index 76aeba7..e4ee9da 100755
--- a/packaging/freebsd/package.sh
+++ b/packaging/freebsd/package.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
-# Build and package SheetCraft for FreeBSD ( is x86_64 or aarch64):
+# Build and package GridCraft for FreeBSD ( is x86_64 or aarch64):
#
-# $DIST/sheetcraft--freebsd-.tar.gz bin/ + share/ (desktop entry, icons,
+# $DIST/gridcraft--freebsd-.tar.gz bin/ + share/ (desktop entry, icons,
# AppStream, MIME), extract under /usr/local
#
# Usage: packaging/freebsd/package.sh [--skip-build]
@@ -12,7 +12,7 @@
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
-APP_ID=ai.storyteller.sheetcraft
+APP_ID=ai.storyteller.gridcraft
LINUX="$ROOT/packaging/linux"
SKIP_BUILD=0
@@ -30,12 +30,12 @@ case "$ARCH" in
arm64 | aarch64) ARCH=aarch64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
esac
-BASENAME="sheetcraft-$VERSION-freebsd-$ARCH"
+BASENAME="gridcraft-$VERSION-freebsd-$ARCH"
-echo "==> SheetCraft $VERSION for FreeBSD $ARCH"
+echo "==> GridCraft $VERSION for FreeBSD $ARCH"
if [ "$SKIP_BUILD" = 0 ]; then
- (cd "$ROOT" && cargo build --release --locked -p sheetcraft -p sheetcraft-cli)
+ (cd "$ROOT" && cargo build --release --locked -p gridcraft -p gridcraft-cli)
fi
BIN="$CARGO_TARGET_DIR/release"
WORK="$CARGO_TARGET_DIR/freebsd-package"
@@ -43,19 +43,19 @@ TREE="$WORK/$BASENAME"
rm -rf "$WORK"
install -d "$TREE/bin" "$TREE/share/applications" "$TREE/share/metainfo" "$TREE/share/mime/packages" \
- "$TREE/share/icons" "$TREE/share/doc/sheetcraft"
-install -m 755 "$BIN/sheetcraft" "$BIN/sheetcraft-cli" "$TREE/bin/"
-strip "$TREE/bin/sheetcraft" "$TREE/bin/sheetcraft-cli" 2>/dev/null || true
+ "$TREE/share/icons" "$TREE/share/doc/gridcraft"
+install -m 755 "$BIN/gridcraft" "$BIN/gridcraft-cli" "$TREE/bin/"
+strip "$TREE/bin/gridcraft" "$TREE/bin/gridcraft-cli" 2>/dev/null || true
install -m 644 "$LINUX/$APP_ID.desktop" "$TREE/share/applications/$APP_ID.desktop"
install -m 644 "$LINUX/$APP_ID.mime.xml" "$TREE/share/mime/packages/$APP_ID.xml"
-sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$SHEETCRAFT_BUILD_DATE/g" \
+sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$GRIDCRAFT_BUILD_DATE/g" \
"$LINUX/$APP_ID.metainfo.xml.in" >"$TREE/share/metainfo/$APP_ID.metainfo.xml"
cp -R "$ROOT/assets/app-icon/hicolor" "$TREE/share/icons/"
-copy_docs "$TREE/share/doc/sheetcraft"
+copy_docs "$TREE/share/doc/gridcraft"
tar -C "$WORK" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
echo "wrote $DIST/$BASENAME.tar.gz"
-"$TREE/bin/sheetcraft-cli" --version
+"$TREE/bin/gridcraft-cli" --version
echo "==> done"
ls -lh "$DIST/$BASENAME.tar.gz"
diff --git a/packaging/icons.sh b/packaging/icons.sh
index d37a88e..2adcb9a 100755
--- a/packaging/icons.sh
+++ b/packaging/icons.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Regenerate every app icon from assets/app-icon/sheetcraft.svg.
+# Regenerate every app icon from assets/app-icon/gridcraft.svg.
#
# Needs: resvg (brew install resvg / cargo install resvg). On macOS, iconutil also writes the
# .icns. The outputs are committed, so builds and packaging never need these tools.
@@ -8,8 +8,8 @@
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIR="$ROOT/assets/app-icon"
-SVG="$DIR/sheetcraft.svg"
-ID="ai.storyteller.sheetcraft"
+SVG="$DIR/gridcraft.svg"
+ID="ai.storyteller.gridcraft"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
@@ -23,9 +23,9 @@ grep -q 'viewBox="-62.136' "$MAC" || { echo "error: expected viewBox=\"0 0 512 5
render() { resvg -w "$2" -h "$2" "$1" "$3" /dev/null; then
- SET="$TMP/sheetcraft.iconset"
+ SET="$TMP/gridcraft.iconset"
mkdir -p "$SET"
for s in 16 32 128 256 512; do
render "$MAC" "$s" "$SET/icon_${s}x${s}.png"
render "$MAC" $((s * 2)) "$SET/icon_${s}x${s}@2x.png"
done
- iconutil -c icns -o "$DIR/sheetcraft.icns" "$SET"
+ iconutil -c icns -o "$DIR/gridcraft.icns" "$SET"
else
- echo "warning: iconutil not found (macOS only); sheetcraft.icns not regenerated" >&2
+ echo "warning: iconutil not found (macOS only); gridcraft.icns not regenerated" >&2
fi
echo "icons written to $DIR"
diff --git a/packaging/linux/ai.storyteller.sheetcraft.desktop b/packaging/linux/ai.storyteller.gridcraft.desktop
similarity index 76%
rename from packaging/linux/ai.storyteller.sheetcraft.desktop
rename to packaging/linux/ai.storyteller.gridcraft.desktop
index 1654594..b2dc840 100644
--- a/packaging/linux/ai.storyteller.sheetcraft.desktop
+++ b/packaging/linux/ai.storyteller.gridcraft.desktop
@@ -1,14 +1,14 @@
[Desktop Entry]
Type=Application
-Name=SheetCraft
+Name=GridCraft
GenericName=Spreadsheet
Comment=Calculate, analyse and chart data in workbooks
-Exec=sheetcraft %F
-TryExec=sheetcraft
-Icon=ai.storyteller.sheetcraft
+Exec=gridcraft %F
+TryExec=gridcraft
+Icon=ai.storyteller.gridcraft
Terminal=false
StartupNotify=true
-StartupWMClass=ai.storyteller.sheetcraft
+StartupWMClass=ai.storyteller.gridcraft
Categories=Office;Spreadsheet;
MimeType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.ms-excel.sheet.macroEnabled.12;text/csv;text/tab-separated-values;
Keywords=spreadsheet;workbook;sheet;table;formula;chart;pivot;xlsx;csv;tsv;excel;
diff --git a/packaging/linux/ai.storyteller.sheetcraft.metainfo.xml.in b/packaging/linux/ai.storyteller.gridcraft.metainfo.xml.in
similarity index 71%
rename from packaging/linux/ai.storyteller.sheetcraft.metainfo.xml.in
rename to packaging/linux/ai.storyteller.gridcraft.metainfo.xml.in
index f987b11..09bbd08 100644
--- a/packaging/linux/ai.storyteller.sheetcraft.metainfo.xml.in
+++ b/packaging/linux/ai.storyteller.gridcraft.metainfo.xml.in
@@ -2,31 +2,31 @@
- ai.storyteller.sheetcraft
+ ai.storyteller.gridcraft
CC0-1.0
MIT OR Apache-2.0
- SheetCraft
+ GridCraft
Calculate, analyse and chart data in workbooks
Learning Machines LLC
- SheetCraft is an open-source, native spreadsheet. It opens and saves .xlsx workbooks and
+ GridCraft is an open-source, native spreadsheet. It opens and saves .xlsx workbooks and
CSV/TSV files, evaluates hundreds of worksheet functions, and formats, sorts, filters and
charts your data.
- Every action is a command that the included sheetcraft-cli tool and AI agents (over MCP)
+ Every action is a command that the included gridcraft-cli tool and AI agents (over MCP)
can run too, so workbooks can be built and checked without opening a window.
- ai.storyteller.sheetcraft.desktop
- ai.storyteller.sheetcraft
+ ai.storyteller.gridcraft.desktop
+ ai.storyteller.gridcraft
- https://getartcraft.com/apps/sheetcraft
- https://github.com/storytold/sheetcraft/issues
- https://github.com/storytold/sheetcraft
+ https://getartcraft.com/apps/gridcraft
+ https://github.com/storytold/gridcraft/issues
+ https://github.com/storytold/gridcraft
https://discord.gg/artcraft
Office
@@ -39,7 +39,7 @@
xlsx
csv
-
+
#a7e3bf
#147a40
@@ -52,8 +52,8 @@
pointing
- sheetcraft
- sheetcraft-cli
+ gridcraft
+ gridcraft-cli
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-excel.sheet.macroEnabled.12
text/csv
diff --git a/packaging/linux/ai.storyteller.sheetcraft.mime.xml b/packaging/linux/ai.storyteller.gridcraft.mime.xml
similarity index 91%
rename from packaging/linux/ai.storyteller.sheetcraft.mime.xml
rename to packaging/linux/ai.storyteller.gridcraft.mime.xml
index ca65dab..9ff36e5 100644
--- a/packaging/linux/ai.storyteller.sheetcraft.mime.xml
+++ b/packaging/linux/ai.storyteller.gridcraft.mime.xml
@@ -1,5 +1,5 @@
-
diff --git a/packaging/linux/flatpak/ai.storyteller.sheetcraft.yml b/packaging/linux/flatpak/ai.storyteller.gridcraft.yml
similarity index 66%
rename from packaging/linux/flatpak/ai.storyteller.sheetcraft.yml
rename to packaging/linux/flatpak/ai.storyteller.gridcraft.yml
index abfc1f2..3d7e8f8 100644
--- a/packaging/linux/flatpak/ai.storyteller.sheetcraft.yml
+++ b/packaging/linux/flatpak/ai.storyteller.gridcraft.yml
@@ -1,20 +1,20 @@
-# Flatpak manifest for SheetCraft, ready for a Flathub submission.
+# Flatpak manifest for GridCraft, ready for a Flathub submission.
#
# Flatpak builds run offline, so the crate sources must be vendored first. From the repo root:
#
# curl -fsSLO https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py
# python3 flatpak-cargo-generator.py Cargo.lock -o packaging/linux/flatpak/cargo-sources.json
-# flatpak-builder --user --install --force-clean build-flatpak packaging/linux/flatpak/ai.storyteller.sheetcraft.yml
-# flatpak run ai.storyteller.sheetcraft
+# flatpak-builder --user --install --force-clean build-flatpak packaging/linux/flatpak/ai.storyteller.gridcraft.yml
+# flatpak run ai.storyteller.gridcraft
#
# For Flathub, replace the `dir` source with a `git` source pinned to a release tag and commit.
-id: ai.storyteller.sheetcraft
+id: ai.storyteller.gridcraft
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
-command: sheetcraft
+command: gridcraft
finish-args:
- --share=ipc
- --socket=wayland
@@ -28,24 +28,24 @@ finish-args:
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
modules:
- - name: sheetcraft
+ - name: gridcraft
buildsystem: simple
build-options:
env:
- CARGO_HOME: /run/build/sheetcraft/cargo
+ CARGO_HOME: /run/build/gridcraft/cargo
CARGO_NET_OFFLINE: 'true'
build-commands:
- - cargo --offline build --release --locked -p sheetcraft -p sheetcraft-cli
- - install -Dm755 target/release/sheetcraft /app/bin/sheetcraft
- - install -Dm755 target/release/sheetcraft-cli /app/bin/sheetcraft-cli
- - install -Dm644 packaging/linux/ai.storyteller.sheetcraft.desktop /app/share/applications/ai.storyteller.sheetcraft.desktop
- - install -Dm644 packaging/linux/ai.storyteller.sheetcraft.mime.xml /app/share/mime/packages/ai.storyteller.sheetcraft.xml
+ - cargo --offline build --release --locked -p gridcraft -p gridcraft-cli
+ - install -Dm755 target/release/gridcraft /app/bin/gridcraft
+ - install -Dm755 target/release/gridcraft-cli /app/bin/gridcraft-cli
+ - install -Dm644 packaging/linux/ai.storyteller.gridcraft.desktop /app/share/applications/ai.storyteller.gridcraft.desktop
+ - install -Dm644 packaging/linux/ai.storyteller.gridcraft.mime.xml /app/share/mime/packages/ai.storyteller.gridcraft.xml
- mkdir -p /app/share/icons && cp -R assets/app-icon/hicolor /app/share/icons/
- >-
version=$(awk '/^\[/{p=($0=="[workspace.package]");next} p&&$1=="version"{gsub(/[" ]/,"",$3);print $3;exit}' Cargo.toml);
install -d /app/share/metainfo;
sed -e "s/@VERSION@/$version/" -e "s/@DATE@/$(date -u +%Y-%m-%d)/"
- packaging/linux/ai.storyteller.sheetcraft.metainfo.xml.in > /app/share/metainfo/ai.storyteller.sheetcraft.metainfo.xml
+ packaging/linux/ai.storyteller.gridcraft.metainfo.xml.in > /app/share/metainfo/ai.storyteller.gridcraft.metainfo.xml
sources:
- type: dir
path: ../../..
diff --git a/packaging/linux/nfpm.yaml b/packaging/linux/nfpm.yaml
index a872592..5e8c3a1 100644
--- a/packaging/linux/nfpm.yaml
+++ b/packaging/linux/nfpm.yaml
@@ -3,7 +3,7 @@
# exports VERSION and NFPM_ARCH.
#
# nfpm package -f packaging/linux/nfpm.yaml -p deb -t dist/release/
-name: sheetcraft
+name: gridcraft
arch: ${NFPM_ARCH}
platform: linux
version: ${VERSION}
@@ -12,15 +12,15 @@ version_schema: semver
release: 1
section: misc
priority: optional
-maintainer: ${SHEETCRAFT_MAINTAINER}
+maintainer: ${GRIDCRAFT_MAINTAINER}
vendor: Learning Machines LLC
-homepage: https://getartcraft.com/apps/sheetcraft
+homepage: https://getartcraft.com/apps/gridcraft
license: MIT OR Apache-2.0
description: |-
Open-source, native spreadsheet that works the way Excel users expect.
Opens and saves .xlsx workbooks and CSV/TSV files; hundreds of worksheet
functions; formatting, sorting, filtering and charts.
- Includes sheetcraft-cli, a headless evaluator, converter and command runner.
+ Includes gridcraft-cli, a headless evaluator, converter and command runner.
# The binaries link only glibc and libgcc_s directly. Windowing (X11/Wayland, xkbcommon) and the
# GPU (Vulkan, EGL/GL) are loaded at runtime, so they are dependencies/recommendations here
@@ -55,11 +55,11 @@ overrides:
- xdg-desktop-portal
contents:
- - src: ${STAGE}/usr/bin/sheetcraft
- dst: /usr/bin/sheetcraft
+ - src: ${STAGE}/usr/bin/gridcraft
+ dst: /usr/bin/gridcraft
file_info: { mode: 0755 }
- - src: ${STAGE}/usr/bin/sheetcraft-cli
- dst: /usr/bin/sheetcraft-cli
+ - src: ${STAGE}/usr/bin/gridcraft-cli
+ dst: /usr/bin/gridcraft-cli
file_info: { mode: 0755 }
- src: ${STAGE}/usr/share/
dst: /usr/share/
diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh
index aa78358..47b5881 100755
--- a/packaging/linux/package.sh
+++ b/packaging/linux/package.sh
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
-# Build and package SheetCraft for Linux ( is x86_64 or aarch64):
+# Build and package GridCraft for Linux ( is x86_64 or aarch64):
#
-# $DIST/sheetcraft--linux-.AppImage any distro with glibc >= the build host's
-# $DIST/sheetcraft--linux-.deb Debian, Ubuntu, Mint, Pop!_OS, ...
-# $DIST/sheetcraft--linux-.rpm Fedora, openSUSE, RHEL, ...
-# $DIST/sheetcraft--linux-.tar.gz plain FHS-style tree (bin/, share/)
+# $DIST/gridcraft--linux-.AppImage any distro with glibc >= the build host's
+# $DIST/gridcraft--linux-.deb Debian, Ubuntu, Mint, Pop!_OS, ...
+# $DIST/gridcraft--linux-.rpm Fedora, openSUSE, RHEL, ...
+# $DIST/gridcraft--linux-.tar.gz plain FHS-style tree (bin/, share/)
#
# Usage: packaging/linux/package.sh [--skip-build] [--formats "appimage deb rpm tar"]
#
@@ -15,7 +15,7 @@ set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/linux"
-APP_ID=ai.storyteller.sheetcraft
+APP_ID=ai.storyteller.gridcraft
SKIP_BUILD=0
FORMATS="appimage deb rpm tar"
@@ -34,13 +34,13 @@ case "$ARCH" in
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
esac
-export SHEETCRAFT_MAINTAINER="${SHEETCRAFT_MAINTAINER:-SheetCraft maintainers }"
-BASENAME="sheetcraft-$VERSION-linux-$ARCH"
+export GRIDCRAFT_MAINTAINER="${GRIDCRAFT_MAINTAINER:-GridCraft maintainers }"
+BASENAME="gridcraft-$VERSION-linux-$ARCH"
-echo "==> SheetCraft $VERSION for Linux $ARCH ($FORMATS)"
+echo "==> GridCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
- (cd "$ROOT" && cargo build --release --locked -p sheetcraft -p sheetcraft-cli)
+ (cd "$ROOT" && cargo build --release --locked -p gridcraft -p gridcraft-cli)
fi
BIN="$CARGO_TARGET_DIR/release"
WORK="$CARGO_TARGET_DIR/linux-package"
@@ -48,18 +48,18 @@ STAGE="$WORK/root"
rm -rf "$WORK"
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
-install -Dm755 "$BIN/sheetcraft" "$STAGE/usr/bin/sheetcraft"
-install -Dm755 "$BIN/sheetcraft-cli" "$STAGE/usr/bin/sheetcraft-cli"
-strip "$STAGE/usr/bin/sheetcraft" "$STAGE/usr/bin/sheetcraft-cli" 2>/dev/null || true
+install -Dm755 "$BIN/gridcraft" "$STAGE/usr/bin/gridcraft"
+install -Dm755 "$BIN/gridcraft-cli" "$STAGE/usr/bin/gridcraft-cli"
+strip "$STAGE/usr/bin/gridcraft" "$STAGE/usr/bin/gridcraft-cli" 2>/dev/null || true
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
mkdir -p "$STAGE/usr/share/metainfo"
-sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$SHEETCRAFT_BUILD_DATE/g" \
+sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$GRIDCRAFT_BUILD_DATE/g" \
"$HERE/$APP_ID.metainfo.xml.in" >"$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
mkdir -p "$STAGE/usr/share/icons"
cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/usr/share/icons/"
-mkdir -p "$STAGE/usr/share/doc/sheetcraft"
-copy_docs "$STAGE/usr/share/doc/sheetcraft"
+mkdir -p "$STAGE/usr/share/doc/gridcraft"
+copy_docs "$STAGE/usr/share/doc/gridcraft"
if command -v desktop-file-validate >/dev/null; then
desktop-file-validate "$STAGE/usr/share/applications/$APP_ID.desktop"
@@ -92,10 +92,10 @@ fi
# ---- AppImage -----------------------------------------------------------------------------------
if has appimage; then
- APPDIR="$WORK/SheetCraft.AppDir"
+ APPDIR="$WORK/GridCraft.AppDir"
cp -R "$STAGE" "$APPDIR"
mv "$APPDIR/usr/share/doc" "$WORK/doc-unused"
- ln -s usr/bin/sheetcraft "$APPDIR/AppRun"
+ ln -s usr/bin/gridcraft "$APPDIR/AppRun"
cp "$HERE/$APP_ID.desktop" "$APPDIR/$APP_ID.desktop"
cp "$ROOT/assets/app-icon/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/$APP_ID.png"
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
@@ -115,6 +115,6 @@ if has appimage; then
echo "wrote $OUT"
fi
-"$STAGE/usr/bin/sheetcraft-cli" --version
+"$STAGE/usr/bin/gridcraft-cli" --version
echo "==> done"
ls -lh "$DIST"
diff --git a/packaging/macos/Info.plist.in b/packaging/macos/Info.plist.in
index c055716..9379279 100644
--- a/packaging/macos/Info.plist.in
+++ b/packaging/macos/Info.plist.in
@@ -7,13 +7,13 @@
CFBundleDevelopmentRegion
en
CFBundleDisplayName
- SheetCraft
+ GridCraft
CFBundleName
- SheetCraft
+ GridCraft
CFBundleExecutable
- SheetCraft
+ GridCraft
CFBundleIdentifier
- ai.storyteller.sheetcraft
+ ai.storyteller.gridcraft
CFBundleInfoDictionaryVersion
6.0
CFBundlePackageType
@@ -24,12 +24,12 @@
@SHORT_VERSION@
CFBundleVersion
@SHORT_VERSION@
- SheetCraftVersion
+ GridCraftVersion
@VERSION@
- SheetCraftBuildCommit
+ GridCraftBuildCommit
@BUILD_SHA@
CFBundleIconFile
- SheetCraft
+ GridCraft
LSApplicationCategoryType
public.app-category.productivity
CFBundleDocumentTypes
diff --git a/packaging/macos/entitlements.plist b/packaging/macos/entitlements.plist
index 64bc36c..8558c5d 100644
--- a/packaging/macos/entitlements.plist
+++ b/packaging/macos/entitlements.plist
@@ -1,6 +1,6 @@
-
diff --git a/packaging/macos/import-cert.sh b/packaging/macos/import-cert.sh
index 22aa632..41d9538 100755
--- a/packaging/macos/import-cert.sh
+++ b/packaging/macos/import-cert.sh
@@ -18,8 +18,8 @@ if [ -z "${APPLE_CERTIFICATE:-}" ]; then
fi
TMP="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
-KEYCHAIN="$TMP/sheetcraft-signing.keychain-db"
-CERT="$TMP/sheetcraft-signing.p12"
+KEYCHAIN="$TMP/gridcraft-signing.keychain-db"
+CERT="$TMP/gridcraft-signing.p12"
KC_PASS="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 24)}"
trap 'rm -f "$CERT"' EXIT
diff --git a/packaging/macos/package.sh b/packaging/macos/package.sh
index 9ade6b7..976db94 100755
--- a/packaging/macos/package.sh
+++ b/packaging/macos/package.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Build, sign and (optionally) notarize the macOS release artifacts:
#
-# $DIST/sheetcraft--macos-.dmg SheetCraft.app on a drag-to-Applications DMG
-# $DIST/sheetcraft-cli--macos-.zip the headless CLI
+# $DIST/gridcraft--macos-.dmg GridCraft.app on a drag-to-Applications DMG
+# $DIST/gridcraft-cli--macos-.zip the headless CLI
#
# Usage: packaging/macos/package.sh [--arch universal|aarch64|x86_64] [--skip-build]
#
@@ -39,9 +39,9 @@ export MACOSX_DEPLOYMENT_TARGET=11.0
IDENTITY="${MACOS_SIGN_IDENTITY:--}"
SHORT_VERSION="${VERSION%%-*}"
WORK="$CARGO_TARGET_DIR/macos-package"
-APP="$WORK/SheetCraft.app"
-DMG="$DIST/sheetcraft-$VERSION-macos-$ARCH.dmg"
-CLI_ZIP="$DIST/sheetcraft-cli-$VERSION-macos-$ARCH.zip"
+APP="$WORK/GridCraft.app"
+DMG="$DIST/gridcraft-$VERSION-macos-$ARCH.dmg"
+CLI_ZIP="$DIST/gridcraft-cli-$VERSION-macos-$ARCH.zip"
NOTARIZE=0
if [ "$IDENTITY" = "-" ]; then
@@ -52,18 +52,18 @@ else
warn "macOS: APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID incomplete; signed but not notarized"
fi
-echo "==> SheetCraft $VERSION for macOS ($ARCH), identity: $IDENTITY, notarize: $NOTARIZE"
+echo "==> GridCraft $VERSION for macOS ($ARCH), identity: $IDENTITY, notarize: $NOTARIZE"
# ---- build -------------------------------------------------------------------------------------
if [ "$SKIP_BUILD" = 0 ]; then
args=()
for t in "${TARGETS[@]}"; do args+=(--target "$t"); done
- (cd "$ROOT" && cargo build --release --locked -p sheetcraft -p sheetcraft-cli "${args[@]}")
+ (cd "$ROOT" && cargo build --release --locked -p gridcraft -p gridcraft-cli "${args[@]}")
fi
rm -rf "$WORK"
mkdir -p "$WORK/bin"
-for bin in sheetcraft sheetcraft-cli; do
+for bin in gridcraft gridcraft-cli; do
inputs=()
for t in "${TARGETS[@]}"; do inputs+=("$CARGO_TARGET_DIR/$t/release/$bin"); done
lipo -create -output "$WORK/bin/$bin" "${inputs[@]}"
@@ -97,29 +97,29 @@ notarize() {
fi
}
-# ---- SheetCraft.app ----------------------------------------------------------------------------
+# ---- GridCraft.app ----------------------------------------------------------------------------
echo "==> assembling $APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
# Executable and icon carry the display name (CFBundleExecutable / CFBundleIconFile).
-cp "$WORK/bin/sheetcraft" "$APP/Contents/MacOS/SheetCraft"
-cp "$ROOT/assets/app-icon/sheetcraft.icns" "$APP/Contents/Resources/SheetCraft.icns"
+cp "$WORK/bin/gridcraft" "$APP/Contents/MacOS/GridCraft"
+cp "$ROOT/assets/app-icon/gridcraft.icns" "$APP/Contents/Resources/GridCraft.icns"
# Licences of the embedded craft-fonts fonts (only when built with CRAFT_FONTS_DIR).
copy_font_licences "$APP/Contents/Resources"
sed -e "s/@VERSION@/$VERSION/g" -e "s/@SHORT_VERSION@/$SHORT_VERSION/g" \
- -e "s/@BUILD_SHA@/${SHEETCRAFT_BUILD_SHA:-unknown}/g" \
+ -e "s/@BUILD_SHA@/${GRIDCRAFT_BUILD_SHA:-unknown}/g" \
"$HERE/Info.plist.in" >"$APP/Contents/Info.plist"
plutil -lint "$APP/Contents/Info.plist"
printf 'APPL????' >"$APP/Contents/PkgInfo"
# Sign inside-out: nested code first, then the bundle itself (no --deep on the final signature).
# Today the only nested code is the main executable; frameworks/helpers would be signed here too.
-sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/MacOS/SheetCraft"
+sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP/Contents/MacOS/GridCraft"
sign --options runtime --entitlements "$HERE/entitlements.plist" "$APP"
codesign --verify --strict --deep --verbose=2 "$APP"
if [ "$NOTARIZE" = 1 ]; then
- ditto -c -k --keepParent "$APP" "$WORK/SheetCraft-notarize.zip"
- notarize "$WORK/SheetCraft-notarize.zip"
+ ditto -c -k --keepParent "$APP" "$WORK/GridCraft-notarize.zip"
+ notarize "$WORK/GridCraft-notarize.zip"
xcrun stapler staple "$APP"
xcrun stapler validate "$APP"
spctl --assess --type execute -vvv "$APP"
@@ -129,12 +129,12 @@ fi
echo "==> building $DMG"
STAGE="$WORK/dmg"
mkdir -p "$STAGE"
-ditto "$APP" "$STAGE/SheetCraft.app"
+ditto "$APP" "$STAGE/GridCraft.app"
ln -s /Applications "$STAGE/Applications"
rm -f "$DMG" "$WORK/raw.dmg"
# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`,
# which is flaky on CI runners ("Resource busy") and hangs in sandboxed sessions.
-hdiutil makehybrid -hfs -hfs-volume-name "SheetCraft $VERSION" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
+hdiutil makehybrid -hfs -hfs-volume-name "GridCraft $VERSION" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG"
rm -f "$WORK/raw.dmg"
sign "$DMG"
@@ -148,17 +148,17 @@ fi
# ---- CLI ---------------------------------------------------------------------------------------
echo "==> building $CLI_ZIP"
-CLI_DIR="$WORK/sheetcraft-cli-$VERSION-macos-$ARCH"
+CLI_DIR="$WORK/gridcraft-cli-$VERSION-macos-$ARCH"
mkdir -p "$CLI_DIR"
-cp "$WORK/bin/sheetcraft-cli" "$CLI_DIR/"
+cp "$WORK/bin/gridcraft-cli" "$CLI_DIR/"
copy_docs "$CLI_DIR"
-sign --options runtime "$CLI_DIR/sheetcraft-cli"
-codesign --verify --strict --verbose=2 "$CLI_DIR/sheetcraft-cli"
+sign --options runtime "$CLI_DIR/gridcraft-cli"
+codesign --verify --strict --verbose=2 "$CLI_DIR/gridcraft-cli"
rm -f "$CLI_ZIP"
ditto -c -k --keepParent "$CLI_DIR" "$CLI_ZIP"
# A bare Mach-O can't carry a stapled ticket; Gatekeeper looks the notarization up online.
if [ "$NOTARIZE" = 1 ]; then notarize "$CLI_ZIP"; fi
-"$WORK/bin/sheetcraft-cli" --version
+"$WORK/bin/gridcraft-cli" --version
echo "==> done"
ls -lh "$DMG" "$CLI_ZIP"
diff --git a/packaging/web/README.md b/packaging/web/README.md
index 0df4e40..90be0ec 100644
--- a/packaging/web/README.md
+++ b/packaging/web/README.md
@@ -1,22 +1,22 @@
-# Hosting SheetCraft for the web
+# Hosting GridCraft for the web
-`sheetcraft-web-.zip` (from the GitHub release, or `packaging/web/package.sh`) holds a
-static site in `sheetcraft-web-/`:
+`gridcraft-web-.zip` (from the GitHub release, or `packaging/web/package.sh`) holds a
+static site in `gridcraft-web-/`:
| File | What it is |
|---|---|
| `index.html` | The page. It loads everything through relative URLs. |
-| `sheetcraft-web-.js` | wasm-bindgen glue (generated, ES module) |
-| `sheetcraft-web-_bg.wasm` | The app (compresses to roughly a third with gzip/Brotli) |
+| `gridcraft-web-.js` | wasm-bindgen glue (generated, ES module) |
+| `gridcraft-web-_bg.wasm` | The app (compresses to roughly a third with gzip/Brotli) |
| `_headers`, `.htaccess` | Sample header rules for Netlify/Cloudflare Pages and Apache |
There is no server-side code. Upload the folder's contents anywhere that serves static files.
## Any path works
-All URLs in `index.html` are relative (`public_url = "./"` in `apps/sheetcraft-web/Trunk.toml`),
+All URLs in `index.html` are relative (`public_url = "./"` in `apps/gridcraft-web/Trunk.toml`),
so the site works at a domain root (`https://example.com/`), under a prefix
-(`https://example.com/tools/sheetcraft/`) and from a CDN bucket. The asset names carry a content
+(`https://example.com/tools/gridcraft/`) and from a CDN bucket. The asset names carry a content
hash, so they can be cached forever. Only `index.html` needs revalidation.
## Required server settings
@@ -31,7 +31,7 @@ hash, so they can be cached forever. Only `index.html` needs revalidation.
`.js` files, and `no-cache` on `index.html`.
- **HTTPS:** WebGPU (and the clipboard) only work in a secure context, which means `https://`
or `http://localhost`. Over plain HTTP elsewhere, the app falls back to WebGL2.
-- **No special isolation headers:** SheetCraft doesn't use `SharedArrayBuffer`, so it doesn't
+- **No special isolation headers:** GridCraft doesn't use `SharedArrayBuffer`, so it doesn't
need `Cross-Origin-Opener-Policy` or `Cross-Origin-Embedder-Policy`. If your site already sends
COEP `require-corp`, also send `Cross-Origin-Resource-Policy: same-origin` (or `cross-origin`
when the files live on a CDN) on the app's files.
@@ -39,7 +39,7 @@ hash, so they can be cached forever. Only `index.html` needs revalidation.
nginx example:
```nginx
-location /sheetcraft/ {
+location /gridcraft/ {
types { application/wasm wasm; text/javascript js; text/html html; }
gzip on;
gzip_types application/wasm text/javascript text/html;
@@ -54,8 +54,8 @@ Local test: `python3 -m http.server 8765` inside the folder, then open http://lo
```html
|