ranger-compiler 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; ==============================================================================
|
|
4
|
+
; evg_agent — the tool surface, before there is a protocol around it
|
|
5
|
+
; ==============================================================================
|
|
6
|
+
;
|
|
7
|
+
; Four verbs over an EVG document. An agent with a shell and a way to read a
|
|
8
|
+
; file already has everything it needs to edit one; MCP, a widget and an editor
|
|
9
|
+
; panel are transports over exactly these, added when something needs them.
|
|
10
|
+
;
|
|
11
|
+
; npm run agent -- outline <doc.evg.json> [--depth=N] [--at=PATH]
|
|
12
|
+
; npm run agent -- query <doc.evg.json> <.class | #id | tag | path>
|
|
13
|
+
; npm run agent -- patch <doc.evg.json> <ops.json> [--out=FILE]
|
|
14
|
+
; npm run agent -- measure <doc.evg.json> [--width=N] [--height=N] [--boxes]
|
|
15
|
+
;
|
|
16
|
+
; --- WHY OUTLINE IS NOT JSON --------------------------------------------------
|
|
17
|
+
;
|
|
18
|
+
; `outline` prints an indented text tree and everything else prints JSON, which
|
|
19
|
+
; looks inconsistent until you count tokens. An outline exists to be READ by a
|
|
20
|
+
; model deciding what to change, and one line per node — path, tag, class, the
|
|
21
|
+
; few properties it actually sets — costs a fraction of the same tree as JSON
|
|
22
|
+
; while carrying the addresses the other verbs take. The others are parsed, not
|
|
23
|
+
; read, so they are JSON.
|
|
24
|
+
;
|
|
25
|
+
; --- WHY MEASURE IS THE INTERESTING ONE ---------------------------------------
|
|
26
|
+
;
|
|
27
|
+
; The rest of this is bookkeeping. `measure` is the verb that does something a
|
|
28
|
+
; model cannot do for itself: it lays the document out and answers, in numbers,
|
|
29
|
+
; whether the result is wrong — text past its box, siblings on top of each
|
|
30
|
+
; other, a node off the page. A screenshot asks the model to SEE that, badly
|
|
31
|
+
; and expensively. This reads it off the boxes layout already computed.
|
|
32
|
+
; ==============================================================================
|
|
33
|
+
|
|
34
|
+
Import "../EVGElement.rgr"
|
|
35
|
+
Import "../EVGInspect.rgr"
|
|
36
|
+
Import "../EVGLayout.rgr"
|
|
37
|
+
Import "../EVGMeasure.rgr"
|
|
38
|
+
Import "../EVGHoles.rgr"
|
|
39
|
+
Import "../EVGHitTest.rgr"
|
|
40
|
+
Import "../EVGPatch.rgr"
|
|
41
|
+
Import "../EVGUnit.rgr"
|
|
42
|
+
Import "../EVGTreeJson.rgr"
|
|
43
|
+
|
|
44
|
+
class EvgAgent {
|
|
45
|
+
def insp:EVGInspect
|
|
46
|
+
|
|
47
|
+
Constructor () {
|
|
48
|
+
insp = (new EVGInspect())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sfn main:void () {
|
|
52
|
+
def a (new EvgAgent())
|
|
53
|
+
a.run()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn run:void () {
|
|
57
|
+
def argc:int (shell_arg_cnt)
|
|
58
|
+
if (argc < 2) {
|
|
59
|
+
this.usage()
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
def verb:string (shell_arg 0)
|
|
63
|
+
def docPath:string (shell_arg 1)
|
|
64
|
+
def text@(optional):string (EvgAgent.slurp(docPath))
|
|
65
|
+
if (null? text) {
|
|
66
|
+
print (("{\"error\":\"cannot read \\\"" + docPath) + "\\\"\"}")
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
def root@(optional):EVGElement (EVGTreeJson.fromText((unwrap text)))
|
|
70
|
+
if (null? root) {
|
|
71
|
+
print (("{\"error\":\"not an EVG document: \\\"" + docPath) + "\\\"\"}")
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
def tree:EVGElement (unwrap root)
|
|
75
|
+
|
|
76
|
+
if (verb == "outline") {
|
|
77
|
+
this.outline(tree)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (verb == "query") {
|
|
81
|
+
this.query(tree)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (verb == "patch") {
|
|
85
|
+
this.patch(tree docPath)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
if (verb == "measure") {
|
|
89
|
+
this.measure(tree)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (verb == "pick") {
|
|
93
|
+
this.pick(tree)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
this.usage()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn usage:void () {
|
|
100
|
+
print "evg_agent — read, edit and check an EVG document"
|
|
101
|
+
print ""
|
|
102
|
+
print " outline <doc> [--depth=N] [--at=PATH] the tree, one line per node"
|
|
103
|
+
print " query <doc> <.class|#id|tag|path> the nodes that match"
|
|
104
|
+
print " patch <doc> <ops.json> [--out=FILE] apply ops, print the inverse"
|
|
105
|
+
print " measure <doc> [--width=N] [--height=N] [--boxes] [--at=PATH]"
|
|
106
|
+
print " lay it out and say what is wrong"
|
|
107
|
+
print " pick <doc> X Y [--width=N] [--height=N]"
|
|
108
|
+
print " what is under that point, root first"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
; --- pick -----------------------------------------------------------------
|
|
112
|
+
; The editor's question, which `query` cannot answer: somebody pointed at
|
|
113
|
+
; the picture — what is that?
|
|
114
|
+
;
|
|
115
|
+
; It answers with the whole CHAIN from the root down to the deepest thing
|
|
116
|
+
; under the point, not with one node, because the node under a pointer is
|
|
117
|
+
; almost never the one somebody means. A click on a switch lands on its
|
|
118
|
+
; thumb, and the thing to select is two levels up. Handing back the chain
|
|
119
|
+
; lets the caller show the levels and let a person choose, and it costs one
|
|
120
|
+
; walk rather than a round trip per level.
|
|
121
|
+
;
|
|
122
|
+
; The tree is dressed first, for the same reason `measure` dresses it: the
|
|
123
|
+
; point was aimed at the picture on the screen, so the boxes have to be the
|
|
124
|
+
; boxes that were drawn.
|
|
125
|
+
fn pick:void (root:EVGElement) {
|
|
126
|
+
def argc:int (shell_arg_cnt)
|
|
127
|
+
if (argc < 4) {
|
|
128
|
+
print "{\"error\":\"pick needs X and Y\"}"
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
EVGDress.dress(root)
|
|
132
|
+
def w:int (EvgAgent.intFlag("--width" (EVGMeasure.sizeOf(root true 1200))))
|
|
133
|
+
def h:int (EvgAgent.intFlag("--height" (EVGMeasure.sizeOf(root false 900))))
|
|
134
|
+
def ax:int (EvgAgent.numAt(2))
|
|
135
|
+
def ay:int (EvgAgent.numAt(3))
|
|
136
|
+
def px:double (to_double ax)
|
|
137
|
+
def py:double (to_double ay)
|
|
138
|
+
def l (new EVGLayout())
|
|
139
|
+
l.setPageSize((to_double w) (to_double h))
|
|
140
|
+
l.layout(root)
|
|
141
|
+
; Every box where it was drawn. A `transform` is carried as a shift
|
|
142
|
+
; that a reader settles before looking, and a picker is a reader.
|
|
143
|
+
; Note this settles a subtree, not the shifted element's own box —
|
|
144
|
+
; the same thing `EVGHitTest` does, so a thumb moved by a transform
|
|
145
|
+
; answers as its track here exactly as it does under a press.
|
|
146
|
+
root.settleAll()
|
|
147
|
+
insp.build(root)
|
|
148
|
+
|
|
149
|
+
; The deepest element holding the point. Depth decides, and among
|
|
150
|
+
; equals the later one wins, because that is the one painted on top.
|
|
151
|
+
def best:string ""
|
|
152
|
+
def bestDepth:int (0 - 1)
|
|
153
|
+
def i:int 0
|
|
154
|
+
while (i < (insp.count())) {
|
|
155
|
+
def path:string (insp.pathAt(i))
|
|
156
|
+
def hit@(optional):EVGElement (insp.elementAt(path))
|
|
157
|
+
if ((null? hit) == false) {
|
|
158
|
+
def el:EVGElement (unwrap hit)
|
|
159
|
+
if ((el.isHidden()) == false) {
|
|
160
|
+
if (EVGHitTest.containsPoint(el px py)) {
|
|
161
|
+
def d:int (EvgAgent.depthOf(path))
|
|
162
|
+
if (d >= bestDepth) {
|
|
163
|
+
bestDepth = d
|
|
164
|
+
best = path
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
i = (i + 1)
|
|
170
|
+
}
|
|
171
|
+
if ((strlen best) == 0) {
|
|
172
|
+
print "{\"chain\":[],\"count\":0}"
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
; Up to the root, then printed the other way round, so the first entry
|
|
176
|
+
; is the page and the last is what the pointer was actually over.
|
|
177
|
+
def up:[string]
|
|
178
|
+
def at:string best
|
|
179
|
+
def guard:int 0
|
|
180
|
+
while ((guard < 64) && ((strlen at) > 0)) {
|
|
181
|
+
push up at
|
|
182
|
+
at = (insp.parentOf(at))
|
|
183
|
+
guard = (guard + 1)
|
|
184
|
+
}
|
|
185
|
+
def out:string "{\"chain\":["
|
|
186
|
+
def n:int 0
|
|
187
|
+
def k:int ((array_length up) - 1)
|
|
188
|
+
while (k >= 0) {
|
|
189
|
+
def p:string (itemAt up k)
|
|
190
|
+
def got@(optional):EVGElement (insp.elementAt(p))
|
|
191
|
+
if ((null? got) == false) {
|
|
192
|
+
def el:EVGElement (unwrap got)
|
|
193
|
+
if (n > 0) {
|
|
194
|
+
out = (out + ",")
|
|
195
|
+
}
|
|
196
|
+
out = (out + "\n ")
|
|
197
|
+
out = (out + (EvgAgent.pickOne(p el)))
|
|
198
|
+
n = (n + 1)
|
|
199
|
+
}
|
|
200
|
+
k = (k - 1)
|
|
201
|
+
}
|
|
202
|
+
if (n > 0) {
|
|
203
|
+
out = (out + "\n")
|
|
204
|
+
}
|
|
205
|
+
out = ((out + "],\"count\":") + (to_string n))
|
|
206
|
+
out = ((out + ",\"at\":") + (EVGTreeJson.str(best)))
|
|
207
|
+
print (out + "}")
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
; One link of the chain: enough to name it in a sidebar and draw a box
|
|
211
|
+
; round it, and not the whole node.
|
|
212
|
+
sfn pickOne:string (path:string el:EVGElement) {
|
|
213
|
+
def out:string "{\"at\":"
|
|
214
|
+
out = (out + (EVGTreeJson.str(path)))
|
|
215
|
+
out = ((out + ",\"tag\":") + (EVGTreeJson.str(el.tagName)))
|
|
216
|
+
if ((strlen el.id) > 0) {
|
|
217
|
+
out = ((out + ",\"id\":") + (EVGTreeJson.str(el.id)))
|
|
218
|
+
}
|
|
219
|
+
if ((strlen el.className) > 0) {
|
|
220
|
+
out = ((out + ",\"class\":") + (EVGTreeJson.str(el.className)))
|
|
221
|
+
}
|
|
222
|
+
if ((strlen el.role) > 0) {
|
|
223
|
+
out = ((out + ",\"role\":") + (EVGTreeJson.str(el.role)))
|
|
224
|
+
}
|
|
225
|
+
def t:string (trim el.textContent)
|
|
226
|
+
if ((strlen t) > 0) {
|
|
227
|
+
out = ((out + ",\"text\":") + (EVGTreeJson.str(t)))
|
|
228
|
+
}
|
|
229
|
+
out = ((out + ",\"x\":") + (EVGMeasure.num(el.calculatedX)))
|
|
230
|
+
out = ((out + ",\"y\":") + (EVGMeasure.num(el.calculatedY)))
|
|
231
|
+
out = ((out + ",\"w\":") + (EVGMeasure.num(el.calculatedWidth)))
|
|
232
|
+
out = ((out + ",\"h\":") + (EVGMeasure.num(el.calculatedHeight)))
|
|
233
|
+
out = ((out + ",\"children\":") + (to_string (array_length el.children)))
|
|
234
|
+
return (out + "}")
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
; A positional number argument. `to_int` on a string is optional-typed and
|
|
238
|
+
; answers differently per target, so a digit loop is the same everywhere —
|
|
239
|
+
; the same reason `intFlag` has one.
|
|
240
|
+
sfn numAt:int (slot:int) {
|
|
241
|
+
def v:string (shell_arg slot)
|
|
242
|
+
def out@(mutable):int 0
|
|
243
|
+
def k@(mutable):int 0
|
|
244
|
+
def neg:boolean ((indexOf v "-") == 0)
|
|
245
|
+
if neg {
|
|
246
|
+
k = 1
|
|
247
|
+
}
|
|
248
|
+
while (k < (strlen v)) {
|
|
249
|
+
def d:int (indexOf "0123456789" (substring v k (k + 1)))
|
|
250
|
+
if (d < 0) {
|
|
251
|
+
k = (strlen v)
|
|
252
|
+
} {
|
|
253
|
+
out = ((out * 10) + d)
|
|
254
|
+
k = (k + 1)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if neg {
|
|
258
|
+
return (0 - out)
|
|
259
|
+
}
|
|
260
|
+
return out
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
; --- outline --------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
fn outline:void (root:EVGElement) {
|
|
266
|
+
def depth:int (EvgAgent.intFlag("--depth" 99))
|
|
267
|
+
def from:string (EvgAgent.textFlag("--at" "0"))
|
|
268
|
+
insp.build(root)
|
|
269
|
+
def shown:int 0
|
|
270
|
+
def hidden:int 0
|
|
271
|
+
def i:int 0
|
|
272
|
+
while (i < (insp.count())) {
|
|
273
|
+
def path:string (insp.pathAt(i))
|
|
274
|
+
if (EvgAgent.under(path from)) {
|
|
275
|
+
def d:int (EvgAgent.depthOf(path))
|
|
276
|
+
def base:int (EvgAgent.depthOf(from))
|
|
277
|
+
if ((d - base) <= depth) {
|
|
278
|
+
def hit@(optional):EVGElement (insp.elementAt(path))
|
|
279
|
+
if ((null? hit) == false) {
|
|
280
|
+
print (EvgAgent.line(path (unwrap hit) (d - base)))
|
|
281
|
+
shown = (shown + 1)
|
|
282
|
+
}
|
|
283
|
+
} {
|
|
284
|
+
hidden = (hidden + 1)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
i = (i + 1)
|
|
288
|
+
}
|
|
289
|
+
; Never truncate in silence — a model that cannot see there is more
|
|
290
|
+
; will confidently describe a document it has only the top of.
|
|
291
|
+
if (hidden > 0) {
|
|
292
|
+
print ""
|
|
293
|
+
print ((("… " + (to_string hidden)) + " deeper nodes not shown — raise --depth or pass --at to go into one"))
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
; One node: its address, what it is, and only what it actually says.
|
|
298
|
+
sfn line:string (path:string el:EVGElement indent:int) {
|
|
299
|
+
def out:string (EvgAgent.pad(path 22))
|
|
300
|
+
def i:int 0
|
|
301
|
+
while (i < indent) {
|
|
302
|
+
out = (out + " ")
|
|
303
|
+
i = (i + 1)
|
|
304
|
+
}
|
|
305
|
+
out = (out + el.tagName)
|
|
306
|
+
if ((strlen el.id) > 0) {
|
|
307
|
+
out = ((out + " #") + el.id)
|
|
308
|
+
}
|
|
309
|
+
if ((strlen el.className) > 0) {
|
|
310
|
+
out = ((out + " .") + el.className)
|
|
311
|
+
}
|
|
312
|
+
if ((strlen el.textContent) > 0) {
|
|
313
|
+
out = ((out + " \"") + (EvgAgent.clip(el.textContent 40)))
|
|
314
|
+
out = (out + "\"")
|
|
315
|
+
}
|
|
316
|
+
def names:[string] (EVGPatch.patchableNames())
|
|
317
|
+
def fresh@(optional):EVGElement (EVGPatch.create(el.tagName))
|
|
318
|
+
if (null? fresh) {
|
|
319
|
+
return out
|
|
320
|
+
}
|
|
321
|
+
def blank:EVGElement (unwrap fresh)
|
|
322
|
+
def k:int 0
|
|
323
|
+
while (k < (array_length names)) {
|
|
324
|
+
def name:string (itemAt names k)
|
|
325
|
+
if (name != "class-name") {
|
|
326
|
+
def mine:string (EVGPatch.readProp(el name))
|
|
327
|
+
def theirs:string (EVGPatch.readProp(blank name))
|
|
328
|
+
if (mine != theirs) {
|
|
329
|
+
out = ((((out + " ") + name) + "=") + mine)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
k = (k + 1)
|
|
333
|
+
}
|
|
334
|
+
; `evg-fx-*` is per element rather than per format, so it is not in the
|
|
335
|
+
; static list above. Without this an outline shows that a node has a
|
|
336
|
+
; starfield and not how it is tuned.
|
|
337
|
+
def fx:[string] (EVGPatch.fxNamesOf(el))
|
|
338
|
+
def f:int 0
|
|
339
|
+
while (f < (array_length fx)) {
|
|
340
|
+
def fxName:string (itemAt fx f)
|
|
341
|
+
out = ((((out + " ") + fxName) + "=") + (EVGPatch.readProp(el fxName)))
|
|
342
|
+
f = (f + 1)
|
|
343
|
+
}
|
|
344
|
+
return out
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
; --- query ----------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
fn query:void (root:EVGElement) {
|
|
350
|
+
def argc:int (shell_arg_cnt)
|
|
351
|
+
if (argc < 3) {
|
|
352
|
+
print "{\"error\":\"query needs something to match\"}"
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
def want:string (shell_arg 2)
|
|
356
|
+
insp.build(root)
|
|
357
|
+
def out:string "{\"matches\":["
|
|
358
|
+
def n:int 0
|
|
359
|
+
def i:int 0
|
|
360
|
+
while (i < (insp.count())) {
|
|
361
|
+
def path:string (insp.pathAt(i))
|
|
362
|
+
def hit@(optional):EVGElement (insp.elementAt(path))
|
|
363
|
+
if ((null? hit) == false) {
|
|
364
|
+
def el:EVGElement (unwrap hit)
|
|
365
|
+
if (EvgAgent.matches(el path want)) {
|
|
366
|
+
if (n > 0) {
|
|
367
|
+
out = (out + ",")
|
|
368
|
+
}
|
|
369
|
+
out = (out + "\n ")
|
|
370
|
+
out = (out + (EVGTreeJson.brief(path el)))
|
|
371
|
+
n = (n + 1)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
i = (i + 1)
|
|
375
|
+
}
|
|
376
|
+
if (n > 0) {
|
|
377
|
+
out = (out + "\n")
|
|
378
|
+
}
|
|
379
|
+
out = ((out + "],\"count\":") + (to_string n))
|
|
380
|
+
out = (out + "}")
|
|
381
|
+
print out
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
; `.class`, `#id`, a bare tag, or a path — which is every way an address is
|
|
385
|
+
; written in the rest of this toolchain. Not a selector engine: a compound
|
|
386
|
+
; selector belongs to `EVGStyleSheet`, and pretending otherwise here would
|
|
387
|
+
; be a second, weaker implementation of one.
|
|
388
|
+
sfn matches:boolean (el:EVGElement path:string want:string) {
|
|
389
|
+
if ((strlen want) == 0) {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
def c:int (charAt want 0)
|
|
393
|
+
if (c == 46) {
|
|
394
|
+
return (el.className == (substring want 1 (strlen want)))
|
|
395
|
+
}
|
|
396
|
+
if (c == 35) {
|
|
397
|
+
return (el.id == (substring want 1 (strlen want)))
|
|
398
|
+
}
|
|
399
|
+
if ((c >= 48) && (c <= 57)) {
|
|
400
|
+
return (path == want)
|
|
401
|
+
}
|
|
402
|
+
return (el.tagName == want)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
; --- patch ----------------------------------------------------------------
|
|
406
|
+
|
|
407
|
+
fn patch:void (root:EVGElement docPath:string) {
|
|
408
|
+
def argc:int (shell_arg_cnt)
|
|
409
|
+
if (argc < 3) {
|
|
410
|
+
print "{\"error\":\"patch needs an ops file\"}"
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
def opsPath:string (shell_arg 2)
|
|
414
|
+
def opsText@(optional):string (EvgAgent.slurp(opsPath))
|
|
415
|
+
if (null? opsText) {
|
|
416
|
+
print (("{\"error\":\"cannot read \\\"" + opsPath) + "\\\"\"}")
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
def ops:[EVGPatchOp] (EvgAgent.readOps((unwrap opsText)))
|
|
420
|
+
if ((array_length ops) == 0) {
|
|
421
|
+
print "{\"error\":\"no ops in that file\"}"
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
def p (new EVGPatch())
|
|
425
|
+
def r:EVGPatchResult (p.apply(root ops))
|
|
426
|
+
if (r.ok == false) {
|
|
427
|
+
print (("{\"ok\":false,\"applied\":0,\"rejected\":" + (EvgAgent.strArray(r.rejected))) + "}")
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
def outPath:string (EvgAgent.textFlag("--out" docPath))
|
|
431
|
+
def text:string (EVGTreeJson.toText(root))
|
|
432
|
+
EvgAgent.spit(outPath text)
|
|
433
|
+
def out:string "{\"ok\":true,\"applied\":"
|
|
434
|
+
out = (out + (to_string r.applied))
|
|
435
|
+
out = ((out + ",\"wrote\":\"") + outPath)
|
|
436
|
+
out = (out + "\"")
|
|
437
|
+
; Read the properties back BEFORE laying the document out: layout
|
|
438
|
+
; resolves units into the elements it measures, so a height asked for
|
|
439
|
+
; in the ops file no longer reads the way it was written afterwards.
|
|
440
|
+
def dflt:string (this.atDefaults(root ops))
|
|
441
|
+
out = (out + ",\"layout\":")
|
|
442
|
+
out = (out + (this.layoutOf(root)))
|
|
443
|
+
if ((strlen dflt) > 0) {
|
|
444
|
+
out = ((out + ",\"atDefault\":[") + dflt)
|
|
445
|
+
out = (out + "],\"note\":\"applied — but those values are the tag default, and the format writes only what differs from a fresh element, so they will not appear in the file or in outline\"")
|
|
446
|
+
}
|
|
447
|
+
out = (out + ",\"inverse\":[")
|
|
448
|
+
def i:int 0
|
|
449
|
+
while (i < (array_length r.inverse)) {
|
|
450
|
+
if (i > 0) {
|
|
451
|
+
out = (out + ",")
|
|
452
|
+
}
|
|
453
|
+
out = (out + "\n ")
|
|
454
|
+
out = (out + (EvgAgent.opJson((itemAt r.inverse i))))
|
|
455
|
+
i = (i + 1)
|
|
456
|
+
}
|
|
457
|
+
if ((array_length r.inverse) > 0) {
|
|
458
|
+
out = (out + "\n")
|
|
459
|
+
}
|
|
460
|
+
out = (out + "]}")
|
|
461
|
+
print out
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
; What the edit did to the layout, without being asked for it.
|
|
465
|
+
;
|
|
466
|
+
; An agent that patches and then stops has changed a picture it cannot see.
|
|
467
|
+
; The numbers that decide whether the change was good — what overlaps, what
|
|
468
|
+
; left the page, how much room is left — cost one layout pass here and a
|
|
469
|
+
; round trip it would usually skip if it had to ask.
|
|
470
|
+
fn layoutOf:string (root:EVGElement) {
|
|
471
|
+
def m (new EVGMeasure())
|
|
472
|
+
def w:int (EVGMeasure.sizeOf(root true 1200))
|
|
473
|
+
def h:int (EVGMeasure.sizeOf(root false 900))
|
|
474
|
+
m.look(root w h "")
|
|
475
|
+
return (m.summary(5))
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
; --- WHY AN APPLIED OP CAN LEAVE NO TRACE ---------------------------------
|
|
479
|
+
;
|
|
480
|
+
; The format writes a property only where it differs from a fresh element of
|
|
481
|
+
; the same tag, and EVG's defaults are not CSS's: a div is `column`, not
|
|
482
|
+
; `row`. So `set-prop flex-direction=column` on a row really does turn the
|
|
483
|
+
; node, and then the file, the outline and a query all show nothing, because
|
|
484
|
+
; there is nothing left to say. An agent that re-reads its own edit concludes
|
|
485
|
+
; the tool dropped it and writes a worse document around the phantom bug.
|
|
486
|
+
; Naming those ops in the result is the whole fix: the answer arrives with
|
|
487
|
+
; the edit instead of after a wrong theory about it.
|
|
488
|
+
;
|
|
489
|
+
; A batch that inserts, removes or moves is skipped — those shift the paths
|
|
490
|
+
; the ops were written against, so a lookup afterwards could name the wrong
|
|
491
|
+
; node, and a confident wrong note is worse than none.
|
|
492
|
+
fn atDefaults:string (root:EVGElement ops:[EVGPatchOp]) {
|
|
493
|
+
def n:int (array_length ops)
|
|
494
|
+
def i:int 0
|
|
495
|
+
while (i < n) {
|
|
496
|
+
def op:EVGPatchOp (itemAt ops i)
|
|
497
|
+
if ((((op.op != "set-prop") && (op.op != "set-text")) && (op.op != "set-id")) && (op.op != "set-css")) {
|
|
498
|
+
return ""
|
|
499
|
+
}
|
|
500
|
+
i = (i + 1)
|
|
501
|
+
}
|
|
502
|
+
insp.build(root)
|
|
503
|
+
def out:string ""
|
|
504
|
+
def wrote:int 0
|
|
505
|
+
def k:int 0
|
|
506
|
+
while (k < n) {
|
|
507
|
+
def op:EVGPatchOp (itemAt ops k)
|
|
508
|
+
if (op.op == "set-prop") {
|
|
509
|
+
def one:string (this.defaultNote(op))
|
|
510
|
+
if ((strlen one) > 0) {
|
|
511
|
+
if (wrote > 0) {
|
|
512
|
+
out = (out + ",")
|
|
513
|
+
}
|
|
514
|
+
out = (out + "\n ")
|
|
515
|
+
out = (out + one)
|
|
516
|
+
wrote = (wrote + 1)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
k = (k + 1)
|
|
520
|
+
}
|
|
521
|
+
if (wrote > 0) {
|
|
522
|
+
out = (out + "\n")
|
|
523
|
+
}
|
|
524
|
+
return out
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
; One op, if what it wrote now reads the same as a fresh element of that tag.
|
|
528
|
+
fn defaultNote:string (op:EVGPatchOp) {
|
|
529
|
+
; An empty value IS the request to go back to the default — the inverse
|
|
530
|
+
; of a property that had never been set writes one. Saying it landed on
|
|
531
|
+
; the default would be reporting the caller's own instruction back.
|
|
532
|
+
if ((strlen op.value) == 0) {
|
|
533
|
+
return ""
|
|
534
|
+
}
|
|
535
|
+
def hit@(optional):EVGElement (insp.elementAt(op.at))
|
|
536
|
+
if (null? hit) {
|
|
537
|
+
return ""
|
|
538
|
+
}
|
|
539
|
+
def el:EVGElement (unwrap hit)
|
|
540
|
+
def fresh@(optional):EVGElement (EVGPatch.create(el.tagName))
|
|
541
|
+
if (null? fresh) {
|
|
542
|
+
return ""
|
|
543
|
+
}
|
|
544
|
+
def blank:EVGElement (unwrap fresh)
|
|
545
|
+
def mine:string (EVGPatch.readProp(el op.prop))
|
|
546
|
+
def theirs:string (EVGPatch.readProp(blank op.prop))
|
|
547
|
+
if (mine != theirs) {
|
|
548
|
+
return ""
|
|
549
|
+
}
|
|
550
|
+
def out:string "{\"at\":"
|
|
551
|
+
out = (out + (EVGTreeJson.str(op.at)))
|
|
552
|
+
out = ((out + ",\"prop\":") + (EVGTreeJson.str(op.prop)))
|
|
553
|
+
out = ((out + ",\"value\":") + (EVGTreeJson.str(mine)))
|
|
554
|
+
out = ((out + ",\"tag\":") + (EVGTreeJson.str(el.tagName)))
|
|
555
|
+
return (out + "}")
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
; An inverse op, in the shape `patch` takes back. Writing the file the
|
|
559
|
+
; caller would have to write is the difference between "here is how to undo
|
|
560
|
+
; it" and "here is a description of the undo".
|
|
561
|
+
sfn opJson:string (op:EVGPatchOp) {
|
|
562
|
+
def out:string "{\"op\":"
|
|
563
|
+
out = (out + (EVGTreeJson.str(op.op)))
|
|
564
|
+
out = ((out + ",\"at\":") + (EVGTreeJson.str(op.at)))
|
|
565
|
+
if ((strlen op.prop) > 0) {
|
|
566
|
+
out = ((out + ",\"prop\":") + (EVGTreeJson.str(op.prop)))
|
|
567
|
+
}
|
|
568
|
+
if (op.op == "set-prop") {
|
|
569
|
+
out = ((out + ",\"value\":") + (EVGTreeJson.str(op.value)))
|
|
570
|
+
}
|
|
571
|
+
if (op.op == "set-text") {
|
|
572
|
+
out = ((out + ",\"value\":") + (EVGTreeJson.str(op.value)))
|
|
573
|
+
}
|
|
574
|
+
if (op.op == "set-id") {
|
|
575
|
+
out = ((out + ",\"value\":") + (EVGTreeJson.str(op.value)))
|
|
576
|
+
}
|
|
577
|
+
if (op.op == "set-css") {
|
|
578
|
+
out = ((out + ",\"value\":") + (EVGTreeJson.str(op.value)))
|
|
579
|
+
}
|
|
580
|
+
if ((strlen op.toPath) > 0) {
|
|
581
|
+
out = ((out + ",\"to\":") + (EVGTreeJson.str(op.toPath)))
|
|
582
|
+
}
|
|
583
|
+
if ((strlen op.tag) > 0) {
|
|
584
|
+
out = ((out + ",\"tag\":") + (EVGTreeJson.str(op.tag)))
|
|
585
|
+
}
|
|
586
|
+
if (((op.op == "insert") || (op.op == "move")) || (op.op == "restore")) {
|
|
587
|
+
out = ((out + ",\"index\":") + (to_string op.index))
|
|
588
|
+
}
|
|
589
|
+
; `restore` names a subtree this process is holding, so an inverse
|
|
590
|
+
; carrying one cannot be replayed from a file. Say so in the file
|
|
591
|
+
; rather than let it look replayable.
|
|
592
|
+
if (op.op == "restore") {
|
|
593
|
+
out = (out + ",\"note\":\"in-process only — a removed subtree lives in the patch that removed it\"")
|
|
594
|
+
}
|
|
595
|
+
return (out + "}")
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
; Is `path` at or below `from`? String prefix is not enough: "0/1" is not
|
|
599
|
+
; under "0/12", and both start the same way.
|
|
600
|
+
sfn under:boolean (path:string from:string) {
|
|
601
|
+
if (path == from) {
|
|
602
|
+
return true
|
|
603
|
+
}
|
|
604
|
+
def head:string (from + "/")
|
|
605
|
+
def n:int (strlen head)
|
|
606
|
+
if ((strlen path) <= n) {
|
|
607
|
+
return false
|
|
608
|
+
}
|
|
609
|
+
return ((substring path 0 n) == head)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
sfn depthOf:int (path:string) {
|
|
613
|
+
def n:int 0
|
|
614
|
+
def i:int 0
|
|
615
|
+
while (i < (strlen path)) {
|
|
616
|
+
if ((charAt path i) == 47) {
|
|
617
|
+
n = (n + 1)
|
|
618
|
+
}
|
|
619
|
+
i = (i + 1)
|
|
620
|
+
}
|
|
621
|
+
return n
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
; --- measure --------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
; The layout, in numbers. `EVGMeasure` is shared with the live-build server
|
|
627
|
+
; so the agent and the page cannot disagree about whether a screen is
|
|
628
|
+
; right; this verb is the flags around it.
|
|
629
|
+
fn measure:void (root:EVGElement) {
|
|
630
|
+
def m (new EVGMeasure())
|
|
631
|
+
; Fill the design's own state holes first, so the numbers describe the
|
|
632
|
+
; control as it is drawn rather than a `ui-switch-state-{wifi}` that
|
|
633
|
+
; matches no rule and lays out to nothing. `measure` never writes the
|
|
634
|
+
; document back — `patch` is a different verb and a different tree —
|
|
635
|
+
; so dressing in place here cannot reach the file.
|
|
636
|
+
EVGDress.dress(root)
|
|
637
|
+
def w:int (EvgAgent.intFlag("--width" (EVGMeasure.sizeOf(root true 1200))))
|
|
638
|
+
def h:int (EvgAgent.intFlag("--height" (EVGMeasure.sizeOf(root false 900))))
|
|
639
|
+
def at:string ""
|
|
640
|
+
if (EvgAgent.hasFlag("--boxes")) {
|
|
641
|
+
at = (EvgAgent.textFlag("--at" "0"))
|
|
642
|
+
} {
|
|
643
|
+
at = (EvgAgent.textFlag("--at" ""))
|
|
644
|
+
}
|
|
645
|
+
m.look(root w h at)
|
|
646
|
+
print (m.json(true))
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
; --- the ops file ---------------------------------------------------------
|
|
650
|
+
|
|
651
|
+
sfn readOps:[EVGPatchOp] (text:string) {
|
|
652
|
+
def out:[EVGPatchOp]
|
|
653
|
+
def c (new EVGJsonCursor())
|
|
654
|
+
c.src = text
|
|
655
|
+
c.at = 0
|
|
656
|
+
c.skipWs()
|
|
657
|
+
if ((c.take()) != 123) {
|
|
658
|
+
return out
|
|
659
|
+
}
|
|
660
|
+
while (c.ok) {
|
|
661
|
+
c.skipWs()
|
|
662
|
+
def ch:int (c.peek())
|
|
663
|
+
if (ch == 125) {
|
|
664
|
+
return out
|
|
665
|
+
}
|
|
666
|
+
if (ch == 0) {
|
|
667
|
+
return out
|
|
668
|
+
}
|
|
669
|
+
if (ch == 44) {
|
|
670
|
+
def comma:int (c.take())
|
|
671
|
+
} {
|
|
672
|
+
def key:string (c.readString())
|
|
673
|
+
c.skipWs()
|
|
674
|
+
def colon:int (c.take())
|
|
675
|
+
if (key == "ops") {
|
|
676
|
+
EvgAgent.readOpList(c out)
|
|
677
|
+
} {
|
|
678
|
+
c.skipValue()
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return out
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
sfn readOpList:void (c:EVGJsonCursor out:[EVGPatchOp]) {
|
|
686
|
+
c.skipWs()
|
|
687
|
+
if ((c.take()) != 91) {
|
|
688
|
+
c.ok = false
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
while (c.ok) {
|
|
692
|
+
c.skipWs()
|
|
693
|
+
def ch:int (c.peek())
|
|
694
|
+
if (ch == 93) {
|
|
695
|
+
def closing:int (c.take())
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
if (ch == 0) {
|
|
699
|
+
c.ok = false
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
if (ch == 44) {
|
|
703
|
+
def comma:int (c.take())
|
|
704
|
+
} {
|
|
705
|
+
def op:EVGPatchOp (EvgAgent.readOp(c))
|
|
706
|
+
push out op
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
sfn readOp:EVGPatchOp (c:EVGJsonCursor) {
|
|
712
|
+
def o (new EVGPatchOp())
|
|
713
|
+
c.skipWs()
|
|
714
|
+
if ((c.take()) != 123) {
|
|
715
|
+
c.ok = false
|
|
716
|
+
return o
|
|
717
|
+
}
|
|
718
|
+
while (c.ok) {
|
|
719
|
+
c.skipWs()
|
|
720
|
+
def ch:int (c.peek())
|
|
721
|
+
if (ch == 125) {
|
|
722
|
+
def closing:int (c.take())
|
|
723
|
+
return o
|
|
724
|
+
}
|
|
725
|
+
if (ch == 0) {
|
|
726
|
+
c.ok = false
|
|
727
|
+
return o
|
|
728
|
+
}
|
|
729
|
+
if (ch == 44) {
|
|
730
|
+
def comma:int (c.take())
|
|
731
|
+
} {
|
|
732
|
+
def key:string (c.readString())
|
|
733
|
+
c.skipWs()
|
|
734
|
+
def colon:int (c.take())
|
|
735
|
+
if (key == "op") {
|
|
736
|
+
o.op = (c.readString())
|
|
737
|
+
} {
|
|
738
|
+
if (key == "at") {
|
|
739
|
+
o.at = (c.readString())
|
|
740
|
+
} {
|
|
741
|
+
if (key == "prop") {
|
|
742
|
+
o.prop = (c.readString())
|
|
743
|
+
} {
|
|
744
|
+
if (key == "value") {
|
|
745
|
+
o.value = (c.readString())
|
|
746
|
+
} {
|
|
747
|
+
if (key == "to") {
|
|
748
|
+
o.toPath = (c.readString())
|
|
749
|
+
} {
|
|
750
|
+
if (key == "tag") {
|
|
751
|
+
o.tag = (c.readString())
|
|
752
|
+
} {
|
|
753
|
+
if (key == "node") {
|
|
754
|
+
; A whole subtree, in the same shape a
|
|
755
|
+
; document is written in, read by the
|
|
756
|
+
; same reader. `insert` then puts in a
|
|
757
|
+
; control rather than an empty box.
|
|
758
|
+
def sub@(optional):EVGElement (c.readNode())
|
|
759
|
+
if sub {
|
|
760
|
+
def kid:EVGElement (unwrap sub)
|
|
761
|
+
o.node = sub
|
|
762
|
+
if ((strlen o.tag) == 0) {
|
|
763
|
+
o.tag = kid.tagName
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
} {
|
|
767
|
+
if (key == "index") {
|
|
768
|
+
o.index = (c.readNumber())
|
|
769
|
+
} {
|
|
770
|
+
c.skipValue()
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return o
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
; --- flags, files and text ------------------------------------------------
|
|
785
|
+
|
|
786
|
+
sfn textFlag:string (name:string fallback:string) {
|
|
787
|
+
def argc:int (shell_arg_cnt)
|
|
788
|
+
def head:string (name + "=")
|
|
789
|
+
def n:int (strlen head)
|
|
790
|
+
def i:int 0
|
|
791
|
+
while (i < argc) {
|
|
792
|
+
def a:string (shell_arg i)
|
|
793
|
+
if ((strlen a) > n) {
|
|
794
|
+
if ((substring a 0 n) == head) {
|
|
795
|
+
return (substring a n (strlen a))
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
i = (i + 1)
|
|
799
|
+
}
|
|
800
|
+
return fallback
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
; A flag with no value: `--boxes`, not `--boxes=1`.
|
|
804
|
+
sfn hasFlag:boolean (name:string) {
|
|
805
|
+
def argc:int (shell_arg_cnt)
|
|
806
|
+
def i:int 0
|
|
807
|
+
while (i < argc) {
|
|
808
|
+
if ((shell_arg i) == name) {
|
|
809
|
+
return true
|
|
810
|
+
}
|
|
811
|
+
i = (i + 1)
|
|
812
|
+
}
|
|
813
|
+
return false
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
sfn intFlag:int (name:string fallback:int) {
|
|
817
|
+
def v:string (EvgAgent.textFlag(name ""))
|
|
818
|
+
if ((strlen v) == 0) {
|
|
819
|
+
return fallback
|
|
820
|
+
}
|
|
821
|
+
def parsed@(optional):int (to_int v)
|
|
822
|
+
if (null? parsed) {
|
|
823
|
+
return fallback
|
|
824
|
+
}
|
|
825
|
+
return (unwrap parsed)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
sfn slurp@(optional):string (path:string) {
|
|
829
|
+
def cut:int (EvgAgent.lastSlash(path))
|
|
830
|
+
def dir:string "."
|
|
831
|
+
def name:string path
|
|
832
|
+
if (cut >= 0) {
|
|
833
|
+
dir = (substring path 0 cut)
|
|
834
|
+
name = (substring path (cut + 1) (strlen path))
|
|
835
|
+
}
|
|
836
|
+
def text@(optional):string (read_file dir name)
|
|
837
|
+
return text
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
sfn spit:void (path:string data:string) {
|
|
841
|
+
def cut:int (EvgAgent.lastSlash(path))
|
|
842
|
+
def dir:string "."
|
|
843
|
+
def name:string path
|
|
844
|
+
if (cut >= 0) {
|
|
845
|
+
dir = (substring path 0 cut)
|
|
846
|
+
name = (substring path (cut + 1) (strlen path))
|
|
847
|
+
}
|
|
848
|
+
write_file dir name data
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
sfn lastSlash:int (path:string) {
|
|
852
|
+
def at:int (0 - 1)
|
|
853
|
+
def i:int 0
|
|
854
|
+
while (i < (strlen path)) {
|
|
855
|
+
if ((charAt path i) == 47) {
|
|
856
|
+
at = i
|
|
857
|
+
}
|
|
858
|
+
i = (i + 1)
|
|
859
|
+
}
|
|
860
|
+
return at
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
sfn strArray:string (items:[string]) {
|
|
864
|
+
def out:string "["
|
|
865
|
+
def i:int 0
|
|
866
|
+
while (i < (array_length items)) {
|
|
867
|
+
if (i > 0) {
|
|
868
|
+
out = (out + ",")
|
|
869
|
+
}
|
|
870
|
+
out = (out + "\n ")
|
|
871
|
+
out = (out + (EVGTreeJson.str((itemAt items i))))
|
|
872
|
+
i = (i + 1)
|
|
873
|
+
}
|
|
874
|
+
if ((array_length items) > 0) {
|
|
875
|
+
out = (out + "\n")
|
|
876
|
+
}
|
|
877
|
+
return (out + "]")
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
sfn pad:string (s:string width:int) {
|
|
881
|
+
def out:string s
|
|
882
|
+
while ((strlen out) < width) {
|
|
883
|
+
out = (out + " ")
|
|
884
|
+
}
|
|
885
|
+
return out
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
sfn clip:string (s:string width:int) {
|
|
889
|
+
if ((strlen s) <= width) {
|
|
890
|
+
return s
|
|
891
|
+
}
|
|
892
|
+
return ((substring s 0 width) + "…")
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
sfn num:string (v:double) {
|
|
896
|
+
def whole:int (to_int v)
|
|
897
|
+
return (to_string whole)
|
|
898
|
+
}
|
|
899
|
+
}
|