From 6d2d51d211fde85c21665f984a793d4e834c7772 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Mon, 1 Sep 2025 02:07:48 +0200 Subject: [PATCH] apply bind --- .cover | 1249 ------------------------------------- .gitignore | 1 + Makefile | 2 +- apply.go | 215 ++----- command.go | 380 ++++++----- commandline.go | 78 +-- config.go | 1 + docreflect.gen.go | 63 +- docs.go | 5 + exec.go | 6 +- format.go | 33 +- go.mod | 11 +- go.sum | 12 + help.go | 71 +-- iniparser.gen.go | 383 +++--------- input.go | 79 +-- wand.go => lib.go | 4 + notes.txt | 13 + output.go | 93 +-- readme.md | 3 + reflect.go | 619 +++++++++--------- script/docreflect/docs.go | 4 +- tools/exec.go | 45 ++ tools/execwand.go | 308 +++++++++ tools/lib.go | 23 + tools/tools.go | 273 -------- 26 files changed, 1238 insertions(+), 2736 deletions(-) delete mode 100644 .cover create mode 100644 docs.go rename wand.go => lib.go (98%) create mode 100644 notes.txt create mode 100644 readme.md create mode 100644 tools/exec.go create mode 100644 tools/execwand.go create mode 100644 tools/lib.go delete mode 100644 tools/tools.go diff --git a/.cover b/.cover deleted file mode 100644 index 6819555..0000000 --- a/.cover +++ /dev/null @@ -1,1249 +0,0 @@ -mode: set -code.squareroundforest.org/arpio/wand/apply.go:10.54,11.15 1 0 -code.squareroundforest.org/arpio/wand/apply.go:11.15,13.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:15.2,15.31 1 0 -code.squareroundforest.org/arpio/wand/apply.go:18.52,19.17 1 0 -code.squareroundforest.org/arpio/wand/apply.go:19.17,23.3 3 0 -code.squareroundforest.org/arpio/wand/apply.go:25.2,25.17 1 0 -code.squareroundforest.org/arpio/wand/apply.go:25.17,28.3 2 0 -code.squareroundforest.org/arpio/wand/apply.go:30.2,30.31 1 0 -code.squareroundforest.org/arpio/wand/apply.go:30.31,32.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:35.47,36.25 1 1 -code.squareroundforest.org/arpio/wand/apply.go:37.23,38.32 1 0 -code.squareroundforest.org/arpio/wand/apply.go:39.21,40.30 1 0 -code.squareroundforest.org/arpio/wand/apply.go:44.50,46.2 1 0 -code.squareroundforest.org/arpio/wand/apply.go:48.48,49.31 1 0 -code.squareroundforest.org/arpio/wand/apply.go:49.31,51.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:54.41,55.14 1 1 -code.squareroundforest.org/arpio/wand/apply.go:55.14,58.3 2 0 -code.squareroundforest.org/arpio/wand/apply.go:60.2,60.47 1 1 -code.squareroundforest.org/arpio/wand/apply.go:63.52,64.22 1 1 -code.squareroundforest.org/arpio/wand/apply.go:65.23,66.28 1 0 -code.squareroundforest.org/arpio/wand/apply.go:67.21,68.26 1 0 -code.squareroundforest.org/arpio/wand/apply.go:69.10,70.24 1 1 -code.squareroundforest.org/arpio/wand/apply.go:74.56,75.43 1 1 -code.squareroundforest.org/arpio/wand/apply.go:75.43,81.10 6 1 -code.squareroundforest.org/arpio/wand/apply.go:82.39,84.24 2 1 -code.squareroundforest.org/arpio/wand/apply.go:85.55,87.39 2 0 -code.squareroundforest.org/arpio/wand/apply.go:87.39,90.5 2 0 -code.squareroundforest.org/arpio/wand/apply.go:91.21,93.33 2 0 -code.squareroundforest.org/arpio/wand/apply.go:98.110,102.23 4 1 -code.squareroundforest.org/arpio/wand/apply.go:102.23,104.3 1 1 -code.squareroundforest.org/arpio/wand/apply.go:106.2,107.42 2 1 -code.squareroundforest.org/arpio/wand/apply.go:107.42,110.3 2 0 -code.squareroundforest.org/arpio/wand/apply.go:112.2,113.23 2 1 -code.squareroundforest.org/arpio/wand/apply.go:113.23,115.41 2 1 -code.squareroundforest.org/arpio/wand/apply.go:115.41,117.4 1 0 -code.squareroundforest.org/arpio/wand/apply.go:119.3,119.28 1 1 -code.squareroundforest.org/arpio/wand/apply.go:122.2,123.26 2 1 -code.squareroundforest.org/arpio/wand/apply.go:123.26,124.12 1 0 -code.squareroundforest.org/arpio/wand/apply.go:124.12,126.4 1 0 -code.squareroundforest.org/arpio/wand/apply.go:129.2,130.26 2 1 -code.squareroundforest.org/arpio/wand/apply.go:130.26,131.12 1 0 -code.squareroundforest.org/arpio/wand/apply.go:131.12,133.4 1 0 -code.squareroundforest.org/arpio/wand/apply.go:136.2,137.20 2 1 -code.squareroundforest.org/arpio/wand/apply.go:137.20,138.12 1 1 -code.squareroundforest.org/arpio/wand/apply.go:138.12,140.4 1 1 -code.squareroundforest.org/arpio/wand/apply.go:143.2,143.75 1 1 -code.squareroundforest.org/arpio/wand/apply.go:143.75,145.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:147.2,148.32 2 1 -code.squareroundforest.org/arpio/wand/apply.go:148.32,150.34 2 0 -code.squareroundforest.org/arpio/wand/apply.go:150.34,152.4 1 0 -code.squareroundforest.org/arpio/wand/apply.go:154.3,154.27 1 0 -code.squareroundforest.org/arpio/wand/apply.go:157.2,157.29 1 1 -code.squareroundforest.org/arpio/wand/apply.go:157.29,159.34 2 0 -code.squareroundforest.org/arpio/wand/apply.go:159.34,161.4 1 0 -code.squareroundforest.org/arpio/wand/apply.go:163.3,163.27 1 0 -code.squareroundforest.org/arpio/wand/apply.go:166.2,166.33 1 1 -code.squareroundforest.org/arpio/wand/apply.go:166.33,168.28 2 1 -code.squareroundforest.org/arpio/wand/apply.go:168.28,170.4 1 1 -code.squareroundforest.org/arpio/wand/apply.go:172.3,172.27 1 1 -code.squareroundforest.org/arpio/wand/apply.go:175.2,175.32 1 1 -code.squareroundforest.org/arpio/wand/apply.go:178.63,179.35 1 1 -code.squareroundforest.org/arpio/wand/apply.go:179.35,181.3 1 1 -code.squareroundforest.org/arpio/wand/apply.go:183.2,185.20 3 1 -code.squareroundforest.org/arpio/wand/apply.go:188.138,191.33 3 1 -code.squareroundforest.org/arpio/wand/apply.go:191.33,197.10 6 1 -code.squareroundforest.org/arpio/wand/apply.go:198.12,199.47 1 1 -code.squareroundforest.org/arpio/wand/apply.go:200.12,201.48 1 1 -code.squareroundforest.org/arpio/wand/apply.go:202.30,203.72 1 0 -code.squareroundforest.org/arpio/wand/apply.go:203.72,205.5 1 0 -code.squareroundforest.org/arpio/wand/apply.go:206.18,208.28 2 1 -code.squareroundforest.org/arpio/wand/apply.go:209.17,210.33 1 0 -code.squareroundforest.org/arpio/wand/apply.go:210.33,212.5 1 0 -code.squareroundforest.org/arpio/wand/apply.go:213.11,216.48 3 1 -code.squareroundforest.org/arpio/wand/apply.go:220.2,220.13 1 1 -code.squareroundforest.org/arpio/wand/apply.go:223.73,224.19 1 1 -code.squareroundforest.org/arpio/wand/apply.go:224.19,226.3 1 1 -code.squareroundforest.org/arpio/wand/apply.go:228.2,231.40 4 1 -code.squareroundforest.org/arpio/wand/apply.go:231.40,233.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:235.2,235.17 1 1 -code.squareroundforest.org/arpio/wand/apply.go:235.17,237.3 1 0 -code.squareroundforest.org/arpio/wand/apply.go:239.2,240.24 2 1 -code.squareroundforest.org/arpio/wand/apply.go:240.24,242.3 1 1 -code.squareroundforest.org/arpio/wand/apply.go:244.2,244.20 1 1 -code.squareroundforest.org/arpio/wand/apply.go:247.104,254.2 6 1 -code.squareroundforest.org/arpio/wand/command.go:13.57,19.2 1 1 -code.squareroundforest.org/arpio/wand/command.go:21.25,23.8 2 1 -code.squareroundforest.org/arpio/wand/command.go:23.8,25.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:27.2,27.26 1 1 -code.squareroundforest.org/arpio/wand/command.go:30.51,33.23 3 1 -code.squareroundforest.org/arpio/wand/command.go:33.23,34.68 1 1 -code.squareroundforest.org/arpio/wand/command.go:34.68,36.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:38.3,38.49 1 1 -code.squareroundforest.org/arpio/wand/command.go:38.49,40.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:42.3,42.19 1 1 -code.squareroundforest.org/arpio/wand/command.go:45.2,45.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:48.77,49.18 1 1 -code.squareroundforest.org/arpio/wand/command.go:63.18,64.13 1 1 -code.squareroundforest.org/arpio/wand/command.go:66.17,67.17 1 0 -code.squareroundforest.org/arpio/wand/command.go:67.17,69.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:71.3,71.21 1 0 -code.squareroundforest.org/arpio/wand/command.go:71.21,73.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:75.3,77.39 3 0 -code.squareroundforest.org/arpio/wand/command.go:78.25,79.24 1 1 -code.squareroundforest.org/arpio/wand/command.go:79.24,81.4 1 1 -code.squareroundforest.org/arpio/wand/command.go:83.3,83.13 1 1 -code.squareroundforest.org/arpio/wand/command.go:84.10,85.57 1 1 -code.squareroundforest.org/arpio/wand/command.go:89.61,92.34 3 1 -code.squareroundforest.org/arpio/wand/command.go:92.34,94.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:96.2,96.23 1 1 -code.squareroundforest.org/arpio/wand/command.go:96.23,97.57 1 1 -code.squareroundforest.org/arpio/wand/command.go:97.57,98.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:101.3,101.52 1 1 -code.squareroundforest.org/arpio/wand/command.go:101.52,103.4 1 1 -code.squareroundforest.org/arpio/wand/command.go:106.2,112.18 4 1 -code.squareroundforest.org/arpio/wand/command.go:112.18,114.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:116.2,116.38 1 1 -code.squareroundforest.org/arpio/wand/command.go:116.38,122.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:124.2,124.55 1 1 -code.squareroundforest.org/arpio/wand/command.go:124.55,130.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:132.2,132.38 1 1 -code.squareroundforest.org/arpio/wand/command.go:132.38,138.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:140.2,140.37 1 1 -code.squareroundforest.org/arpio/wand/command.go:140.37,146.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:148.2,148.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:151.47,155.30 4 1 -code.squareroundforest.org/arpio/wand/command.go:155.30,157.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:159.2,161.16 3 1 -code.squareroundforest.org/arpio/wand/command.go:161.16,163.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:165.2,165.48 1 1 -code.squareroundforest.org/arpio/wand/command.go:165.48,167.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:169.2,169.84 1 1 -code.squareroundforest.org/arpio/wand/command.go:169.84,171.3 1 1 -code.squareroundforest.org/arpio/wand/command.go:173.2,173.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:176.78,178.32 2 1 -code.squareroundforest.org/arpio/wand/command.go:178.32,182.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:184.2,184.46 1 1 -code.squareroundforest.org/arpio/wand/command.go:184.46,187.51 3 0 -code.squareroundforest.org/arpio/wand/command.go:187.51,189.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:191.3,191.26 1 0 -code.squareroundforest.org/arpio/wand/command.go:191.26,193.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:195.3,195.27 1 0 -code.squareroundforest.org/arpio/wand/command.go:195.27,196.12 1 0 -code.squareroundforest.org/arpio/wand/command.go:199.3,199.55 1 0 -code.squareroundforest.org/arpio/wand/command.go:199.55,201.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:203.3,203.30 1 0 -code.squareroundforest.org/arpio/wand/command.go:206.2,206.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:209.103,210.16 1 1 -code.squareroundforest.org/arpio/wand/command.go:210.16,212.3 1 1 -code.squareroundforest.org/arpio/wand/command.go:214.2,214.23 1 1 -code.squareroundforest.org/arpio/wand/command.go:214.23,216.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:218.2,218.59 1 1 -code.squareroundforest.org/arpio/wand/command.go:218.59,220.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:222.2,222.21 1 1 -code.squareroundforest.org/arpio/wand/command.go:222.21,223.49 1 1 -code.squareroundforest.org/arpio/wand/command.go:223.49,225.4 1 1 -code.squareroundforest.org/arpio/wand/command.go:228.2,228.50 1 1 -code.squareroundforest.org/arpio/wand/command.go:228.50,230.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:232.2,232.21 1 1 -code.squareroundforest.org/arpio/wand/command.go:232.21,233.69 1 1 -code.squareroundforest.org/arpio/wand/command.go:233.69,235.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:238.2,240.36 3 1 -code.squareroundforest.org/arpio/wand/command.go:240.36,241.19 1 1 -code.squareroundforest.org/arpio/wand/command.go:241.19,243.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:245.3,245.20 1 1 -code.squareroundforest.org/arpio/wand/command.go:245.20,247.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:249.3,250.81 2 1 -code.squareroundforest.org/arpio/wand/command.go:250.81,252.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:254.3,254.37 1 1 -code.squareroundforest.org/arpio/wand/command.go:254.37,260.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:262.3,262.32 1 1 -code.squareroundforest.org/arpio/wand/command.go:262.32,264.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:266.3,266.18 1 1 -code.squareroundforest.org/arpio/wand/command.go:266.18,268.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:271.2,271.12 1 1 -code.squareroundforest.org/arpio/wand/command.go:274.38,276.37 2 1 -code.squareroundforest.org/arpio/wand/command.go:276.37,278.3 1 1 -code.squareroundforest.org/arpio/wand/command.go:280.2,280.46 1 1 -code.squareroundforest.org/arpio/wand/command.go:280.46,282.3 1 0 -code.squareroundforest.org/arpio/wand/command.go:284.2,284.11 1 1 -code.squareroundforest.org/arpio/wand/command.go:287.50,289.81 2 1 -code.squareroundforest.org/arpio/wand/command.go:289.81,291.3 1 1 -code.squareroundforest.org/arpio/wand/command.go:293.2,294.25 2 1 -code.squareroundforest.org/arpio/wand/command.go:294.25,295.43 1 0 -code.squareroundforest.org/arpio/wand/command.go:295.43,297.4 1 0 -code.squareroundforest.org/arpio/wand/command.go:300.2,300.12 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:28.30,30.2 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:32.34,34.2 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:36.48,37.26 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:37.26,38.18 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:38.18,40.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:43.2,43.30 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:46.56,47.32 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:47.32,48.16 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:48.16,50.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:53.2,53.32 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:56.36,65.23 8 1 -code.squareroundforest.org/arpio/wand/commandline.go:65.23,67.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:69.2,71.46 3 1 -code.squareroundforest.org/arpio/wand/commandline.go:71.46,74.3 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:76.2,77.23 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:77.23,78.28 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:78.28,80.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:83.2,84.25 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:87.32,89.17 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:89.17,91.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:93.2,93.27 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:93.27,95.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:97.2,97.28 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:97.28,99.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:101.2,101.26 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:101.26,102.25 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:102.25,103.12 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:106.3,106.25 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:106.25,107.12 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:110.3,110.15 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:110.15,111.12 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:114.3,114.15 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:114.15,116.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:118.3,118.15 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:121.2,121.13 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:124.40,126.16 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:126.16,128.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:130.2,130.17 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:130.17,132.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:134.2,134.28 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:134.28,136.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:138.2,138.26 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:138.26,139.15 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:139.15,141.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:143.3,143.26 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:143.26,145.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:148.2,148.13 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:151.34,152.21 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:152.21,154.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:156.2,156.37 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:156.37,157.19 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:157.19,159.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:162.2,162.12 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:165.51,166.37 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:166.37,167.22 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:167.22,169.4 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:172.2,172.21 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:175.70,176.20 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:176.20,178.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:180.2,181.9 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:181.9,183.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:185.2,187.31 3 0 -code.squareroundforest.org/arpio/wand/commandline.go:190.49,195.2 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:197.46,202.2 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:204.33,207.2 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:209.34,210.17 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:210.17,212.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:214.2,214.19 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:214.19,216.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:218.2,218.27 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:218.27,220.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:222.2,222.13 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:225.38,228.2 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:230.85,232.14 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:232.14,234.65 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:234.65,237.4 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:239.3,239.40 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:242.2,247.19 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:247.19,251.3 3 1 -code.squareroundforest.org/arpio/wand/commandline.go:253.2,253.39 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:253.39,255.25 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:255.25,258.4 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:260.3,260.38 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:263.2,263.21 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:263.21,265.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:267.2,267.42 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:270.95,273.14 3 0 -code.squareroundforest.org/arpio/wand/commandline.go:273.14,275.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:277.2,278.31 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:278.31,280.3 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:282.2,282.94 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:282.94,285.3 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:287.2,290.16 4 0 -code.squareroundforest.org/arpio/wand/commandline.go:293.55,295.20 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:295.20,297.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:299.2,300.9 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:301.19,302.20 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:302.20,306.4 3 0 -code.squareroundforest.org/arpio/wand/commandline.go:307.21,311.35 4 1 -code.squareroundforest.org/arpio/wand/commandline.go:312.29,316.38 4 0 -code.squareroundforest.org/arpio/wand/commandline.go:317.10,318.43 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:321.2,324.10 4 1 -code.squareroundforest.org/arpio/wand/commandline.go:327.46,329.21 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:329.21,331.3 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:333.2,334.46 2 1 -code.squareroundforest.org/arpio/wand/commandline.go:334.46,337.3 2 0 -code.squareroundforest.org/arpio/wand/commandline.go:339.2,339.23 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:339.23,340.23 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:340.23,341.12 1 1 -code.squareroundforest.org/arpio/wand/commandline.go:344.3,344.24 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:344.24,345.32 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:345.32,347.5 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:350.3,350.37 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:350.37,351.16 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:351.16,353.5 1 0 -code.squareroundforest.org/arpio/wand/commandline.go:357.2,357.14 1 1 -code.squareroundforest.org/arpio/wand/config.go:24.40,28.2 1 0 -code.squareroundforest.org/arpio/wand/config.go:30.41,32.2 1 0 -code.squareroundforest.org/arpio/wand/config.go:34.44,35.19 1 0 -code.squareroundforest.org/arpio/wand/config.go:35.19,37.17 2 0 -code.squareroundforest.org/arpio/wand/config.go:37.17,39.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:41.3,41.16 1 0 -code.squareroundforest.org/arpio/wand/config.go:44.2,45.16 2 0 -code.squareroundforest.org/arpio/wand/config.go:45.16,47.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:49.2,49.15 1 0 -code.squareroundforest.org/arpio/wand/config.go:52.30,53.19 1 0 -code.squareroundforest.org/arpio/wand/config.go:53.19,55.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:57.2,57.39 1 0 -code.squareroundforest.org/arpio/wand/config.go:57.39,59.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:61.2,61.12 1 0 -code.squareroundforest.org/arpio/wand/config.go:64.59,66.21 2 0 -code.squareroundforest.org/arpio/wand/config.go:66.21,69.3 2 0 -code.squareroundforest.org/arpio/wand/config.go:69.8,71.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:73.2,74.16 2 0 -code.squareroundforest.org/arpio/wand/config.go:74.16,75.92 1 0 -code.squareroundforest.org/arpio/wand/config.go:75.92,77.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:79.3,79.69 1 0 -code.squareroundforest.org/arpio/wand/config.go:82.2,83.34 2 0 -code.squareroundforest.org/arpio/wand/config.go:83.34,84.30 1 0 -code.squareroundforest.org/arpio/wand/config.go:84.30,85.12 1 0 -code.squareroundforest.org/arpio/wand/config.go:88.3,94.37 2 0 -code.squareroundforest.org/arpio/wand/config.go:94.37,95.27 1 0 -code.squareroundforest.org/arpio/wand/config.go:95.27,97.13 2 0 -code.squareroundforest.org/arpio/wand/config.go:100.4,100.29 1 0 -code.squareroundforest.org/arpio/wand/config.go:100.29,103.13 3 0 -code.squareroundforest.org/arpio/wand/config.go:107.3,107.29 1 0 -code.squareroundforest.org/arpio/wand/config.go:107.29,109.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:111.3,113.16 3 0 -code.squareroundforest.org/arpio/wand/config.go:113.16,115.12 2 0 -code.squareroundforest.org/arpio/wand/config.go:118.3,118.22 1 0 -code.squareroundforest.org/arpio/wand/config.go:118.22,120.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:122.3,122.49 1 0 -code.squareroundforest.org/arpio/wand/config.go:125.2,125.15 1 0 -code.squareroundforest.org/arpio/wand/config.go:128.81,130.30 2 0 -code.squareroundforest.org/arpio/wand/config.go:130.30,132.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:134.2,135.31 2 0 -code.squareroundforest.org/arpio/wand/config.go:135.31,136.25 1 0 -code.squareroundforest.org/arpio/wand/config.go:136.25,137.12 1 0 -code.squareroundforest.org/arpio/wand/config.go:140.3,140.46 1 0 -code.squareroundforest.org/arpio/wand/config.go:140.46,140.80 1 0 -code.squareroundforest.org/arpio/wand/config.go:143.2,143.46 1 0 -code.squareroundforest.org/arpio/wand/config.go:146.76,148.32 2 1 -code.squareroundforest.org/arpio/wand/config.go:148.32,150.17 2 0 -code.squareroundforest.org/arpio/wand/config.go:150.17,152.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:154.3,154.21 1 0 -code.squareroundforest.org/arpio/wand/config.go:157.2,158.23 2 1 -code.squareroundforest.org/arpio/wand/config.go:158.23,159.32 1 0 -code.squareroundforest.org/arpio/wand/config.go:159.32,161.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:163.3,163.39 1 0 -code.squareroundforest.org/arpio/wand/config.go:163.39,164.24 1 0 -code.squareroundforest.org/arpio/wand/config.go:164.24,166.5 1 0 -code.squareroundforest.org/arpio/wand/config.go:168.4,168.28 1 0 -code.squareroundforest.org/arpio/wand/config.go:172.2,172.16 1 1 -code.squareroundforest.org/arpio/wand/config.go:175.71,176.41 1 1 -code.squareroundforest.org/arpio/wand/config.go:176.41,178.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:180.2,180.21 1 1 -code.squareroundforest.org/arpio/wand/config.go:180.21,182.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:184.2,184.39 1 1 -code.squareroundforest.org/arpio/wand/config.go:187.44,188.21 1 1 -code.squareroundforest.org/arpio/wand/config.go:188.21,190.3 1 0 -code.squareroundforest.org/arpio/wand/config.go:192.2,192.31 1 1 -code.squareroundforest.org/arpio/wand/config.go:192.31,193.29 1 0 -code.squareroundforest.org/arpio/wand/config.go:193.29,195.4 1 0 -code.squareroundforest.org/arpio/wand/config.go:198.2,198.14 1 1 -code.squareroundforest.org/arpio/wand/docreflect.gen.go:5.13,64.2 58 1 -code.squareroundforest.org/arpio/wand/env.go:13.39,20.30 2 0 -code.squareroundforest.org/arpio/wand/env.go:20.30,21.13 1 0 -code.squareroundforest.org/arpio/wand/env.go:21.13,24.12 3 0 -code.squareroundforest.org/arpio/wand/env.go:27.3,27.16 1 0 -code.squareroundforest.org/arpio/wand/env.go:27.16,29.12 2 0 -code.squareroundforest.org/arpio/wand/env.go:32.3,32.15 1 0 -code.squareroundforest.org/arpio/wand/env.go:32.15,35.12 3 0 -code.squareroundforest.org/arpio/wand/env.go:38.3,38.31 1 0 -code.squareroundforest.org/arpio/wand/env.go:41.2,42.15 2 0 -code.squareroundforest.org/arpio/wand/env.go:45.50,52.26 3 1 -code.squareroundforest.org/arpio/wand/env.go:52.26,54.22 2 1 -code.squareroundforest.org/arpio/wand/env.go:54.22,55.12 1 1 -code.squareroundforest.org/arpio/wand/env.go:58.3,60.73 3 0 -code.squareroundforest.org/arpio/wand/env.go:60.73,61.12 1 0 -code.squareroundforest.org/arpio/wand/env.go:64.3,66.39 3 0 -code.squareroundforest.org/arpio/wand/env.go:69.2,69.10 1 1 -code.squareroundforest.org/arpio/wand/exec.go:12.112,15.51 3 1 -code.squareroundforest.org/arpio/wand/exec.go:15.51,19.3 3 1 -code.squareroundforest.org/arpio/wand/exec.go:21.2,21.40 1 1 -code.squareroundforest.org/arpio/wand/exec.go:21.40,22.56 1 0 -code.squareroundforest.org/arpio/wand/exec.go:22.56,25.4 2 0 -code.squareroundforest.org/arpio/wand/exec.go:27.3,27.9 1 0 -code.squareroundforest.org/arpio/wand/exec.go:30.2,30.45 1 1 -code.squareroundforest.org/arpio/wand/exec.go:30.45,32.68 2 0 -code.squareroundforest.org/arpio/wand/exec.go:32.68,35.4 2 0 -code.squareroundforest.org/arpio/wand/exec.go:37.3,37.9 1 0 -code.squareroundforest.org/arpio/wand/exec.go:40.2,42.16 3 1 -code.squareroundforest.org/arpio/wand/exec.go:42.16,43.62 1 0 -code.squareroundforest.org/arpio/wand/exec.go:43.62,46.4 2 0 -code.squareroundforest.org/arpio/wand/exec.go:48.3,48.9 1 0 -code.squareroundforest.org/arpio/wand/exec.go:51.2,51.23 1 1 -code.squareroundforest.org/arpio/wand/exec.go:51.23,52.50 1 0 -code.squareroundforest.org/arpio/wand/exec.go:52.50,55.4 2 0 -code.squareroundforest.org/arpio/wand/exec.go:57.3,57.9 1 0 -code.squareroundforest.org/arpio/wand/exec.go:60.2,61.21 2 1 -code.squareroundforest.org/arpio/wand/exec.go:61.21,63.3 1 1 -code.squareroundforest.org/arpio/wand/exec.go:65.2,66.36 2 1 -code.squareroundforest.org/arpio/wand/exec.go:66.36,67.62 1 0 -code.squareroundforest.org/arpio/wand/exec.go:67.62,70.4 2 0 -code.squareroundforest.org/arpio/wand/exec.go:72.3,72.9 1 0 -code.squareroundforest.org/arpio/wand/exec.go:75.2,75.21 1 1 -code.squareroundforest.org/arpio/wand/exec.go:75.21,80.3 4 0 -code.squareroundforest.org/arpio/wand/exec.go:82.2,83.16 2 1 -code.squareroundforest.org/arpio/wand/exec.go:83.16,87.3 3 0 -code.squareroundforest.org/arpio/wand/exec.go:89.2,89.59 1 1 -code.squareroundforest.org/arpio/wand/exec.go:89.59,94.3 4 1 -code.squareroundforest.org/arpio/wand/exec.go:96.2,97.16 2 1 -code.squareroundforest.org/arpio/wand/exec.go:97.16,101.3 3 0 -code.squareroundforest.org/arpio/wand/exec.go:103.2,103.52 1 1 -code.squareroundforest.org/arpio/wand/exec.go:103.52,107.3 3 0 -code.squareroundforest.org/arpio/wand/format.go:10.114,12.39 2 0 -code.squareroundforest.org/arpio/wand/format.go:12.39,13.17 1 0 -code.squareroundforest.org/arpio/wand/format.go:13.17,15.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:17.3,17.40 1 0 -code.squareroundforest.org/arpio/wand/format.go:20.2,20.30 1 0 -code.squareroundforest.org/arpio/wand/format.go:20.30,21.17 1 0 -code.squareroundforest.org/arpio/wand/format.go:21.17,23.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:25.3,25.38 1 0 -code.squareroundforest.org/arpio/wand/format.go:28.2,28.24 1 0 -code.squareroundforest.org/arpio/wand/format.go:28.24,30.3 1 0 -code.squareroundforest.org/arpio/wand/format.go:32.2,32.8 1 0 -code.squareroundforest.org/arpio/wand/format.go:35.34,42.19 3 0 -code.squareroundforest.org/arpio/wand/format.go:42.19,44.17 2 0 -code.squareroundforest.org/arpio/wand/format.go:44.17,45.26 1 0 -code.squareroundforest.org/arpio/wand/format.go:45.26,47.5 1 0 -code.squareroundforest.org/arpio/wand/format.go:49.4,49.12 1 0 -code.squareroundforest.org/arpio/wand/format.go:52.3,52.38 1 0 -code.squareroundforest.org/arpio/wand/format.go:55.2,55.24 1 0 -code.squareroundforest.org/arpio/wand/format.go:55.24,57.3 1 0 -code.squareroundforest.org/arpio/wand/format.go:59.2,60.31 2 0 -code.squareroundforest.org/arpio/wand/format.go:60.31,62.3 1 0 -code.squareroundforest.org/arpio/wand/format.go:64.2,64.42 1 0 -code.squareroundforest.org/arpio/wand/format.go:67.29,71.2 3 0 -code.squareroundforest.org/arpio/wand/format.go:73.37,76.20 3 0 -code.squareroundforest.org/arpio/wand/format.go:76.20,78.3 1 0 -code.squareroundforest.org/arpio/wand/format.go:80.2,80.31 1 0 -code.squareroundforest.org/arpio/wand/format.go:83.32,86.20 3 0 -code.squareroundforest.org/arpio/wand/format.go:86.20,88.3 1 0 -code.squareroundforest.org/arpio/wand/format.go:90.2,90.31 1 0 -code.squareroundforest.org/arpio/wand/format.go:93.85,94.23 1 0 -code.squareroundforest.org/arpio/wand/format.go:94.23,96.36 2 0 -code.squareroundforest.org/arpio/wand/format.go:96.36,98.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:100.3,101.16 2 0 -code.squareroundforest.org/arpio/wand/format.go:101.16,103.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:105.3,105.25 1 0 -code.squareroundforest.org/arpio/wand/format.go:105.25,107.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:109.3,110.26 2 0 -code.squareroundforest.org/arpio/wand/format.go:110.26,112.4 1 0 -code.squareroundforest.org/arpio/wand/format.go:114.3,114.35 1 0 -code.squareroundforest.org/arpio/wand/format.go:117.2,118.8 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:11.38,13.19 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:13.19,14.67 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:14.67,16.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:18.3,18.35 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:18.35,20.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:23.2,23.18 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:26.47,30.58 4 0 -code.squareroundforest.org/arpio/wand/formathelp.go:30.58,34.3 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:36.2,36.27 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:36.27,39.30 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:39.30,41.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:43.3,43.50 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:43.50,45.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:47.3,47.38 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:47.38,51.11 4 0 -code.squareroundforest.org/arpio/wand/formathelp.go:52.28,53.71 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:54.17,55.55 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:56.17,57.55 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:61.3,61.12 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:64.2,64.33 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:64.33,71.3 6 0 -code.squareroundforest.org/arpio/wand/formathelp.go:73.2,73.27 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:73.27,77.3 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:79.2,79.26 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:79.26,82.47 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:82.47,84.26 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:84.26,87.5 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:89.4,89.26 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:89.26,92.5 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:95.3,100.27 5 0 -code.squareroundforest.org/arpio/wand/formathelp.go:100.27,101.20 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:101.20,103.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:106.3,106.24 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:106.24,109.4 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:111.3,111.27 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:111.27,113.19 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:113.19,115.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:117.4,117.13 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:121.2,121.30 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:121.30,129.38 7 0 -code.squareroundforest.org/arpio/wand/formathelp.go:129.38,131.20 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:131.20,133.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:135.4,136.17 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:136.17,138.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:140.4,140.28 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:140.28,142.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:142.10,142.31 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:142.31,144.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:146.4,146.16 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:149.3,152.27 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:152.27,153.20 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:153.20,155.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:158.3,158.24 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:158.24,161.4 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:163.3,163.27 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:163.27,165.19 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:165.19,167.5 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:169.4,169.13 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:173.2,173.76 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:173.76,185.15 12 0 -code.squareroundforest.org/arpio/wand/formathelp.go:185.15,187.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:187.9,189.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:191.3,191.12 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:194.2,194.54 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:194.54,203.38 9 0 -code.squareroundforest.org/arpio/wand/formathelp.go:203.38,204.21 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:204.21,207.13 3 0 -code.squareroundforest.org/arpio/wand/formathelp.go:210.4,210.19 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:210.19,212.20 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:212.20,214.6 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:216.5,217.13 2 0 -code.squareroundforest.org/arpio/wand/formathelp.go:221.3,232.15 12 0 -code.squareroundforest.org/arpio/wand/formathelp.go:232.15,234.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:234.9,236.4 1 0 -code.squareroundforest.org/arpio/wand/formathelp.go:238.3,246.12 9 0 -code.squareroundforest.org/arpio/wand/formathelp.go:249.2,249.17 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:11.34,18.23 3 0 -code.squareroundforest.org/arpio/wand/formatman.go:18.23,19.13 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:20.23,21.29 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:22.18,23.19 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:23.19,25.5 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:27.4,27.23 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:28.15,29.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:30.15,31.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:32.15,33.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:34.15,35.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:36.15,37.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:38.15,39.39 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:40.11,41.23 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:44.3,44.27 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:47.2,47.19 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:50.83,53.29 3 0 -code.squareroundforest.org/arpio/wand/formatman.go:53.29,55.3 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:57.2,57.49 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:57.49,59.3 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:61.2,63.37 3 0 -code.squareroundforest.org/arpio/wand/formatman.go:63.37,65.25 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:65.25,67.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:69.3,69.10 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:70.27,71.70 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:72.16,73.54 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:74.16,75.54 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:79.2,79.33 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:79.33,80.25 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:80.25,82.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:84.3,84.38 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:84.38,85.13 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:85.13,87.5 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:89.4,89.52 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:93.2,93.27 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:93.27,96.3 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:98.2,98.26 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:98.26,100.47 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:100.47,102.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:104.3,104.25 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:104.25,107.4 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:109.3,109.25 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:109.25,110.26 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:110.26,112.5 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:114.4,115.60 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:118.3,119.27 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:119.27,123.4 3 0 -code.squareroundforest.org/arpio/wand/formatman.go:126.2,126.76 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:126.76,134.15 8 0 -code.squareroundforest.org/arpio/wand/formatman.go:134.15,136.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:136.9,138.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:140.3,140.12 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:143.2,143.54 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:143.54,147.38 4 0 -code.squareroundforest.org/arpio/wand/formatman.go:147.38,148.13 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:148.13,150.5 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:152.4,152.21 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:152.21,154.13 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:157.4,157.19 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:157.19,159.20 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:159.20,161.6 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:163.5,164.13 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:168.3,175.15 8 0 -code.squareroundforest.org/arpio/wand/formatman.go:175.15,177.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:177.9,179.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:181.3,186.47 6 0 -code.squareroundforest.org/arpio/wand/formatman.go:190.58,197.32 7 0 -code.squareroundforest.org/arpio/wand/formatman.go:197.32,198.12 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:198.12,200.4 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:202.3,202.37 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:205.2,205.32 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:205.32,208.3 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:210.2,210.17 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:213.59,219.2 5 0 -code.squareroundforest.org/arpio/wand/formatman.go:221.46,223.37 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:223.37,224.34 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:224.34,225.12 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:228.3,229.8 2 0 -code.squareroundforest.org/arpio/wand/formatman.go:232.2,232.20 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:232.20,234.3 1 0 -code.squareroundforest.org/arpio/wand/formatman.go:236.2,236.41 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:10.31,13.2 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:15.32,22.23 3 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:22.23,23.13 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:24.68,25.29 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:26.12,27.17 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:27.17,29.5 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:31.4,31.23 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:32.11,34.34 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:38.2,38.19 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:41.99,45.29 4 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:45.29,47.3 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:49.2,49.49 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:49.49,51.3 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:53.2,53.37 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:53.37,55.3 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:57.2,62.37 5 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:62.37,63.25 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:63.25,65.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:67.3,67.10 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:68.27,69.70 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:70.16,71.54 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:72.16,73.54 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:77.2,77.33 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:77.33,80.38 3 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:80.38,82.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:84.3,84.17 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:87.2,87.27 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:87.27,90.3 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:92.2,92.26 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:92.26,94.25 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:94.25,96.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:98.3,98.25 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:98.25,100.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:102.3,102.47 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:102.47,104.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:106.3,107.27 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:107.27,109.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:112.2,112.76 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:112.76,122.15 10 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:122.15,124.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:124.9,126.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:128.3,129.17 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:132.2,132.54 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:132.54,138.38 6 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:138.38,139.21 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:139.21,141.13 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:144.4,144.19 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:144.19,146.20 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:146.20,148.6 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:150.5,151.13 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:155.3,163.15 9 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:163.15,165.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:165.9,167.4 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:169.3,177.17 9 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:181.74,187.32 6 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:187.32,189.3 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:191.2,192.32 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:192.32,195.3 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:197.2,197.17 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:200.75,205.2 4 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:207.62,209.37 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:209.37,210.34 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:210.34,211.12 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:214.3,215.8 2 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:218.2,218.20 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:218.20,220.3 1 0 -code.squareroundforest.org/arpio/wand/formatmarkdown.go:222.2,222.53 1 0 -code.squareroundforest.org/arpio/wand/help.go:68.17,73.2 1 1 -code.squareroundforest.org/arpio/wand/help.go:75.30,77.37 2 1 -code.squareroundforest.org/arpio/wand/help.go:77.37,79.25 2 0 -code.squareroundforest.org/arpio/wand/help.go:79.25,81.4 1 0 -code.squareroundforest.org/arpio/wand/help.go:84.2,84.38 1 1 -code.squareroundforest.org/arpio/wand/help.go:84.38,86.3 1 1 -code.squareroundforest.org/arpio/wand/help.go:88.2,88.12 1 1 -code.squareroundforest.org/arpio/wand/help.go:91.38,92.37 1 1 -code.squareroundforest.org/arpio/wand/help.go:92.37,93.16 1 1 -code.squareroundforest.org/arpio/wand/help.go:93.16,95.4 1 1 -code.squareroundforest.org/arpio/wand/help.go:98.2,98.14 1 0 -code.squareroundforest.org/arpio/wand/help.go:101.40,102.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:102.21,104.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:106.2,108.12 3 0 -code.squareroundforest.org/arpio/wand/help.go:111.64,112.28 1 1 -code.squareroundforest.org/arpio/wand/help.go:112.28,115.3 2 1 -code.squareroundforest.org/arpio/wand/help.go:117.2,117.31 1 0 -code.squareroundforest.org/arpio/wand/help.go:117.31,120.3 2 0 -code.squareroundforest.org/arpio/wand/help.go:123.31,124.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:124.21,126.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:128.2,132.30 5 0 -code.squareroundforest.org/arpio/wand/help.go:135.33,137.37 2 0 -code.squareroundforest.org/arpio/wand/help.go:137.37,139.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:141.2,141.43 1 0 -code.squareroundforest.org/arpio/wand/help.go:141.43,143.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:145.2,145.17 1 0 -code.squareroundforest.org/arpio/wand/help.go:148.59,150.25 2 0 -code.squareroundforest.org/arpio/wand/help.go:150.25,152.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:154.2,154.14 1 0 -code.squareroundforest.org/arpio/wand/help.go:157.46,158.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:158.21,160.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:162.2,168.24 7 0 -code.squareroundforest.org/arpio/wand/help.go:168.24,170.30 2 0 -code.squareroundforest.org/arpio/wand/help.go:170.30,172.4 1 0 -code.squareroundforest.org/arpio/wand/help.go:175.2,183.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:186.64,193.2 1 0 -code.squareroundforest.org/arpio/wand/help.go:195.43,196.23 1 0 -code.squareroundforest.org/arpio/wand/help.go:196.23,198.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:200.2,200.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:200.21,202.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:204.2,204.74 1 0 -code.squareroundforest.org/arpio/wand/help.go:207.70,208.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:208.21,210.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:212.2,213.46 2 0 -code.squareroundforest.org/arpio/wand/help.go:213.46,216.3 2 0 -code.squareroundforest.org/arpio/wand/help.go:218.2,221.23 4 0 -code.squareroundforest.org/arpio/wand/help.go:221.23,223.24 2 0 -code.squareroundforest.org/arpio/wand/help.go:223.24,225.4 1 0 -code.squareroundforest.org/arpio/wand/help.go:228.2,230.26 3 0 -code.squareroundforest.org/arpio/wand/help.go:230.26,238.26 2 0 -code.squareroundforest.org/arpio/wand/help.go:238.26,239.27 1 0 -code.squareroundforest.org/arpio/wand/help.go:239.27,241.5 1 0 -code.squareroundforest.org/arpio/wand/help.go:244.3,244.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:247.2,247.25 1 0 -code.squareroundforest.org/arpio/wand/help.go:247.25,254.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:256.2,256.10 1 0 -code.squareroundforest.org/arpio/wand/help.go:259.60,261.22 2 0 -code.squareroundforest.org/arpio/wand/help.go:261.22,264.3 2 0 -code.squareroundforest.org/arpio/wand/help.go:266.2,266.21 1 0 -code.squareroundforest.org/arpio/wand/help.go:266.21,269.3 2 0 -code.squareroundforest.org/arpio/wand/help.go:271.2,271.31 1 0 -code.squareroundforest.org/arpio/wand/help.go:271.31,273.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:275.2,275.13 1 0 -code.squareroundforest.org/arpio/wand/help.go:278.67,282.37 3 0 -code.squareroundforest.org/arpio/wand/help.go:282.37,284.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:286.2,288.28 3 0 -code.squareroundforest.org/arpio/wand/help.go:288.28,289.15 1 0 -code.squareroundforest.org/arpio/wand/help.go:289.15,291.4 1 0 -code.squareroundforest.org/arpio/wand/help.go:293.3,293.24 1 0 -code.squareroundforest.org/arpio/wand/help.go:293.24,295.4 1 0 -code.squareroundforest.org/arpio/wand/help.go:298.2,317.3 1 0 -code.squareroundforest.org/arpio/wand/help.go:320.80,323.2 2 0 -code.squareroundforest.org/arpio/wand/help.go:325.61,328.2 2 0 -code.squareroundforest.org/arpio/wand/help.go:330.77,333.2 2 0 -code.squareroundforest.org/arpio/wand/help.go:335.48,338.2 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:44.40,46.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:47.35,49.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:50.46,52.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:53.73,54.27 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:54.27,55.17 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:55.17,57.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:59.2,59.28 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:59.28,60.37 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:60.37,62.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:64.2,64.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:66.41,68.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:69.40,70.48 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:70.48,71.30 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:71.30,74.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:75.3,76.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:78.2,78.25 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:80.41,82.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:83.36,85.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:86.57,88.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:109.44,111.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:112.39,114.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:115.50,117.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:118.44,119.17 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:119.17,120.40 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:120.40,123.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:124.3,124.40 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:126.2,133.31 5 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:133.31,135.19 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:135.19,136.46 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:136.46,139.13 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:141.4,142.24 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:142.24,143.26 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:143.26,146.6 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:147.5,147.20 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:147.20,149.6 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:150.5,150.11 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:152.4,152.113 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:152.113,154.5 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:155.4,156.19 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:156.19,158.5 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:159.4,159.10 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:161.3,162.13 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:162.13,164.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:165.3,166.86 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:166.86,169.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:171.2,171.54 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:171.54,172.112 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:172.112,174.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:175.3,176.18 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:176.18,178.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:179.3,179.9 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:181.2,181.38 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:181.38,182.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:182.33,184.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:186.2,186.23 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:186.23,189.3 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:190.2,192.17 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:192.17,194.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:196.45,198.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:199.40,201.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:202.61,204.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:204.9,206.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:207.2,209.16 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:209.16,211.26 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:211.26,213.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:214.3,214.77 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:215.8,215.19 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:215.19,217.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:217.39,219.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:220.8,221.40 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:221.40,223.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:224.3,225.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:225.39,227.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:229.2,234.31 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:234.31,237.10 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:237.10,240.12 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:242.3,242.26 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:242.26,245.76 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:245.76,248.5 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:249.4,249.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:251.3,251.44 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:251.44,252.61 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:252.61,254.5 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:256.3,257.19 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:259.2,259.13 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:259.13,261.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:261.39,263.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:265.2,265.25 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:265.25,267.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:268.2,268.90 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:286.42,288.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:289.37,291.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:292.48,294.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:295.42,296.22 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:296.22,298.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:299.2,299.39 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:299.39,302.3 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:303.2,315.6 8 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:315.6,318.36 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:318.36,321.20 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:321.20,322.34 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:322.34,325.6 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:327.4,327.47 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:327.47,329.13 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:331.4,335.38 5 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:337.3,337.18 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:337.18,338.9 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:341.2,341.11 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:341.11,342.55 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:342.55,343.113 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:343.113,345.5 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:346.4,348.10 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:350.3,350.22 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:350.22,353.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:353.9,353.36 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:353.36,356.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:356.9,359.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:360.3,362.9 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:364.2,364.36 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:364.36,367.130 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:367.130,369.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:371.2,373.37 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:375.43,377.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:378.38,380.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:381.59,383.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:383.9,385.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:386.2,388.12 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:388.12,390.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:390.39,392.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:393.8,394.40 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:394.40,396.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:397.3,398.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:398.39,400.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:402.2,403.30 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:403.30,404.53 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:404.53,406.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:409.2,410.13 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:410.13,412.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:412.39,414.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:416.2,416.25 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:416.25,418.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:419.2,419.86 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:424.36,426.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:427.29,429.21 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:429.21,430.21 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:430.21,432.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:432.9,434.37 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:434.37,436.5 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:439.2,439.26 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:441.31,443.21 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:443.21,445.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:446.2,446.27 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:448.34,450.21 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:450.21,452.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:453.2,453.35 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:462.57,463.24 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:463.24,465.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:466.2,466.24 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:466.24,469.3 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:470.2,471.39 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:471.39,473.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:474.2,474.13 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:476.57,477.23 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:477.23,479.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:480.2,480.23 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:480.23,483.3 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:484.2,485.38 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:485.38,487.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:488.2,488.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:490.48,492.47 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:492.47,493.61 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:493.61,494.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:496.3,496.9 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:498.2,498.51 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:500.46,501.27 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:501.27,502.48 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:502.48,503.32 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:503.32,504.13 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:506.4,506.10 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:509.2,510.30 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:510.30,512.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:513.2,513.27 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:515.55,516.28 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:516.28,518.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:519.2,519.47 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:519.47,520.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:520.31,521.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:523.3,523.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:523.33,525.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:527.2,527.14 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:529.60,530.28 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:530.28,532.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:533.2,535.47 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:535.47,536.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:536.31,537.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:539.3,539.32 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:539.32,541.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:542.3,542.15 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:544.2,544.18 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:546.67,547.86 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:547.86,549.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:550.2,551.19 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:553.51,554.47 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:554.47,555.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:555.31,556.12 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:558.3,558.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:558.33,561.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:564.34,566.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:567.48,568.28 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:568.28,570.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:571.2,571.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:571.33,572.35 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:572.35,574.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:576.2,576.14 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:578.47,580.33 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:580.33,581.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:581.31,584.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:586.2,586.51 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:588.49,589.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:589.33,590.35 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:590.35,592.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:613.62,615.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:616.31,617.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:617.31,619.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:620.2,621.16 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:621.16,622.20 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:622.20,623.14 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:623.14,626.5 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:627.9,630.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:632.2,633.38 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:633.38,636.3 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:637.2,638.13 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:640.40,641.31 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:641.31,643.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:644.2,644.30 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:644.30,645.16 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:645.16,647.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:649.2,649.33 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:651.46,653.9 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:653.9,655.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:656.2,656.7 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:656.7,658.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:658.8,660.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:661.2,661.13 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:663.48,666.15 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:666.15,668.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:669.2,669.32 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:669.32,672.36 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:672.36,674.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:676.2,676.14 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:678.35,681.21 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:681.21,683.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:685.36,688.2 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:689.61,691.24 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:691.24,693.24 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:693.24,696.4 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:698.2,698.8 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:700.46,703.19 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:703.19,705.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:706.2,706.28 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:706.28,708.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:709.2,710.91 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:712.52,714.15 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:714.15,716.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:717.2,718.61 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:718.61,720.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:721.2,722.11 2 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:722.11,724.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:725.2,725.22 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:725.22,727.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:728.2,728.27 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:738.32,740.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:741.32,743.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:744.30,746.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:791.38,793.2 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:794.79,797.22 3 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:797.22,799.3 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:800.2,800.43 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:800.43,801.40 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:801.40,803.4 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:804.3,804.18 1 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:806.2,809.18 4 0 -code.squareroundforest.org/arpio/wand/iniparser.gen.go:814.40,1050.2 232 0 -code.squareroundforest.org/arpio/wand/input.go:9.103,11.38 2 1 -code.squareroundforest.org/arpio/wand/input.go:11.38,13.10 2 0 -code.squareroundforest.org/arpio/wand/input.go:13.10,14.12 1 0 -code.squareroundforest.org/arpio/wand/input.go:17.3,17.24 1 0 -code.squareroundforest.org/arpio/wand/input.go:17.24,18.46 1 0 -code.squareroundforest.org/arpio/wand/input.go:18.46,24.5 1 0 -code.squareroundforest.org/arpio/wand/input.go:26.4,26.29 1 0 -code.squareroundforest.org/arpio/wand/input.go:26.29,27.28 1 0 -code.squareroundforest.org/arpio/wand/input.go:27.28,32.6 1 0 -code.squareroundforest.org/arpio/wand/input.go:37.2,37.12 1 1 -code.squareroundforest.org/arpio/wand/input.go:40.46,42.2 1 1 -code.squareroundforest.org/arpio/wand/input.go:44.40,46.2 1 1 -code.squareroundforest.org/arpio/wand/input.go:48.62,51.46 3 1 -code.squareroundforest.org/arpio/wand/input.go:51.46,55.3 3 0 -code.squareroundforest.org/arpio/wand/input.go:57.2,58.23 2 1 -code.squareroundforest.org/arpio/wand/input.go:58.23,60.42 2 1 -code.squareroundforest.org/arpio/wand/input.go:60.42,62.4 1 0 -code.squareroundforest.org/arpio/wand/input.go:64.3,64.28 1 1 -code.squareroundforest.org/arpio/wand/input.go:67.2,68.31 2 1 -code.squareroundforest.org/arpio/wand/input.go:68.31,73.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:75.2,75.24 1 1 -code.squareroundforest.org/arpio/wand/input.go:75.24,77.26 2 1 -code.squareroundforest.org/arpio/wand/input.go:77.26,79.4 1 0 -code.squareroundforest.org/arpio/wand/input.go:81.3,82.18 2 1 -code.squareroundforest.org/arpio/wand/input.go:82.18,84.4 1 0 -code.squareroundforest.org/arpio/wand/input.go:86.3,86.24 1 1 -code.squareroundforest.org/arpio/wand/input.go:86.24,87.42 1 1 -code.squareroundforest.org/arpio/wand/input.go:87.42,93.5 1 0 -code.squareroundforest.org/arpio/wand/input.go:95.4,95.26 1 1 -code.squareroundforest.org/arpio/wand/input.go:95.26,96.57 1 1 -code.squareroundforest.org/arpio/wand/input.go:96.57,101.6 1 0 -code.squareroundforest.org/arpio/wand/input.go:103.5,103.59 1 1 -code.squareroundforest.org/arpio/wand/input.go:103.59,108.6 1 0 -code.squareroundforest.org/arpio/wand/input.go:113.2,113.12 1 1 -code.squareroundforest.org/arpio/wand/input.go:116.56,130.18 11 1 -code.squareroundforest.org/arpio/wand/input.go:130.18,133.3 2 0 -code.squareroundforest.org/arpio/wand/input.go:135.2,135.29 1 1 -code.squareroundforest.org/arpio/wand/input.go:135.29,137.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:139.2,139.27 1 1 -code.squareroundforest.org/arpio/wand/input.go:139.27,141.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:143.2,143.18 1 1 -code.squareroundforest.org/arpio/wand/input.go:143.18,145.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:147.2,147.30 1 1 -code.squareroundforest.org/arpio/wand/input.go:147.30,149.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:151.2,151.23 1 1 -code.squareroundforest.org/arpio/wand/input.go:151.23,152.57 1 1 -code.squareroundforest.org/arpio/wand/input.go:152.57,153.12 1 1 -code.squareroundforest.org/arpio/wand/input.go:156.3,157.18 2 1 -code.squareroundforest.org/arpio/wand/input.go:157.18,159.4 1 0 -code.squareroundforest.org/arpio/wand/input.go:159.9,161.4 1 1 -code.squareroundforest.org/arpio/wand/input.go:163.3,163.37 1 1 -code.squareroundforest.org/arpio/wand/input.go:163.37,164.12 1 1 -code.squareroundforest.org/arpio/wand/input.go:167.3,167.23 1 1 -code.squareroundforest.org/arpio/wand/input.go:167.23,173.4 1 1 -code.squareroundforest.org/arpio/wand/input.go:176.2,176.12 1 1 -code.squareroundforest.org/arpio/wand/input.go:179.81,180.47 1 1 -code.squareroundforest.org/arpio/wand/input.go:180.47,182.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:184.2,184.44 1 1 -code.squareroundforest.org/arpio/wand/input.go:184.44,186.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:188.2,188.63 1 1 -code.squareroundforest.org/arpio/wand/input.go:188.63,190.3 1 0 -code.squareroundforest.org/arpio/wand/input.go:192.2,192.67 1 1 -code.squareroundforest.org/arpio/wand/input.go:192.67,194.3 1 1 -code.squareroundforest.org/arpio/wand/input.go:196.2,196.12 1 1 -code.squareroundforest.org/arpio/wand/output.go:10.46,11.35 1 1 -code.squareroundforest.org/arpio/wand/output.go:11.35,13.3 1 0 -code.squareroundforest.org/arpio/wand/output.go:15.2,15.23 1 1 -code.squareroundforest.org/arpio/wand/output.go:15.23,17.9 2 1 -code.squareroundforest.org/arpio/wand/output.go:17.9,18.43 1 1 -code.squareroundforest.org/arpio/wand/output.go:18.43,20.5 1 0 -code.squareroundforest.org/arpio/wand/output.go:22.4,22.12 1 1 -code.squareroundforest.org/arpio/wand/output.go:25.3,26.52 2 1 -code.squareroundforest.org/arpio/wand/output.go:26.52,27.49 1 0 -code.squareroundforest.org/arpio/wand/output.go:27.49,29.5 1 0 -code.squareroundforest.org/arpio/wand/output.go:32.3,33.19 2 1 -code.squareroundforest.org/arpio/wand/output.go:49.26,50.49 1 1 -code.squareroundforest.org/arpio/wand/output.go:50.49,52.5 1 0 -code.squareroundforest.org/arpio/wand/output.go:53.11,54.54 1 0 -code.squareroundforest.org/arpio/wand/output.go:54.54,56.5 1 0 -code.squareroundforest.org/arpio/wand/output.go:58.4,58.45 1 0 -code.squareroundforest.org/arpio/wand/output.go:58.45,60.5 1 0 -code.squareroundforest.org/arpio/wand/output.go:64.2,64.12 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:30.58,31.19 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:31.19,33.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:35.2,35.33 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:35.33,40.3 4 1 -code.squareroundforest.org/arpio/wand/reflect.go:42.2,45.10 4 1 -code.squareroundforest.org/arpio/wand/reflect.go:48.60,49.21 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:49.21,51.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:53.2,53.38 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:53.38,55.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:57.2,57.10 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:60.36,62.2 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:64.36,66.2 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:68.36,71.2 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:73.54,75.9 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:76.34,77.45 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:78.34,79.46 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:80.33,81.45 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:82.10,83.42 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:87.56,89.9 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:90.34,91.46 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:92.34,93.47 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:94.33,95.46 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:96.10,97.43 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:101.45,102.18 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:103.20,105.20 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:106.78,108.20 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:109.83,111.20 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:112.40,114.20 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:115.22,116.14 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:117.10,118.15 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:122.41,124.18 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:125.20,127.46 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:128.78,130.46 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:131.83,133.46 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:134.40,136.46 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:137.10,138.46 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:141.2,141.29 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:144.87,145.17 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:145.17,147.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:149.2,154.39 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:154.39,161.21 7 1 -code.squareroundforest.org/arpio/wand/reflect.go:175.19,181.6 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:182.26,183.28 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:183.28,190.5 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:191.23,192.20 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:192.20,194.5 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:196.4,196.22 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:196.22,198.5 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:200.4,202.18 3 0 -code.squareroundforest.org/arpio/wand/reflect.go:202.18,204.5 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:206.4,206.20 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:206.20,208.5 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:208.10,209.24 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:209.24,213.6 3 0 -code.squareroundforest.org/arpio/wand/reflect.go:215.5,215.46 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:220.2,221.32 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:221.32,223.3 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:225.2,225.33 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:225.33,227.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:229.2,230.24 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:230.24,232.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:234.2,235.16 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:235.16,237.3 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:239.2,239.30 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:242.40,245.2 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:247.36,249.23 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:249.23,250.36 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:250.36,252.4 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:255.2,255.10 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:258.45,265.23 7 1 -code.squareroundforest.org/arpio/wand/reflect.go:265.23,267.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:269.2,269.11 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:272.81,274.33 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:274.33,277.11 3 1 -code.squareroundforest.org/arpio/wand/reflect.go:277.11,279.4 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:282.2,282.10 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:285.58,286.55 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:286.55,288.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:291.52,297.23 2 1 -code.squareroundforest.org/arpio/wand/reflect.go:297.23,298.10 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:299.21,300.30 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:301.21,302.30 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:306.2,306.23 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:309.54,310.55 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:310.55,312.3 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:315.46,316.16 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:316.16,318.3 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:320.2,322.19 3 0 -code.squareroundforest.org/arpio/wand/reflect.go:323.20,324.35 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:325.78,326.20 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:327.79,328.15 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:329.11,330.16 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:332.83,333.20 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:334.84,335.15 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:336.11,337.16 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:339.40,340.20 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:341.41,342.15 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:343.11,344.16 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:346.22,347.37 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:348.25,349.86 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:350.10,351.15 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:355.43,356.31 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:356.31,358.3 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:360.2,360.18 1 1 -code.squareroundforest.org/arpio/wand/reflect.go:361.38,362.35 1 0 -code.squareroundforest.org/arpio/wand/reflect.go:363.10,364.15 1 1 -code.squareroundforest.org/arpio/wand/wand.go:32.57,34.2 1 1 -code.squareroundforest.org/arpio/wand/wand.go:36.27,39.2 2 0 -code.squareroundforest.org/arpio/wand/wand.go:41.38,45.2 3 0 -code.squareroundforest.org/arpio/wand/wand.go:47.49,49.33 2 0 -code.squareroundforest.org/arpio/wand/wand.go:49.33,54.3 1 0 -code.squareroundforest.org/arpio/wand/wand.go:56.2,56.12 1 0 -code.squareroundforest.org/arpio/wand/wand.go:59.43,66.2 2 0 -code.squareroundforest.org/arpio/wand/wand.go:68.41,72.2 1 0 -code.squareroundforest.org/arpio/wand/wand.go:74.41,76.28 2 0 -code.squareroundforest.org/arpio/wand/wand.go:76.28,78.3 1 0 -code.squareroundforest.org/arpio/wand/wand.go:80.2,80.13 1 0 -code.squareroundforest.org/arpio/wand/wand.go:83.19,85.29 1 0 -code.squareroundforest.org/arpio/wand/wand.go:85.29,87.4 1 0 -code.squareroundforest.org/arpio/wand/wand.go:91.26,94.30 1 0 -code.squareroundforest.org/arpio/wand/wand.go:94.30,98.5 1 0 -code.squareroundforest.org/arpio/wand/wand.go:101.30,105.5 1 0 -code.squareroundforest.org/arpio/wand/wand.go:110.32,112.2 1 0 -code.squareroundforest.org/arpio/wand/wand.go:114.28,116.2 1 0 -code.squareroundforest.org/arpio/wand/wand.go:118.37,120.2 1 0 diff --git a/.gitignore b/.gitignore index 24e5b0a..95f602d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .build +.cover diff --git a/Makefile b/Makefile index 77727c5..37f610c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,6 @@ iniparser.gen.go: ini.treerack docreflect.gen.go: $(SOURCES) go run script/docreflect/docs.go \ wand \ - code.squareroundforest.org/arpio/docreflect/generate \ code.squareroundforest.org/arpio/wand/tools \ > docreflect.gen.go \ || rm -f docreflect.gen.go @@ -44,6 +43,7 @@ install: .build/wand cp .build/wand ~/bin clean: + go clean ./... rm -rf .build rm -f docreflect.gen.go rm -f iniparser.gen.go diff --git a/apply.go b/apply.go index 47eda7b..ad6390b 100644 --- a/apply.go +++ b/apply.go @@ -1,188 +1,61 @@ package wand import ( - "github.com/iancoleman/strcase" "io" "reflect" - "strings" ) -func ensurePointerAllocation(p reflect.Value, n int) { - if p.IsNil() { - p.Set(reflect.New(p.Type().Elem())) - } - - ensureAllocation(p.Elem(), n) -} - -func ensureSliceAllocation(s reflect.Value, n int) { - if s.Len() < n { - a := reflect.MakeSlice(s.Type(), n-s.Len(), n-s.Len()) - a = reflect.AppendSlice(s, a) - s.Set(a) - } - - if s.Len() > n { - a := s.Slice(0, n) - s.Set(a) - } - - for i := 0; i < s.Len(); i++ { - ensureAllocation(s.Index(i), 1) - } -} - -func ensureAllocation(v reflect.Value, n int) { - switch v.Type().Kind() { - case reflect.Pointer: - ensurePointerAllocation(v, n) - case reflect.Slice: - ensureSliceAllocation(v, n) - } -} - -func setPointerValue(p reflect.Value, v []value) { - setFieldValue(p.Elem(), v) -} - -func setSliceValue(s reflect.Value, v []value) { - for i := 0; i < s.Len(); i++ { - setFieldValue(s.Index(i), v[i:i+1]) - } -} - -func setValue(f reflect.Value, v value) { - if v.isBool { - f.Set(reflect.ValueOf(v.boolean)) - return - } - - f.Set(reflect.ValueOf(scan(f.Type(), v.str))) -} - -func setFieldValue(field reflect.Value, v []value) { - switch field.Kind() { - case reflect.Pointer: - setPointerValue(field, v) - case reflect.Slice: - setSliceValue(field, v) - default: - setValue(field, v[0]) - } -} - -func setField(s reflect.Value, name string, v []value) { - for i := 0; i < s.Type().NumField(); i++ { - fs := s.Type().Field(i) - fname := strcase.ToKebab(fs.Name) - ft := fs.Type - ftup := unpack(ft) - fv := s.Field(i) - switch { - case !fs.Anonymous && fname == name: - ensureAllocation(fv, len(v)) - setFieldValue(fv, v) - case !fs.Anonymous && ftup.Kind() == reflect.Struct: - prefix := fname + "-" - if strings.HasPrefix(name, prefix) { - ensureAllocation(fv, len(v)) - setField(unpack(fv), name[len(prefix):], v) - } - case fs.Anonymous: - ensureAllocation(fv, 1) - setField(unpack(fv), name, v) +func bindKeyVals(receiver reflect.Value, keyVals map[string][]string) bool { + v := make(map[string][]any) + for name, values := range keyVals { + for _, vi := range values { + v[name] = append(v[name], vi) } } + + u := bindFields(receiver, v) + return len(v) > 0 && len(u) < len(v) +} + +func bindOptions(receiver reflect.Value, shortForms []string, o []option) bool { + ms := make(map[string]string) + for i := 0; i < len(shortForms); i += 2 { + ms[shortForms[i]] = shortForms[i + 1] + } + + v := make(map[string][]any) + for _, oi := range o { + n := oi.name + if oi.shortForm { + n = ms[n] + } + + var val any + if oi.value.isBool { + val = oi.value.boolean + } else { + val = oi.value.str + } + + v[n] = append(v[n], val) + } + + u := bindFields(receiver, v) + return len(v) > 0 && len(u) < len(v) } func createStructArg(t reflect.Type, shortForms []string, c config, e env, o []option) (reflect.Value, bool) { - tup := unpack(t) - f := fields(tup) - fn := make(map[string]bool) - for _, fi := range f { - fn[fi.name] = true - } - - ms := make(map[string]string) - for i := 0; i < len(shortForms); i += 2 { - l, s := shortForms[i], shortForms[i+1] - ms[s] = l - } - - om := make(map[string][]option) - for _, oi := range o { - n := oi.name - if l, ok := ms[n]; ok && oi.shortForm { - n = l - } - - om[n] = append(om[n], oi) - } - - var foundConfig []string - for n := range c.values { - if fn[n] { - foundConfig = append(foundConfig, n) - } - } - - var foundEnv []string - for n := range e.values { - if fn[n] { - foundEnv = append(foundEnv, n) - } - } - - var foundOptions []string - for n := range om { - if fn[n] { - foundOptions = append(foundOptions, n) - } - } - - if len(foundConfig) == 0 && len(foundEnv) == 0 && len(foundOptions) == 0 { - return reflect.Zero(t), false - } - - p := reflect.New(tup) - for _, n := range foundConfig { - var v []value - for _, vi := range c.values[n] { - v = append(v, stringValue(vi)) - } - - setField(p.Elem(), n, v) - } - - for _, n := range foundEnv { - var v []value - for _, vi := range e.values[n] { - v = append(v, stringValue(vi)) - } - - setField(p.Elem(), n, v) - } - - for _, n := range foundOptions { - var v []value - for _, oi := range om[n] { - v = append(v, oi.value) - } - - setField(p.Elem(), n, v) - } - - return pack(p.Elem(), t), true + r := allocate(t) + hasConfigMatches := bindKeyVals(r, c.values) + hasEnvMatches := bindKeyVals(r, e.values) + hasOptionMatches := bindOptions(r, shortForms, o) + return r, hasConfigMatches || hasEnvMatches || hasOptionMatches } func createPositional(t reflect.Type, v string) reflect.Value { - if t.Kind() == reflect.Interface { - return reflect.ValueOf(v) - } - - tup := unpack(t) - sv := reflect.ValueOf(scan(tup, v)) - return pack(sv, t) + r := allocate(t) + bindScalar(r, v) + return r } func createArgs(stdin io.Reader, stdout io.Writer, t reflect.Type, shortForms []string, c config, e env, cl commandLine) []reflect.Value { @@ -246,7 +119,7 @@ func processResults(t reflect.Type, out []reflect.Value) ([]any, error) { func apply(stdin io.Reader, stdout io.Writer, cmd Cmd, c config, e env, cl commandLine) ([]any, error) { v := reflect.ValueOf(cmd.impl) - v = unpack(v) + v = unpackValue(v) t := v.Type() args := createArgs(stdin, stdout, t, cmd.shortForms, c, e, cl) out := v.Call(args) diff --git a/command.go b/command.go index 4bab4d6..2c3f896 100644 --- a/command.go +++ b/command.go @@ -5,7 +5,7 @@ import ( "fmt" "reflect" "regexp" - "slices" + "code.squareroundforest.org/arpio/bind" ) var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9]*$") @@ -19,103 +19,41 @@ func wrap(impl any) Cmd { return Command("", impl) } -func validateFields(f []field, conf Config) error { +func validateFields(f []bind.Field, conf Config) error { hasConfigFromOption := hasConfigFromOption(conf) - mf := make(map[string]field) + mf := make(map[string]bind.Field) for _, fi := range f { - if ef, ok := mf[fi.name]; ok && !compatibleTypes(fi.typ, ef.typ) { - return fmt.Errorf("duplicate fields with different types: %s", fi.name) + if ef, ok := mf[fi.Name()]; ok && !compatibleTypes(fi.Type(), ef.Type()) { + return fmt.Errorf("duplicate fields with different types: %s", fi.Name()) } - if hasConfigFromOption && fi.name == "config" { + if hasConfigFromOption && fi.Name() == "config" { return errors.New("option reserved for config file shadowed by struct field") } - mf[fi.name] = fi + mf[fi.Name()] = fi } return nil } -func validateParameter(visited map[reflect.Type]bool, t reflect.Type) error { - switch t.Kind() { - case reflect.Bool, - reflect.Int, - reflect.Int8, - reflect.Int16, - reflect.Int32, - reflect.Int64, - reflect.Uint, - reflect.Uint8, - reflect.Uint16, - reflect.Uint32, - reflect.Uint64, - reflect.Float32, - reflect.Float64, - reflect.String: - return nil - case reflect.Pointer, - reflect.Slice: - if visited[t] { - return fmt.Errorf("circular type definitions not supported: %s", t.Name()) - } - - if visited == nil { - visited = make(map[reflect.Type]bool) - } - - visited[t] = true - t = unpack(t) - return validateParameter(visited, t) - case reflect.Interface: - if t.NumMethod() > 0 { - return errors.New("non-empty interface parameter") - } - - return nil - default: - return fmt.Errorf("unsupported parameter type: %v", t) - } -} - -func validatePositional(t reflect.Type, min, max int) error { - p := positionalParameters(t) - ior, iow := ioParameters(p) - if len(ior) > 1 || len(iow) > 1 { - return errors.New("only zero or one reader and zero or one writer parameters is supported") - } - - for i, pi := range p { - if slices.Contains(ior, i) || slices.Contains(iow, i) { - continue - } - - if err := validateParameter(nil, pi); err != nil { - return err - } - } - - last := t.NumIn() - 1 - lastVariadic := t.IsVariadic() && - !isStruct(t.In(last)) && - !slices.Contains(ior, last) && - !slices.Contains(iow, last) - fixedPositional := len(p) - len(ior) - len(iow) - if lastVariadic { +func validatePositional(p []reflect.Type, variadic bool, min, max int) error { + fixedPositional := len(p) + if variadic { fixedPositional-- } if min > 0 && min < fixedPositional { return fmt.Errorf( - "minimum positional defined as %d but the implementation expects minimum %d fixed parameters", + "minimum positional arguments defined as %d but the implementation expects minimum %d fixed parameters", min, fixedPositional, ) } - if min > 0 && min > fixedPositional && !lastVariadic { + if min > 0 && min > fixedPositional && !variadic { return fmt.Errorf( - "minimum positional defined as %d but the implementation has only %d fixed parameters and no variadic parameter", + "minimum positional arguments defined as %d but the implementation has only %d fixed parameters and no variadic parameter", min, fixedPositional, ) @@ -123,7 +61,7 @@ func validatePositional(t reflect.Type, min, max int) error { if max > 0 && max < fixedPositional { return fmt.Errorf( - "maximum positional defined as %d but the implementation expects minimum %d fixed parameters", + "maximum positional arguments defined as %d but the implementation expects minimum %d fixed parameters", max, fixedPositional, ) @@ -131,7 +69,7 @@ func validatePositional(t reflect.Type, min, max int) error { if min > 0 && max > 0 && min > max { return fmt.Errorf( - "minimum positional defined as larger then the maxmimum positional: %d > %d", + "minimum positional arguments defined as larger then the maxmimum positional: %d > %d", min, max, ) @@ -140,65 +78,45 @@ func validatePositional(t reflect.Type, min, max int) error { return nil } +func validateIOParameters(ior, iow []reflect.Type) error { + if len(ior) > 1 || len(iow) > 1 { + return errors.New("only zero or one reader and zero or one writer parameter is supported") + } + + return nil +} + func validateImpl(cmd Cmd, conf Config) error { - v := reflect.ValueOf(cmd.impl) - v = unpack(v) - t := v.Type() - if t.Kind() != reflect.Func { - return errors.New("command implementation not a function") + if !isFunc(cmd.impl) { + return errors.New("command implementation must be a function or a pointer to a function") } - s := structParameters(t) - f, err := fieldsChecked(nil, s...) - if err != nil { - return err + p := parameters(cmd.impl) + for _, pi := range p { + if !isReader(pi) && !isWriter(pi) && !bindable(pi) { + return fmt.Errorf("unsupported parameter type: %s", pi.Name()) + } } + f := fields(cmd.impl) if err := validateFields(f, conf); err != nil { return err } - if err := validatePositional(t, cmd.minPositional, cmd.maxPositional); err != nil { + pos, variadic := positional(cmd.impl) + if err := validatePositional(pos, variadic, cmd.minPositional, cmd.maxPositional); err != nil { + return err + } + + ior, iow := ioParameters(cmd.impl) + if err := validateIOParameters(ior, iow); err != nil { return err } return nil } -func validateShortForms(cmd Cmd, assignedShortForms map[string]string) error { - mf := mapFields(cmd.impl) - if len(cmd.shortForms)%2 != 0 { - return fmt.Errorf( - "undefined option short form: %s", cmd.shortForms[len(cmd.shortForms)-1], - ) - } - - for i := 0; i < len(cmd.shortForms); i += 2 { - fn := cmd.shortForms[i] - sf := cmd.shortForms[i+1] - if len(sf) != 1 && (sf[0] < 'a' || sf[0] > 'z') { - return fmt.Errorf("invalid short form: %s", sf) - } - - if _, ok := mf[sf]; ok { - return fmt.Errorf("short form shadowing field name: %s", sf) - } - - if _, ok := mf[fn]; !ok { - continue - } - - if lf, ok := assignedShortForms[sf]; ok && lf != fn { - return fmt.Errorf("ambigous short form: %s", sf) - } - - assignedShortForms[sf] = fn - } - - return nil -} - -func validateCommandTree(cmd Cmd, conf Config, assignedShortForms map[string]string, root bool) error { +func validateCommandTree(cmd Cmd, conf Config) error { if cmd.isHelp { return nil } @@ -207,35 +125,25 @@ func validateCommandTree(cmd Cmd, conf Config, assignedShortForms map[string]str return nil } - if !root && !commandNameExpression.MatchString(cmd.name) { - return fmt.Errorf("command name is not a valid symbol: '%s'", cmd.name) - } - if cmd.impl == nil && !cmd.group { return fmt.Errorf("command does not have an implementation: %s", cmd.name) } + if cmd.impl == nil && len(cmd.subcommands) == 0 { + return fmt.Errorf("empty command category: %s", cmd.name) + } + if cmd.impl != nil { if err := validateImpl(cmd, conf); err != nil { return fmt.Errorf("%s: %w", cmd.name, err) } } - if cmd.impl == nil && len(cmd.subcommands) == 0 { - return fmt.Errorf("empty command category: %s", cmd.name) - } - - if cmd.impl != nil { - if err := validateShortForms(cmd, assignedShortForms); err != nil { - return fmt.Errorf("%s: %w", cmd.name, err) - } - } - var hasDefault bool names := make(map[string]bool) for _, s := range cmd.subcommands { - if s.name == "" { - return fmt.Errorf("unnamed subcommand of: %s", cmd.name) + if !commandNameExpression.MatchString(s.name) { + return fmt.Errorf("command name is not a valid symbol: '%s'", cmd.name) } if names[s.name] { @@ -243,13 +151,9 @@ func validateCommandTree(cmd Cmd, conf Config, assignedShortForms map[string]str } names[s.name] = true - if err := validateCommandTree(s, conf, assignedShortForms, false); err != nil { - return fmt.Errorf("%s: %w", s.name, err) - } - if s.isDefault && cmd.impl != nil { return fmt.Errorf( - "default subcommand defined for a command with explicit implementation: %s, %s", + "default subcommand defined for a command that has an explicit implementation: %s, %s", cmd.name, s.name, ) @@ -262,36 +166,174 @@ func validateCommandTree(cmd Cmd, conf Config, assignedShortForms map[string]str if s.isDefault { hasDefault = true } - } - return nil -} - -func allShortForms(cmd Cmd) []string { - var sf []string - for _, sc := range cmd.subcommands { - sf = append(sf, allShortForms(sc)...) - } - - for i := 0; i < len(cmd.shortForms); i += 2 { - sf = append(sf, cmd.shortForms[i]) - } - - return sf -} - -func validateCommand(cmd Cmd, conf Config) error { - assignedShortForms := make(map[string]string) - if err := validateCommandTree(cmd, conf, assignedShortForms, true); err != nil { - return err - } - - asf := allShortForms(cmd) - for _, sf := range asf { - if _, ok := assignedShortForms[sf]; !ok { - return fmt.Errorf("unassigned option short form: %s", sf) + if err := validateCommandTree(s, conf); err != nil { + return fmt.Errorf("%s: %w", s.name, err) } } return nil } + +func checkShortFormDefinition(existing map[string]string, short, long string) error { + e, ok := existing[short] + if !ok { + return nil + } + + if e == long { + return nil + } + + return fmt.Errorf( + "using the same short form for different options is not allowed: %s->%s, %s->%s", + short, long, short, e, + ) +} + +func collectMappedShortForms(to, from map[string]string) (map[string]string, error) { + for s, l := range from { + if err := checkShortFormDefinition(to, s, l); err != nil { + return nil, err + } + + if to == nil { + to = make(map[string]string) + } + + to[s] = l + } + + return to, nil +} + +func validateShortFormsTree(cmd Cmd) (map[string]string, map[string]string, error) { + var mapped, unmapped map[string]string + for _, sc := range cmd.subcommands { + m, um, err := validateShortFormsTree(sc) + if err != nil { + return nil, nil, err + } + + if mapped, err = collectMappedShortForms(mapped, m); err != nil { + return nil, nil, err + } + + if unmapped, err = collectMappedShortForms(unmapped, um); err != nil { + return nil, nil, err + } + } + + if len(cmd.shortForms) % 2 != 0 { + return nil, nil, fmt.Errorf("unassigned short form: %s", cmd.shortForms[len(cmd.shortForms) - 1]) + } + + mf := mapFields(cmd.impl) + for i := 0; i < len(cmd.shortForms); i += 2 { + s, l := cmd.shortForms[i], cmd.shortForms[i + 1] + r := []rune(s) + if len(r) != 1 || r[0] < 'a' || r[0] > 'z' { + return nil, nil, fmt.Errorf("invalid short form: %s", s) + } + + if err := checkShortFormDefinition(mapped, s, l); err != nil { + return nil, nil, err + } + + if err := checkShortFormDefinition(unmapped, s, l); err != nil { + return nil, nil, err + } + + _, hasField := mf[l] + _, isMapped := mapped[s] + if !hasField && !isMapped { + if unmapped == nil { + unmapped = make(map[string]string) + } + + unmapped[s] = l + continue + } + + if mapped == nil { + mapped = make(map[string]string) + } + + delete(unmapped, s) + mapped[s] = l + } + + return mapped, unmapped, nil +} + +func validateShortForms(cmd Cmd) error { + _, um, err := validateShortFormsTree(cmd) + if err != nil { + return err + } + + if len(um) != 0 { + return errors.New("unmapped short forms") + } + + return nil +} + +func validateCommand(cmd Cmd, conf Config) error { + if err := validateCommandTree(cmd, conf); err != nil { + return err + } + + if err := validateShortForms(cmd); err != nil { + return err + } + + return nil +} + +func insertHelpOption(names []string) []string { + for _, n := range names { + if n == "help" { + return names + } + } + + return append(names, "help") +} + +func insertHelpShortForm(shortForms []string) []string { + for _, sf := range shortForms { + if sf == "h" { + return shortForms + } + } + + return append(shortForms, "h") +} + +func boolOptions(cmd Cmd) []string { + f := fields(cmd.impl) + b := boolFields(f) + + var n []string + for _, fi := range b { + n = append(n, fi.Name()) + } + + n = insertHelpOption(n) + sfm := make(map[string][]string) + for i := 0; i < len(cmd.shortForms); i += 2 { + s, l := cmd.shortForms[i], cmd.shortForms[i+1] + sfm[l] = append(sfm[l], s) + } + + var sf []string + for _, ni := range n { + if sn, ok := sfm[ni]; ok { + sf = append(sf, sn...) + } + } + + sf = insertHelpShortForm(sf) + return append(n, sf...) +} diff --git a/commandline.go b/commandline.go index c04d3cb..33eb11c 100644 --- a/commandline.go +++ b/commandline.go @@ -1,11 +1,11 @@ package wand import ( - "reflect" "slices" "strconv" "strings" "unicode" + "code.squareroundforest.org/arpio/bind" ) type value struct { @@ -33,57 +33,6 @@ func stringValue(s string) value { return value{str: s} } -func insertHelpOption(names []string) []string { - for _, n := range names { - if n == "help" { - return names - } - } - - return append(names, "help") -} - -func insertHelpShortForm(shortForms []string) []string { - for _, sf := range shortForms { - if sf == "h" { - return shortForms - } - } - - return append(shortForms, "h") -} - -func boolOptions(cmd Cmd) []string { - v := reflect.ValueOf(cmd.impl) - v = unpack(v) - t := v.Type() - s := structParameters(t) - f := fields(s...) - b := boolFields(f) - - var n []string - for _, fi := range b { - n = append(n, fi.name) - } - - n = insertHelpOption(n) - sfm := make(map[string][]string) - for i := 0; i < len(cmd.shortForms); i += 2 { - l, s := cmd.shortForms[i], cmd.shortForms[i+1] - sfm[l] = append(sfm[l], s) - } - - var sf []string - for _, ni := range n { - if sn, ok := sfm[ni]; ok { - sf = append(sf, sn...) - } - } - - sf = insertHelpShortForm(sf) - return append(n, sf...) -} - func isOption(arg string) bool { a := []rune(arg) if len(a) <= 2 { @@ -325,15 +274,14 @@ func readArgs(boolOptions, args []string) commandLine { } func hasHelpOption(cmd Cmd, o []option) bool { - var mf map[string][]field + var mf map[string][]bind.Field if cmd.impl != nil { mf = mapFields(cmd.impl) } - sf := make(map[string]bool) + sf := make(map[string]string) for i := 0; i < len(cmd.shortForms); i += 2 { - s := cmd.shortForms[i+1] - sf[s] = true + sf[cmd.shortForms[i]] = cmd.shortForms[i + 1] } for _, oi := range o { @@ -341,14 +289,22 @@ func hasHelpOption(cmd Cmd, o []option) bool { continue } - if oi.name == "help" { - if _, ok := mf["help"]; !ok { - return true + n := oi.name + if oi.shortForm && n == "h" { + l, ok := sf["h"] + if !ok { + continue } + + if l != "help" { + continue + } + + n = "help" } - if oi.shortForm && oi.name == "h" { - if !sf["h"] { + if n == "help" { + if _, ok := mf["help"]; !ok { return true } } diff --git a/config.go b/config.go index d415dcb..76f2930 100644 --- a/config.go +++ b/config.go @@ -111,6 +111,7 @@ func readConfigFile(cmd Cmd, conf Config) (config, error) { name := strcase.ToKebab(key) c.originalNames[name] = key if !hasValue { + delete(c.values, name) c.discard = append(c.discard, name) continue } diff --git a/docreflect.gen.go b/docreflect.gen.go index 6f2f085..45379f1 100644 --- a/docreflect.gen.go +++ b/docreflect.gen.go @@ -1,62 +1,31 @@ +/* +Generated with https://code.squareroundforest.org/arpio/docreflect +*/ + + package wand import "code.squareroundforest.org/arpio/docreflect" func init() { -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate", "Package generate provides a generator to generate go code from go docs that registers doc entries\nfor use with the docreflect package.\n") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.GenerateRegistry", "GenerateRegistry generates a Go code file to the output, including a package init function that\nwill register the documentation of the declarations specified by their gopath.\n\nThe gopath argument accepts any number of package, package level symbol, of struct field paths.\nIt is recommended to use package paths unless special circumstances.\n\nSome important gotchas to keep in mind, GenerateRegistry does not resolve type references like\ntype aliases, or type definitions based on named types, and it doesn't follow import paths.\n\nfunc(w, outputPackageName, gopath)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.cleanPaths", "\nfunc(gopath)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.collectGoDirs", "\nfunc(o)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.findFieldDocs", "\nfunc(str, fieldPath)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.findGoMod", "\nfunc(dir)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.fixDocPackage", "\nfunc(p)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.format", "\nfunc(w, pname, docs)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.funcDocs", "\nfunc(f)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.funcParams", "\nfunc(f)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.generate", "\nfunc(o, gopaths)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.getGoroot", "\nfunc()") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.importPackages", "\nfunc(o, godirs, paths)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.initOptions", "\nfunc()") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.merge", "\nfunc(m)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.methodDocs", "\nfunc(importPath, t)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.modCache", "\nfunc()") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options", "") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.gomod", "") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.goroot", "") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.modules", "") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.options.wd", "") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packageDocs", "\nfunc(pkg)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packageFuncDocs", "\nfunc(importPath, funcs)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.packagePaths", "\nfunc(p)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.parsePackages", "\nfunc(pkgs)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.parserInclude", "\nfunc(pkg)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.readGomod", "\nfunc(wd)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.set", "\nfunc(m, key, value)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.splitGopath", "\nfunc(p)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.structFieldDocs", "\nfunc(t, fieldPath)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.structFieldsDocs", "\nfunc(importPath, t)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.symbolDocs", "\nfunc(pkg, gopath)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.symbolPath", "\nfunc(packagePath, name)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.takeDocs", "\nfunc(pkgs, gopaths)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.takeFieldDocs", "\nfunc(packagePath, prefix, f)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.typeDocs", "\nfunc(importPath, types)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.typeMethodDocs", "\nfunc(t, name)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.unpack", "\nfunc(e)") -docreflect.Register("code.squareroundforest.org/arpio/docreflect/generate.valueDocs", "\nfunc(packagePath, v)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools", "") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\nfunc(out, packageName, gopaths)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, function, args)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, stdin, args)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.Import", "") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.InlineImport", "") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, commandDir)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, commandDir)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.copyGomod", "\nfunc(mn, dst, src)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, o, commandDir)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions", "") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions.Level", "") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.commandReader", "\nfunc(in)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execCommandDir", "\nfunc(out, commandDir, env)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInternal", "\nfunc(command, args)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execTransparent", "\nfunc(command, args)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execWand", "\nfunc(o, args)") docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execc", "\nfunc(stdin, stdout, stderr, command, args, env)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.findGomod", "\nfunc(wd)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.functionHash", "\nfunc(function)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printFile", "\nfunc(fn, pkg, expression)") -docreflect.Register("code.squareroundforest.org/arpio/wand/tools.splitFunction", "\nfunc(function)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.hash", "\nfunc(expression, imports, inlineImports)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printGoFile", "\nfunc(fn, expression, imports, inlineImports)") +docreflect.Register("code.squareroundforest.org/arpio/wand/tools.readExec", "\nfunc(o, stdin)") } \ No newline at end of file diff --git a/docs.go b/docs.go new file mode 100644 index 0000000..88393ae --- /dev/null +++ b/docs.go @@ -0,0 +1,5 @@ +/* +Wand provides utilities for constructing command line applications from functions, with automatic parameter +binding from command line arguments, environment variables and configuration files. +*/ +package wand diff --git a/exec.go b/exec.go index 85b2ae2..56159ad 100644 --- a/exec.go +++ b/exec.go @@ -18,7 +18,7 @@ func exec(stdin io.Reader, stdout, stderr io.Writer, exit func(int), cmd Cmd, co return } - if os.Getenv("wandgenerate") == "man" { + if os.Getenv("_wandgenerate") == "man" { if err := generateMan(stdout, cmd, conf); err != nil { fmt.Fprintln(stderr, err) exit(1) @@ -27,8 +27,8 @@ func exec(stdin io.Reader, stdout, stderr io.Writer, exit func(int), cmd Cmd, co return } - if os.Getenv("wandgenerate") == "markdown" { - level, _ := strconv.Atoi(os.Getenv("wandmarkdownlevel")) + if os.Getenv("_wandgenerate") == "markdown" { + level, _ := strconv.Atoi(os.Getenv("_wandmarkdownlevel")) if err := generateMarkdown(stdout, cmd, conf, level); err != nil { fmt.Fprintln(stderr, err) exit(1) diff --git a/format.go b/format.go index f246716..85c605e 100644 --- a/format.go +++ b/format.go @@ -70,21 +70,6 @@ func lines(s string) string { return strings.Join(pp, "\n") } -func escapeTeletype(s string) string { - r := []rune(s) - for i := range r { - if r[i] >= 0x00 && r[i] <= 0x1f && r[i] != '\n' && r[i] != '\t' { - r[i] = 0xb7 - } - - if r[i] >= 0x7f && r[i] <= 0x9f { - r[i] = 0xb7 - } - } - - return string(r) -} - func manParagraphs(s string) string { p := paragraphs(s) pp := strings.Split(p, "\n\n") @@ -105,6 +90,21 @@ func manLines(s string) string { return strings.Join(ll, "\n") } +func escapeTeletype(s string) string { + r := []rune(s) + for i := range r { + if r[i] >= 0x00 && r[i] <= 0x1f && r[i] != '\n' && r[i] != '\t' { + r[i] = 0xb7 + } + + if r[i] >= 0x7f && r[i] <= 0x9f { + r[i] = 0xb7 + } + } + + return string(r) +} + func escapeRoff(s string) string { var ( rr []rune @@ -163,8 +163,9 @@ func escapeMD(s string) string { rr = append(rr, ri) default: rr = append(rr, ri) - lastDigit = ri >= 0 && ri <= 9 } + + lastDigit = ri >= 0 && ri <= 9 } return string(rr) diff --git a/go.mod b/go.mod index 3f69a13..9a144a8 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,15 @@ module code.squareroundforest.org/arpio/wand -go 1.24.6 +go 1.25.0 require ( - code.squareroundforest.org/arpio/docreflect v0.0.0-20250823192303-755a103f3788 - code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 + code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 + code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610 github.com/iancoleman/strcase v0.3.0 ) -require golang.org/x/mod v0.27.0 // indirect +require ( + code.squareroundforest.org/arpio/bind v0.0.0-20250831235903-9a6db08a25d0 // indirect + golang.org/x/mod v0.27.0 // indirect +) diff --git a/go.sum b/go.sum index 5474983..9465a5c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,19 @@ +code.squareroundforest.org/arpio/bind v0.0.0-20250831151900-af0bbca22e99 h1:1p3wtLY/USO+niU9d7yNqk/sbRluZ3/Xoo7L7gEF+ew= +code.squareroundforest.org/arpio/bind v0.0.0-20250831151900-af0bbca22e99/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI= +code.squareroundforest.org/arpio/bind v0.0.0-20250831235903-9a6db08a25d0 h1:dpekVQNpmH39MDNig+hA2IFF6J7TXPQNc8hTuENAn7A= +code.squareroundforest.org/arpio/bind v0.0.0-20250831235903-9a6db08a25d0/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI= code.squareroundforest.org/arpio/docreflect v0.0.0-20250823192303-755a103f3788 h1:jJoq0FdasFFDX1uJowXD8iyX/2G3gjwxtVEDyXtfeuw= code.squareroundforest.org/arpio/docreflect v0.0.0-20250823192303-755a103f3788/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250826190210-b092c9cb4c2e h1:FJ9rP44KGmiDZb+gGRH0ty209R05x2vR3wNPUG3HB4I= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250826190210-b092c9cb4c2e/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250826190339-b00034d8ca42 h1:w9JPDwsnPvDC70helP9RNL2lnRj+ab2eVgv9fK56kIg= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250826190339-b00034d8ca42/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 h1:QUCgxUEA5/ng7GwRnzb/WezmFQXSHXl48GdLJc0KC5k= +code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA= code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 h1:DKMSagVY3uyRhJ4ohiwQzNnR6CWdVKLkg97A8eQGxQU= code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY= +code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I= +code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY= code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610 h1:I0jebdyQQfqJcwq2lT/TkUPBU8secHa5xZ+VzOdYVsw= code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610/go.mod h1:9XhPcVt1Y1M609z02lHvEcp00dwPD9NUCoVxS2TpcH8= github.com/aryszka/notation v0.0.0-20230129164653-172017dde5e4 h1:JzqT9RArcw2sD4QPAyTss/sHaCZvCv+91DDJPZOrShw= diff --git a/help.go b/help.go index 862ad8a..757b273 100644 --- a/help.go +++ b/help.go @@ -2,6 +2,7 @@ package wand import ( "code.squareroundforest.org/arpio/docreflect" + "code.squareroundforest.org/arpio/bind" "fmt" "io" "reflect" @@ -67,10 +68,11 @@ type ( } ) -func help() Cmd { +func help(sf []string) Cmd { return Cmd{ - name: "help", - isHelp: true, + name: "help", + isHelp: true, + shortForms: sf, } } @@ -84,7 +86,7 @@ func insertHelp(cmd Cmd) Cmd { } if !hasHelpCmd && cmd.version == "" { - cmd.subcommands = append(cmd.subcommands, help()) + cmd.subcommands = append(cmd.subcommands, help(cmd.shortForms)) } return cmd @@ -127,11 +129,7 @@ func hasOptions(cmd Cmd) bool { return false } - v := reflect.ValueOf(cmd.impl) - t := v.Type() - t = unpack(t) - s := structParameters(t) - return len(fields(s...)) > 0 + return len(fields(cmd.impl)) > 0 } func allCommands(cmd doc) []doc { @@ -147,17 +145,17 @@ func allCommands(cmd doc) []doc { return commands } -func functionParams(v reflect.Value, skip []int) ([]string, []string) { - names := docreflect.FunctionParams(v) - - var types []reflect.Kind - for i := 0; i < v.Type().NumIn(); i++ { - types = append(types, v.Type().In(i).Kind()) +func functionParams(v any, indices []int) ([]string, []string) { + var names []string + r := reflect.ValueOf(v) + allNames := docreflect.FunctionParams(r) + for _, i := range indices { + names = append(names, allNames[i]) } - for _, i := range skip { - names = append(names[:i], names[i+1:]...) - types = append(types[:i], types[i+1:]...) + var types []reflect.Kind + for _, i := range indices { + types = append(types, r.Type().In(i).Kind()) } var stypes []string @@ -173,24 +171,22 @@ func constructArguments(cmd Cmd) argumentSet { return argumentSet{} } - v := unpack(reflect.ValueOf(cmd.impl)) - t := v.Type() - p := positionalParameters(t) - ior, iow := ioParameters(p) - count := len(p) - len(ior) - len(iow) - names, types := functionParams(v, append(ior, iow...)) - if len(names) < count { + p, variadic := positional(cmd.impl) + pi := positionalIndices(cmd.impl) + ior, iow := ioParameters(cmd.impl) + names, types := functionParams(cmd.impl, pi) + if len(names) < len(p) { names = nil - for i := 0; i < count; i++ { + for i := 0; i < len(p); i++ { names = append(names, fmt.Sprintf("arg%d", i)) } } return argumentSet{ - count: count, + count: len(p), names: names, types: types, - variadic: t.IsVariadic(), + variadic: variadic, usesStdin: len(ior) > 0, usesStdout: len(iow) > 0, minPositional: cmd.minPositional, @@ -230,13 +226,12 @@ func constructOptions(cmd Cmd, hasConfigFromOption bool) []docOption { sf[l] = append(sf[l], s) } - t := unpack(reflect.ValueOf(cmd.impl).Type()) - s := structParameters(t) + s := structParameters(cmd.impl) d := make(map[string]string) for _, si := range s { - f := fields(si) + f := structFields(si) for _, fi := range f { - d[fi.name] = docreflect.Field(si, fi.path...) + d[fi.Name()] = docreflect.Field(si, fi.Path()...) } } @@ -245,14 +240,14 @@ func constructOptions(cmd Cmd, hasConfigFromOption bool) []docOption { for name, fi := range f { opt := docOption{ name: name, - typ: strings.ToLower(fmt.Sprint(fi[0].typ.Kind())), + typ: scalarTypeString(fi[0].Type()), description: d[name], shortNames: sf[name], - isBool: fi[0].typ.Kind() == reflect.Bool, + isBool: fi[0].Type() == bind.Bool, } for _, fii := range fi { - if fii.acceptsMultiple { + if fii.List() { opt.acceptsMultiple = true } } @@ -292,15 +287,13 @@ func constructConfigDocs(cmd Cmd, conf Config) []docConfig { } func constructDoc(cmd Cmd, conf Config, fullCommand []string) doc { - hasConfigFromOption := hasConfigFromOption(conf) - var subcommands []doc for _, sc := range cmd.subcommands { subcommands = append(subcommands, constructDoc(sc, conf, append(fullCommand, sc.name))) } var hasBoolOptions, hasListOptions bool - options := constructOptions(cmd, hasConfigFromOption) + options := constructOptions(cmd, hasConfigFromOption(conf)) for _, o := range options { if o.isBool { hasBoolOptions = true @@ -322,7 +315,7 @@ func constructDoc(cmd Cmd, conf Config, fullCommand []string) doc { isHelp: cmd.isHelp, isVersion: cmd.version != "", hasHelpSubcommand: hasHelpSubcommand(cmd), - hasHelpOption: hasCustomHelpOption(cmd), + hasHelpOption: !hasCustomHelpOption(cmd), options: options, hasBoolOptions: hasBoolOptions, hasListOptions: hasListOptions, diff --git a/iniparser.gen.go b/iniparser.gen.go index 7023607..957d3ba 100644 --- a/iniparser.gen.go +++ b/iniparser.gen.go @@ -1,3 +1,4 @@ + /* This file was generated with treerack (https://code.squareroundforest.org/arpio/treerack). @@ -16,29 +17,30 @@ that the user of treerack generating this file declares for it, or it is unlicensed. */ + package wand // head import ( - "bufio" - "errors" - "fmt" - "io" "strconv" + "io" "strings" "unicode" + "fmt" + "bufio" + "errors" ) type charParser struct { - name string - id int - not bool - chars []rune - ranges [][]rune + name string + id int + not bool + chars []rune + ranges [][]rune } type charBuilder struct { - name string - id int + name string + id int } func (p *charParser) nodeName() string { @@ -88,22 +90,22 @@ func (b *charBuilder) build(c *context) ([]*node, bool) { } type sequenceParser struct { - name string - id int - commit commitType - items []parser - ranges [][]int - generalizations []int - allChars bool + name string + id int + commit commitType + items []parser + ranges [][]int + generalizations []int + allChars bool } type sequenceBuilder struct { - name string - id int - commit commitType - items []builder - ranges [][]int - generalizations []int - allChars bool + name string + id int + commit commitType + items []builder + ranges [][]int + generalizations []int + allChars bool } func (p *sequenceParser) nodeName() string { @@ -124,8 +126,8 @@ func (p *sequenceParser) parse(c *context) { c.results.markPending(c.offset, p.id) } var ( - currentCount int - parsed bool + currentCount int + parsed bool ) itemIndex := 0 from := c.offset @@ -227,9 +229,9 @@ func (b *sequenceBuilder) build(c *context) ([]*node, bool) { } } var ( - itemIndex int - currentCount int - nodes []*node + itemIndex int + currentCount int + nodes []*node ) for itemIndex < len(b.items) { itemFrom := c.offset @@ -269,18 +271,18 @@ func (b *sequenceBuilder) build(c *context) ([]*node, bool) { } type choiceParser struct { - name string - id int - commit commitType - options []parser - generalizations []int + name string + id int + commit commitType + options []parser + generalizations []int } type choiceBuilder struct { - name string - id int - commit commitType - options []builder - generalizations []int + name string + id int + commit commitType + options []builder + generalizations []int } func (p *choiceParser) nodeName() string { @@ -302,10 +304,10 @@ func (p *choiceParser) parse(c *context) { } c.results.markPending(c.offset, p.id) var ( - match bool - optionIndex int - foundMatch bool - failingParser parser + match bool + optionIndex int + foundMatch bool + failingParser parser ) from := c.offset to := c.offset @@ -454,9 +456,9 @@ func (s *idSet) has(id int) bool { } type results struct { - noMatch []*idSet - match [][]int - isPending [][]int + noMatch []*idSet + match [][]int + isPending [][]int } func ensureOffsetInts(ints [][]int, offset int) [][]int { @@ -595,19 +597,19 @@ func (r *results) unmarkPending(offset, id int) { } type context struct { - reader io.RuneReader - keywords []parser - offset int - readOffset int - consumed int - offsetLimit int - failOffset int - failingParser parser - readErr error - eof bool - results *results - tokens []rune - matchLast bool + reader io.RuneReader + keywords []parser + offset int + readOffset int + consumed int + offsetLimit int + failOffset int + failingParser parser + readErr error + eof bool + results *results + tokens []rune + matchLast bool } func newContext(r io.RuneReader, keywords []parser) *context { @@ -729,10 +731,10 @@ func (c *context) finalizeParse(root parser) error { } type node struct { - Name string - Nodes []*node - From, To int - tokens []rune + Name string + Nodes []*node + From, To int + tokens []rune } func (n *node) Tokens() []rune { @@ -748,8 +750,8 @@ func (n *node) Text() string { type commitType int const ( - none commitType = 0 - alias commitType = 1 << iota + none commitType = 0 + alias commitType = 1 << iota whitespace noWhitespace keyword @@ -762,17 +764,17 @@ const ( type formatFlags int const ( - formatNone formatFlags = 0 - formatPretty formatFlags = 1 << iota + formatNone formatFlags = 0 + formatPretty formatFlags = 1 << iota formatIncludeComments ) type parseError struct { - Input string - Offset int - Line int - Column int - Definition string + Input string + Offset int + Line int + Column int + Definition string } type parser interface { nodeName() string @@ -813,238 +815,9 @@ func parseInput(r io.Reader, p parser, b builder, kw []parser) (*node, error) { func parse(r io.Reader) (*node, error) { - var p67 = sequenceParser{id: 67, commit: 128, ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var p65 = choiceParser{id: 65, commit: 2} - var p64 = sequenceParser{id: 64, commit: 262, name: "whitespace", allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{65}} - var p1 = charParser{id: 1, chars: []rune{32, 8, 12, 13, 9, 11}} - p64.items = []parser{&p1} - p65.options = []parser{&p64} - var p66 = sequenceParser{id: 66, commit: 258, name: "doc:wsroot", ranges: [][]int{{0, 1}}} - var p63 = sequenceParser{id: 63, commit: 2, ranges: [][]int{{1, 1}, {0, -1}}} - var p61 = choiceParser{id: 61, commit: 2} - var p58 = sequenceParser{id: 58, commit: 256, name: "key-val", ranges: [][]int{{0, 1}, {0, -1}, {1, 1}, {0, -1}, {0, 1}, {0, -1}, {0, 1}}, generalizations: []int{61}} - var p54 = sequenceParser{id: 54, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}}} - var p14 = sequenceParser{id: 14, commit: 256, name: "comment", ranges: [][]int{{1, 1}, {0, 1}}} - var p8 = sequenceParser{id: 8, commit: 258, name: "comment-line", ranges: [][]int{{1, 1}, {0, 1}}, generalizations: []int{61}} - var p3 = sequenceParser{id: 3, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p2 = charParser{id: 2, chars: []rune{35}} - p3.items = []parser{&p2} - var p7 = sequenceParser{id: 7, commit: 2, ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var p5 = sequenceParser{id: 5, commit: 2, allChars: true, ranges: [][]int{{1, 1}}} - var p4 = charParser{id: 4, not: true, chars: []rune{10}} - p5.items = []parser{&p4} - var p6 = sequenceParser{id: 6, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - p6.items = []parser{&p65, &p5} - p7.items = []parser{&p65, &p5, &p6} - p8.items = []parser{&p3, &p7} - var p13 = sequenceParser{id: 13, commit: 2, ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var p11 = sequenceParser{id: 11, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}}} - var p10 = sequenceParser{id: 10, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p9 = charParser{id: 9, chars: []rune{10}} - p10.items = []parser{&p9} - p11.items = []parser{&p10, &p65, &p8} - var p12 = sequenceParser{id: 12, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - p12.items = []parser{&p65, &p11} - p13.items = []parser{&p65, &p11, &p12} - p14.items = []parser{&p8, &p13} - var p53 = sequenceParser{id: 53, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p52 = charParser{id: 52, chars: []rune{10}} - p53.items = []parser{&p52} - p54.items = []parser{&p14, &p65, &p53} - var p39 = choiceParser{id: 39, commit: 256, name: "key"} - var p38 = sequenceParser{id: 38, commit: 266, name: "word", ranges: [][]int{{1, 1}, {0, -1}, {1, 1}, {0, -1}}, generalizations: []int{39}} - var p29 = sequenceParser{id: 29, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p28 = charParser{id: 28, chars: []rune{95}, ranges: [][]rune{{97, 122}, {65, 90}}} - p29.items = []parser{&p28} - var p37 = choiceParser{id: 37, commit: 10} - var p31 = sequenceParser{id: 31, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{37}} - var p30 = charParser{id: 30, chars: []rune{95, 45}, ranges: [][]rune{{97, 122}, {65, 90}, {48, 57}}} - p31.items = []parser{&p30} - var p36 = sequenceParser{id: 36, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{37}} - var p33 = sequenceParser{id: 33, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p32 = charParser{id: 32, chars: []rune{92}} - p33.items = []parser{&p32} - var p35 = sequenceParser{id: 35, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p34 = charParser{id: 34, not: true} - p35.items = []parser{&p34} - p36.items = []parser{&p33, &p35} - p37.options = []parser{&p31, &p36} - p38.items = []parser{&p29, &p37} - var p27 = sequenceParser{id: 27, commit: 266, name: "quoted", ranges: [][]int{{1, 1}, {0, -1}, {1, 1}, {1, 1}, {0, -1}, {1, 1}}, generalizations: []int{39, 51}} - var p16 = sequenceParser{id: 16, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p15 = charParser{id: 15, chars: []rune{34}} - p16.items = []parser{&p15} - var p24 = choiceParser{id: 24, commit: 10} - var p18 = sequenceParser{id: 18, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{24}} - var p17 = charParser{id: 17, not: true, chars: []rune{92, 34}} - p18.items = []parser{&p17} - var p23 = sequenceParser{id: 23, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{24}} - var p20 = sequenceParser{id: 20, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p19 = charParser{id: 19, chars: []rune{92}} - p20.items = []parser{&p19} - var p22 = sequenceParser{id: 22, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p21 = charParser{id: 21, not: true} - p22.items = []parser{&p21} - p23.items = []parser{&p20, &p22} - p24.options = []parser{&p18, &p23} - var p26 = sequenceParser{id: 26, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p25 = charParser{id: 25, chars: []rune{34}} - p26.items = []parser{&p25} - p27.items = []parser{&p16, &p24, &p26} - p39.options = []parser{&p38, &p27} - var p57 = sequenceParser{id: 57, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {0, 1}}} - var p56 = sequenceParser{id: 56, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p55 = charParser{id: 55, chars: []rune{61}} - p56.items = []parser{&p55} - var p51 = choiceParser{id: 51, commit: 256, name: "value"} - var p50 = sequenceParser{id: 50, commit: 2, ranges: [][]int{{1, 1}, {0, -1}}, generalizations: []int{51}} - var p48 = sequenceParser{id: 48, commit: 266, name: "value-chars", ranges: [][]int{{1, -1}, {1, -1}}} - var p47 = choiceParser{id: 47, commit: 10} - var p41 = sequenceParser{id: 41, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{47}} - var p40 = charParser{id: 40, not: true, chars: []rune{92, 34, 10, 61, 35, 32, 8, 12, 13, 9, 11}} - p41.items = []parser{&p40} - var p46 = sequenceParser{id: 46, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{47}} - var p43 = sequenceParser{id: 43, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p42 = charParser{id: 42, chars: []rune{92}} - p43.items = []parser{&p42} - var p45 = sequenceParser{id: 45, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var p44 = charParser{id: 44, not: true} - p45.items = []parser{&p44} - p46.items = []parser{&p43, &p45} - p47.options = []parser{&p41, &p46} - p48.items = []parser{&p47} - var p49 = sequenceParser{id: 49, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - p49.items = []parser{&p65, &p48} - p50.items = []parser{&p48, &p49} - p51.options = []parser{&p50, &p27} - p57.items = []parser{&p56, &p65, &p51} - p58.items = []parser{&p54, &p65, &p39, &p65, &p57, &p65, &p8} - var p60 = sequenceParser{id: 60, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{61}} - var p59 = charParser{id: 59, chars: []rune{10}} - p60.items = []parser{&p59} - p61.options = []parser{&p58, &p8, &p60} - var p62 = sequenceParser{id: 62, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - p62.items = []parser{&p65, &p61} - p63.items = []parser{&p61, &p62} - p66.items = []parser{&p63} - p67.items = []parser{&p65, &p66, &p65} - var b67 = sequenceBuilder{id: 67, commit: 128, name: "doc", ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var b65 = choiceBuilder{id: 65, commit: 2} - var b64 = sequenceBuilder{id: 64, commit: 262, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{65}} - var b1 = charBuilder{} - b64.items = []builder{&b1} - b65.options = []builder{&b64} - var b66 = sequenceBuilder{id: 66, commit: 258, ranges: [][]int{{0, 1}}} - var b63 = sequenceBuilder{id: 63, commit: 2, ranges: [][]int{{1, 1}, {0, -1}}} - var b61 = choiceBuilder{id: 61, commit: 2} - var b58 = sequenceBuilder{id: 58, commit: 256, name: "key-val", ranges: [][]int{{0, 1}, {0, -1}, {1, 1}, {0, -1}, {0, 1}, {0, -1}, {0, 1}}, generalizations: []int{61}} - var b54 = sequenceBuilder{id: 54, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}}} - var b14 = sequenceBuilder{id: 14, commit: 256, name: "comment", ranges: [][]int{{1, 1}, {0, 1}}} - var b8 = sequenceBuilder{id: 8, commit: 258, ranges: [][]int{{1, 1}, {0, 1}}, generalizations: []int{61}} - var b3 = sequenceBuilder{id: 3, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b2 = charBuilder{} - b3.items = []builder{&b2} - var b7 = sequenceBuilder{id: 7, commit: 2, ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var b5 = sequenceBuilder{id: 5, commit: 2, allChars: true, ranges: [][]int{{1, 1}}} - var b4 = charBuilder{} - b5.items = []builder{&b4} - var b6 = sequenceBuilder{id: 6, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - b6.items = []builder{&b65, &b5} - b7.items = []builder{&b65, &b5, &b6} - b8.items = []builder{&b3, &b7} - var b13 = sequenceBuilder{id: 13, commit: 2, ranges: [][]int{{0, -1}, {1, 1}, {0, -1}}} - var b11 = sequenceBuilder{id: 11, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}}} - var b10 = sequenceBuilder{id: 10, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b9 = charBuilder{} - b10.items = []builder{&b9} - b11.items = []builder{&b10, &b65, &b8} - var b12 = sequenceBuilder{id: 12, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - b12.items = []builder{&b65, &b11} - b13.items = []builder{&b65, &b11, &b12} - b14.items = []builder{&b8, &b13} - var b53 = sequenceBuilder{id: 53, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b52 = charBuilder{} - b53.items = []builder{&b52} - b54.items = []builder{&b14, &b65, &b53} - var b39 = choiceBuilder{id: 39, commit: 256, name: "key"} - var b38 = sequenceBuilder{id: 38, commit: 266, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}, {0, -1}}, generalizations: []int{39}} - var b29 = sequenceBuilder{id: 29, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b28 = charBuilder{} - b29.items = []builder{&b28} - var b37 = choiceBuilder{id: 37, commit: 10} - var b31 = sequenceBuilder{id: 31, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{37}} - var b30 = charBuilder{} - b31.items = []builder{&b30} - var b36 = sequenceBuilder{id: 36, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{37}} - var b33 = sequenceBuilder{id: 33, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b32 = charBuilder{} - b33.items = []builder{&b32} - var b35 = sequenceBuilder{id: 35, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b34 = charBuilder{} - b35.items = []builder{&b34} - b36.items = []builder{&b33, &b35} - b37.options = []builder{&b31, &b36} - b38.items = []builder{&b29, &b37} - var b27 = sequenceBuilder{id: 27, commit: 266, ranges: [][]int{{1, 1}, {0, -1}, {1, 1}, {1, 1}, {0, -1}, {1, 1}}, generalizations: []int{39, 51}} - var b16 = sequenceBuilder{id: 16, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b15 = charBuilder{} - b16.items = []builder{&b15} - var b24 = choiceBuilder{id: 24, commit: 10} - var b18 = sequenceBuilder{id: 18, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{24}} - var b17 = charBuilder{} - b18.items = []builder{&b17} - var b23 = sequenceBuilder{id: 23, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{24}} - var b20 = sequenceBuilder{id: 20, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b19 = charBuilder{} - b20.items = []builder{&b19} - var b22 = sequenceBuilder{id: 22, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b21 = charBuilder{} - b22.items = []builder{&b21} - b23.items = []builder{&b20, &b22} - b24.options = []builder{&b18, &b23} - var b26 = sequenceBuilder{id: 26, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b25 = charBuilder{} - b26.items = []builder{&b25} - b27.items = []builder{&b16, &b24, &b26} - b39.options = []builder{&b38, &b27} - var b57 = sequenceBuilder{id: 57, commit: 2, ranges: [][]int{{1, 1}, {0, -1}, {0, 1}}} - var b56 = sequenceBuilder{id: 56, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b55 = charBuilder{} - b56.items = []builder{&b55} - var b51 = choiceBuilder{id: 51, commit: 256, name: "value"} - var b50 = sequenceBuilder{id: 50, commit: 2, ranges: [][]int{{1, 1}, {0, -1}}, generalizations: []int{51}} - var b48 = sequenceBuilder{id: 48, commit: 266, ranges: [][]int{{1, -1}, {1, -1}}} - var b47 = choiceBuilder{id: 47, commit: 10} - var b41 = sequenceBuilder{id: 41, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{47}} - var b40 = charBuilder{} - b41.items = []builder{&b40} - var b46 = sequenceBuilder{id: 46, commit: 10, ranges: [][]int{{1, 1}, {1, 1}, {1, 1}, {1, 1}}, generalizations: []int{47}} - var b43 = sequenceBuilder{id: 43, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b42 = charBuilder{} - b43.items = []builder{&b42} - var b45 = sequenceBuilder{id: 45, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}} - var b44 = charBuilder{} - b45.items = []builder{&b44} - b46.items = []builder{&b43, &b45} - b47.options = []builder{&b41, &b46} - b48.items = []builder{&b47} - var b49 = sequenceBuilder{id: 49, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - b49.items = []builder{&b65, &b48} - b50.items = []builder{&b48, &b49} - b51.options = []builder{&b50, &b27} - b57.items = []builder{&b56, &b65, &b51} - b58.items = []builder{&b54, &b65, &b39, &b65, &b57, &b65, &b8} - var b60 = sequenceBuilder{id: 60, commit: 10, allChars: true, ranges: [][]int{{1, 1}, {1, 1}}, generalizations: []int{61}} - var b59 = charBuilder{} - b60.items = []builder{&b59} - b61.options = []builder{&b58, &b8, &b60} - var b62 = sequenceBuilder{id: 62, commit: 2, ranges: [][]int{{0, -1}, {1, 1}}} - b62.items = []builder{&b65, &b61} - b63.items = []builder{&b61, &b62} - b66.items = []builder{&b63} - b67.items = []builder{&b65, &b66, &b65} +var p67 = sequenceParser{id: 67, commit: 128,ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var p65 = choiceParser{id: 65, commit: 2,};var p64 = sequenceParser{id: 64, commit: 262,name: "whitespace",allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{65,},};var p1 = charParser{id: 1,chars: []rune{32,8,12,13,9,11,},};p64.items = []parser{&p1,};p65.options = []parser{&p64,};var p66 = sequenceParser{id: 66, commit: 258,name: "doc:wsroot",ranges: [][]int{{0, 1},},};var p63 = sequenceParser{id: 63, commit: 2,ranges: [][]int{{1, 1},{0, -1},},};var p61 = choiceParser{id: 61, commit: 2,};var p58 = sequenceParser{id: 58, commit: 256,name: "key-val",ranges: [][]int{{0, 1},{0, -1},{1, 1},{0, -1},{0, 1},{0, -1},{0, 1},},generalizations: []int{61,},};var p54 = sequenceParser{id: 54, commit: 2,ranges: [][]int{{1, 1},{0, -1},{1, 1},},};var p14 = sequenceParser{id: 14, commit: 256,name: "comment",ranges: [][]int{{1, 1},{0, 1},},};var p8 = sequenceParser{id: 8, commit: 258,name: "comment-line",ranges: [][]int{{1, 1},{0, 1},},generalizations: []int{61,},};var p3 = sequenceParser{id: 3, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p2 = charParser{id: 2,chars: []rune{35,},};p3.items = []parser{&p2,};var p7 = sequenceParser{id: 7, commit: 2,ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var p5 = sequenceParser{id: 5, commit: 2,allChars: true,ranges: [][]int{{1, 1},},};var p4 = charParser{id: 4,not: true,chars: []rune{10,},};p5.items = []parser{&p4,};var p6 = sequenceParser{id: 6, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};p6.items = []parser{&p65,&p5,};p7.items = []parser{&p65,&p5,&p6,};p8.items = []parser{&p3,&p7,};var p13 = sequenceParser{id: 13, commit: 2,ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var p11 = sequenceParser{id: 11, commit: 2,ranges: [][]int{{1, 1},{0, -1},{1, 1},},};var p10 = sequenceParser{id: 10, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p9 = charParser{id: 9,chars: []rune{10,},};p10.items = []parser{&p9,};p11.items = []parser{&p10,&p65,&p8,};var p12 = sequenceParser{id: 12, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};p12.items = []parser{&p65,&p11,};p13.items = []parser{&p65,&p11,&p12,};p14.items = []parser{&p8,&p13,};var p53 = sequenceParser{id: 53, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p52 = charParser{id: 52,chars: []rune{10,},};p53.items = []parser{&p52,};p54.items = []parser{&p14,&p65,&p53,};var p39 = choiceParser{id: 39, commit: 256,name: "key",};var p38 = sequenceParser{id: 38, commit: 266,name: "word",ranges: [][]int{{1, 1},{0, -1},{1, 1},{0, -1},},generalizations: []int{39,},};var p29 = sequenceParser{id: 29, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p28 = charParser{id: 28,chars: []rune{95,},ranges: [][]rune{{97, 122},{65, 90},},};p29.items = []parser{&p28,};var p37 = choiceParser{id: 37, commit: 10,};var p31 = sequenceParser{id: 31, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{37,},};var p30 = charParser{id: 30,chars: []rune{95,45,},ranges: [][]rune{{97, 122},{65, 90},{48, 57},},};p31.items = []parser{&p30,};var p36 = sequenceParser{id: 36, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{37,},};var p33 = sequenceParser{id: 33, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p32 = charParser{id: 32,chars: []rune{92,},};p33.items = []parser{&p32,};var p35 = sequenceParser{id: 35, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p34 = charParser{id: 34,not: true,};p35.items = []parser{&p34,};p36.items = []parser{&p33,&p35,};p37.options = []parser{&p31,&p36,};p38.items = []parser{&p29,&p37,};var p27 = sequenceParser{id: 27, commit: 266,name: "quoted",ranges: [][]int{{1, 1},{0, -1},{1, 1},{1, 1},{0, -1},{1, 1},},generalizations: []int{39,51,},};var p16 = sequenceParser{id: 16, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p15 = charParser{id: 15,chars: []rune{34,},};p16.items = []parser{&p15,};var p24 = choiceParser{id: 24, commit: 10,};var p18 = sequenceParser{id: 18, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{24,},};var p17 = charParser{id: 17,not: true,chars: []rune{92,34,},};p18.items = []parser{&p17,};var p23 = sequenceParser{id: 23, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{24,},};var p20 = sequenceParser{id: 20, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p19 = charParser{id: 19,chars: []rune{92,},};p20.items = []parser{&p19,};var p22 = sequenceParser{id: 22, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p21 = charParser{id: 21,not: true,};p22.items = []parser{&p21,};p23.items = []parser{&p20,&p22,};p24.options = []parser{&p18,&p23,};var p26 = sequenceParser{id: 26, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p25 = charParser{id: 25,chars: []rune{34,},};p26.items = []parser{&p25,};p27.items = []parser{&p16,&p24,&p26,};p39.options = []parser{&p38,&p27,};var p57 = sequenceParser{id: 57, commit: 2,ranges: [][]int{{1, 1},{0, -1},{0, 1},},};var p56 = sequenceParser{id: 56, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p55 = charParser{id: 55,chars: []rune{61,},};p56.items = []parser{&p55,};var p51 = choiceParser{id: 51, commit: 256,name: "value",};var p50 = sequenceParser{id: 50, commit: 2,ranges: [][]int{{1, 1},{0, -1},},generalizations: []int{51,},};var p48 = sequenceParser{id: 48, commit: 266,name: "value-chars",ranges: [][]int{{1, -1},{1, -1},},};var p47 = choiceParser{id: 47, commit: 10,};var p41 = sequenceParser{id: 41, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{47,},};var p40 = charParser{id: 40,not: true,chars: []rune{92,34,10,61,35,32,8,12,13,9,11,},};p41.items = []parser{&p40,};var p46 = sequenceParser{id: 46, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{47,},};var p43 = sequenceParser{id: 43, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p42 = charParser{id: 42,chars: []rune{92,},};p43.items = []parser{&p42,};var p45 = sequenceParser{id: 45, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var p44 = charParser{id: 44,not: true,};p45.items = []parser{&p44,};p46.items = []parser{&p43,&p45,};p47.options = []parser{&p41,&p46,};p48.items = []parser{&p47,};var p49 = sequenceParser{id: 49, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};p49.items = []parser{&p65,&p48,};p50.items = []parser{&p48,&p49,};p51.options = []parser{&p50,&p27,};p57.items = []parser{&p56,&p65,&p51,};p58.items = []parser{&p54,&p65,&p39,&p65,&p57,&p65,&p8,};var p60 = sequenceParser{id: 60, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{61,},};var p59 = charParser{id: 59,chars: []rune{10,},};p60.items = []parser{&p59,};p61.options = []parser{&p58,&p8,&p60,};var p62 = sequenceParser{id: 62, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};p62.items = []parser{&p65,&p61,};p63.items = []parser{&p61,&p62,};p66.items = []parser{&p63,};p67.items = []parser{&p65,&p66,&p65,};var b67 = sequenceBuilder{id: 67, commit: 128,name: "doc",ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var b65 = choiceBuilder{id: 65, commit: 2,};var b64 = sequenceBuilder{id: 64, commit: 262,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{65,},};var b1 = charBuilder{};b64.items = []builder{&b1,};b65.options = []builder{&b64,};var b66 = sequenceBuilder{id: 66, commit: 258,ranges: [][]int{{0, 1},},};var b63 = sequenceBuilder{id: 63, commit: 2,ranges: [][]int{{1, 1},{0, -1},},};var b61 = choiceBuilder{id: 61, commit: 2,};var b58 = sequenceBuilder{id: 58, commit: 256,name: "key-val",ranges: [][]int{{0, 1},{0, -1},{1, 1},{0, -1},{0, 1},{0, -1},{0, 1},},generalizations: []int{61,},};var b54 = sequenceBuilder{id: 54, commit: 2,ranges: [][]int{{1, 1},{0, -1},{1, 1},},};var b14 = sequenceBuilder{id: 14, commit: 256,name: "comment",ranges: [][]int{{1, 1},{0, 1},},};var b8 = sequenceBuilder{id: 8, commit: 258,ranges: [][]int{{1, 1},{0, 1},},generalizations: []int{61,},};var b3 = sequenceBuilder{id: 3, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b2 = charBuilder{};b3.items = []builder{&b2,};var b7 = sequenceBuilder{id: 7, commit: 2,ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var b5 = sequenceBuilder{id: 5, commit: 2,allChars: true,ranges: [][]int{{1, 1},},};var b4 = charBuilder{};b5.items = []builder{&b4,};var b6 = sequenceBuilder{id: 6, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};b6.items = []builder{&b65,&b5,};b7.items = []builder{&b65,&b5,&b6,};b8.items = []builder{&b3,&b7,};var b13 = sequenceBuilder{id: 13, commit: 2,ranges: [][]int{{0, -1},{1, 1},{0, -1},},};var b11 = sequenceBuilder{id: 11, commit: 2,ranges: [][]int{{1, 1},{0, -1},{1, 1},},};var b10 = sequenceBuilder{id: 10, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b9 = charBuilder{};b10.items = []builder{&b9,};b11.items = []builder{&b10,&b65,&b8,};var b12 = sequenceBuilder{id: 12, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};b12.items = []builder{&b65,&b11,};b13.items = []builder{&b65,&b11,&b12,};b14.items = []builder{&b8,&b13,};var b53 = sequenceBuilder{id: 53, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b52 = charBuilder{};b53.items = []builder{&b52,};b54.items = []builder{&b14,&b65,&b53,};var b39 = choiceBuilder{id: 39, commit: 256,name: "key",};var b38 = sequenceBuilder{id: 38, commit: 266,ranges: [][]int{{1, 1},{0, -1},{1, 1},{0, -1},},generalizations: []int{39,},};var b29 = sequenceBuilder{id: 29, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b28 = charBuilder{};b29.items = []builder{&b28,};var b37 = choiceBuilder{id: 37, commit: 10,};var b31 = sequenceBuilder{id: 31, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{37,},};var b30 = charBuilder{};b31.items = []builder{&b30,};var b36 = sequenceBuilder{id: 36, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{37,},};var b33 = sequenceBuilder{id: 33, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b32 = charBuilder{};b33.items = []builder{&b32,};var b35 = sequenceBuilder{id: 35, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b34 = charBuilder{};b35.items = []builder{&b34,};b36.items = []builder{&b33,&b35,};b37.options = []builder{&b31,&b36,};b38.items = []builder{&b29,&b37,};var b27 = sequenceBuilder{id: 27, commit: 266,ranges: [][]int{{1, 1},{0, -1},{1, 1},{1, 1},{0, -1},{1, 1},},generalizations: []int{39,51,},};var b16 = sequenceBuilder{id: 16, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b15 = charBuilder{};b16.items = []builder{&b15,};var b24 = choiceBuilder{id: 24, commit: 10,};var b18 = sequenceBuilder{id: 18, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{24,},};var b17 = charBuilder{};b18.items = []builder{&b17,};var b23 = sequenceBuilder{id: 23, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{24,},};var b20 = sequenceBuilder{id: 20, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b19 = charBuilder{};b20.items = []builder{&b19,};var b22 = sequenceBuilder{id: 22, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b21 = charBuilder{};b22.items = []builder{&b21,};b23.items = []builder{&b20,&b22,};b24.options = []builder{&b18,&b23,};var b26 = sequenceBuilder{id: 26, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b25 = charBuilder{};b26.items = []builder{&b25,};b27.items = []builder{&b16,&b24,&b26,};b39.options = []builder{&b38,&b27,};var b57 = sequenceBuilder{id: 57, commit: 2,ranges: [][]int{{1, 1},{0, -1},{0, 1},},};var b56 = sequenceBuilder{id: 56, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b55 = charBuilder{};b56.items = []builder{&b55,};var b51 = choiceBuilder{id: 51, commit: 256,name: "value",};var b50 = sequenceBuilder{id: 50, commit: 2,ranges: [][]int{{1, 1},{0, -1},},generalizations: []int{51,},};var b48 = sequenceBuilder{id: 48, commit: 266,ranges: [][]int{{1, -1},{1, -1},},};var b47 = choiceBuilder{id: 47, commit: 10,};var b41 = sequenceBuilder{id: 41, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{47,},};var b40 = charBuilder{};b41.items = []builder{&b40,};var b46 = sequenceBuilder{id: 46, commit: 10,ranges: [][]int{{1, 1},{1, 1},{1, 1},{1, 1},},generalizations: []int{47,},};var b43 = sequenceBuilder{id: 43, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b42 = charBuilder{};b43.items = []builder{&b42,};var b45 = sequenceBuilder{id: 45, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},};var b44 = charBuilder{};b45.items = []builder{&b44,};b46.items = []builder{&b43,&b45,};b47.options = []builder{&b41,&b46,};b48.items = []builder{&b47,};var b49 = sequenceBuilder{id: 49, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};b49.items = []builder{&b65,&b48,};b50.items = []builder{&b48,&b49,};b51.options = []builder{&b50,&b27,};b57.items = []builder{&b56,&b65,&b51,};b58.items = []builder{&b54,&b65,&b39,&b65,&b57,&b65,&b8,};var b60 = sequenceBuilder{id: 60, commit: 10,allChars: true,ranges: [][]int{{1, 1},{1, 1},},generalizations: []int{61,},};var b59 = charBuilder{};b60.items = []builder{&b59,};b61.options = []builder{&b58,&b8,&b60,};var b62 = sequenceBuilder{id: 62, commit: 2,ranges: [][]int{{0, -1},{1, 1},},};b62.items = []builder{&b65,&b61,};b63.items = []builder{&b61,&b62,};b66.items = []builder{&b63,};b67.items = []builder{&b65,&b66,&b65,}; - var keywords = []parser{} +var keywords = []parser{} - return parseInput(r, &p67, &b67, keywords) +return parseInput(r, &p67, &b67, keywords) } diff --git a/input.go b/input.go index d621da2..ffd3410 100644 --- a/input.go +++ b/input.go @@ -3,7 +3,7 @@ package wand import ( "fmt" "reflect" - "slices" + "code.squareroundforest.org/arpio/bind" ) func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map[string]string) error { @@ -15,20 +15,17 @@ func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map } for _, fi := range f { - if len(values) > 1 && !fi.acceptsMultiple { + if len(values) > 1 && !fi.List() { return fmt.Errorf( - "expected only one value, received %d, as environment value, %s", + "expected only one value, received %d, for %s", len(values), originalNames[name], ) } for _, v := range values { - if !canScan(fi.typ, v) { - return fmt.Errorf( - "environment variable cannot be applied, type mismatch: %s", - originalNames[name], - ) + if !canScan(fi.Type(), v) { + return fmt.Errorf("type mismatch: %s", originalNames[name]) } } } @@ -38,11 +35,19 @@ func validateKeyValues(cmd Cmd, keyValues map[string][]string, originalNames map } func validateConfig(cmd Cmd, c config) error { - return validateKeyValues(cmd, c.values, c.originalNames) + if err := validateKeyValues(cmd, c.values, c.originalNames); err != nil { + return fmt.Errorf("config: %w", err) + } + + return nil } func validateEnv(cmd Cmd, e env) error { - return validateKeyValues(cmd, e.values, e.originalNames) + if err := validateKeyValues(cmd, e.values, e.originalNames); err != nil { + return fmt.Errorf("environment: %w", err) + } + + return nil } func validateOptions(cmd Cmd, o []option, conf Config) error { @@ -64,27 +69,25 @@ func validateOptions(cmd Cmd, o []option, conf Config) error { mo[n] = append(mo[n], oi) } + hasConfigOption := hasConfigFromOption(conf) mf := mapFields(cmd.impl) - if hasConfigFromOption(conf) { - mf["config"] = []field{{ - acceptsMultiple: true, - typ: reflect.TypeFor[string](), - }} - } - for n, os := range mo { en := "--" + n if sn, ok := ml[n]; ok { en += ", -" + sn } + if hasConfigOption && n == "config" { + continue + } + f := mf[n] if len(f) == 0 { return fmt.Errorf("option not supported: %s", en) } for _, fi := range f { - if len(os) > 1 && !fi.acceptsMultiple { + if len(os) > 1 && !fi.List() { return fmt.Errorf( "expected only one value, received %d, as option, %s", len(os), @@ -93,14 +96,14 @@ func validateOptions(cmd Cmd, o []option, conf Config) error { } for _, oi := range os { - if oi.value.isBool && fi.typ.Kind() != reflect.Bool { + if oi.value.isBool && fi.Type() != bind.Bool { return fmt.Errorf( "received boolean value for field that does not accept it: %s", en, ) } - if !oi.value.isBool && !canScan(fi.typ, oi.value.str) { + if !oi.value.isBool && !canScan(fi.Type(), oi.value.str) { return fmt.Errorf( "option cannot be applied, type mismatch: %s", en, @@ -114,20 +117,10 @@ func validateOptions(cmd Cmd, o []option, conf Config) error { } func validatePositionalArgs(cmd Cmd, a []string) error { - v := reflect.ValueOf(cmd.impl) - v = unpack(v) - t := v.Type() - p := positionalParameters(t) - ior, iow := ioParameters(p) - last := t.NumIn() - 1 - lastVariadic := t.IsVariadic() && - !isStruct(t.In(last)) && - !slices.Contains(ior, last) && - !slices.Contains(iow, last) - length := len(p) - len(ior) - len(iow) - min := length - max := length - if lastVariadic { + p, variadic := positional(cmd.impl) + min := len(p) + max := len(p) + if variadic { min-- max = -1 } @@ -149,26 +142,18 @@ func validatePositionalArgs(cmd Cmd, a []string) error { } for i, ai := range a { - if slices.Contains(ior, i) || slices.Contains(iow, i) { - continue - } - var pi reflect.Type - if i >= length { - pi = p[length-1] + if i >= len(p) { + pi = p[len(p)-1] } else { pi = p[i] } - if pi.Kind() == reflect.Interface { - continue - } - - if !canScan(pi, ai) { + if !canScanType(pi, ai) { return fmt.Errorf( - "cannot apply positional argument at index %d, expecting %v", + "cannot apply positional argument at index %d, expecting %s", i, - pi, + fmt.Sprint(pi.Kind()), ) } } diff --git a/wand.go b/lib.go similarity index 98% rename from wand.go rename to lib.go index 37ee5c3..0ef000c 100644 --- a/wand.go +++ b/lib.go @@ -49,6 +49,10 @@ func Args(cmd Cmd, min, max int) Cmd { } func ShortForm(cmd Cmd, f ...string) Cmd { + if len(f)%2 != 0 { + f = append(f, "") + } + cmd.shortForms = append(cmd.shortForms, f...) for i := range cmd.subcommands { cmd.subcommands[i] = ShortForm( diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..84b3f05 --- /dev/null +++ b/notes.txt @@ -0,0 +1,13 @@ +review if any symbols unused +test: +- nil return values +- options in variadic +- options in pointer +- options in slice +- options in slice and pointer +- parameters in pointers +- parameters in slices +- parameters in slices and pointers +- implementation in pointer +- implementation in slice => not accepted +- implementation in pointer and slice => not accepted diff --git a/output.go b/output.go index caa0d3b..bdfa4f2 100644 --- a/output.go +++ b/output.go @@ -7,59 +7,60 @@ import ( "reflect" ) -func printOutput(w io.Writer, o []any) error { - wraperr := func(err error) error { - return fmt.Errorf("error copying output: %w", err) +func fprintOne(out io.Writer, v any) error { + reader, ok := v.(io.Reader) + if ok { + _, err := io.Copy(out, reader) + return err } - for _, oi := range o { - r, ok := oi.(io.Reader) - if ok { - if _, err := io.Copy(w, r); err != nil { - return wraperr(err) - } - - continue - } - - t := reflect.TypeOf(oi) + r := reflect.ValueOf(v) + if r.IsValid() { + t := r.Type() if t.Implements(reflect.TypeFor[fmt.Stringer]()) { - if _, err := fmt.Fprintln(w, oi); err != nil { - return wraperr(err) - } - - continue + _, err := fmt.Fprintln(out, r.Interface()) + return err } - t = unpack(t, reflect.Pointer) - switch t.Kind() { - case reflect.Bool, - reflect.Int, - reflect.Int8, - reflect.Int16, - reflect.Int32, - reflect.Int64, - reflect.Uint, - reflect.Uint8, - reflect.Uint16, - reflect.Uint32, - reflect.Uint64, - reflect.Uintptr, - reflect.Float32, - reflect.Float64, - reflect.String, - reflect.UnsafePointer: - if _, err := fmt.Fprintln(w, oi); err != nil { - return wraperr(err) - } - default: - if _, err := notation.Fprintwt(w, oi); err != nil { - return wraperr(err) + if t.Kind() == reflect.Slice { + for i := 0; i < r.Len(); i++ { + if err := fprintOne(out, r.Index(i).Interface()); err != nil { + return err + } } - if _, err := fmt.Fprintln(w); err != nil { - return wraperr(err) - } + return nil + } + } + + switch r.Kind() { + case reflect.Bool, + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr, + reflect.Float32, + reflect.Float64, + reflect.String: + _, err := fmt.Fprintln(out, v) + return err + default: + _, err := notation.Fprintlnwt(out, v) + return err + } +} + +func printOutput(out io.Writer, o []any) error { + for _, oi := range o { + if err := fprintOne(out, oi); err != nil { + return fmt.Errorf("error displaying output: %w", err) } } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2924028 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# Wand + +*Made in Berlin, DE* diff --git a/reflect.go b/reflect.go index 4201035..d4a4e77 100644 --- a/reflect.go +++ b/reflect.go @@ -1,366 +1,379 @@ package wand import ( - "fmt" - "github.com/iancoleman/strcase" - "io" "reflect" - "slices" - "strconv" + "code.squareroundforest.org/arpio/bind" + "io" + "time" "strings" ) -type packedKind[T any] interface { - Kind() reflect.Kind - Elem() T +func filter[T any](list []T, predicate func(T) bool) []T { + var filtered []T + for _, item := range list { + if predicate(item) { + filtered = append(filtered, item) + } + } + + return filtered } -type field struct { - name string - path []string - typ reflect.Type - acceptsMultiple bool +func not[T any](p func(T) bool) func(T) bool { + return func(v T) bool { + return !p(v) + } } -var ( - readerType = reflect.TypeFor[io.Reader]() - writerType = reflect.TypeFor[io.Writer]() -) +func and[T any](p ...func(T) bool) func(T) bool { + return func(v T) bool { + for _, pi := range p { + if !pi(v) { + return false + } + } -func pack(v reflect.Value, t reflect.Type) reflect.Value { - if v.Type() == t { + return true + } +} + +func or[T any](p ...func(T) bool) func(T) bool { + return func(v T) bool { + for _, pi := range p { + if pi(v) { + return true + } + } + + return false + } +} + +func unpackTypeChecked(visited map[reflect.Type]bool, t reflect.Type) reflect.Type { + if t == nil { + return t + } + + if visited[t] { + return t + } + + if visited == nil { + visited = make(map[reflect.Type]bool) + } + + visited[t] = true + switch t.Kind() { + case reflect.Pointer, reflect.Slice: + return unpackTypeChecked(visited, t.Elem()) + default: + return t + } +} + +func unpackType(t reflect.Type) reflect.Type { + return unpackTypeChecked(nil, t) +} + +func unpackValueChecked(visited map[uintptr]bool, v reflect.Value) reflect.Value { + if !v.IsValid() { return v } - if t.Kind() == reflect.Pointer { - pv := pack(v, t.Elem()) - p := reflect.New(t.Elem()) - p.Elem().Set(pv) - return p - } + switch v.Kind() { + case reflect.Pointer: + p := v.Pointer() + if visited[p] { + return v + } - iv := pack(v, t.Elem()) - s := reflect.MakeSlice(t, 1, 1) - s.Index(0).Set(iv) - return s + if visited == nil { + visited = make(map[uintptr]bool) + } + + visited[p] = true + return unpackValueChecked(visited, v.Elem()) + case reflect.Interface: + if v.IsNil() { + return v + } + + return unpackValueChecked(visited, v.Elem()) + default: + return v + } } -func unpack[T packedKind[T]](p T, kinds ...reflect.Kind) T { - if len(kinds) == 0 { - kinds = []reflect.Kind{reflect.Pointer, reflect.Slice} +func unpackValue(v reflect.Value) reflect.Value { + return unpackValueChecked(nil, v) +} + +func isFunc(v any) bool { + r := reflect.ValueOf(v) + r = unpackValue(r) + return r.Kind() == reflect.Func +} + +func isTime(t reflect.Type) bool { + if t == nil { + return false } - if slices.Contains(kinds, p.Kind()) { - return unpack(p.Elem(), kinds...) + return t.ConvertibleTo(reflect.TypeFor[time.Time]()) +} + +func isStruct(t reflect.Type) bool { + if t == nil { + return false + } + + t = unpackType(t) + return !isTime(t) && t.Kind() == reflect.Struct +} + +func isReader(t reflect.Type) bool { + if t == nil || t.Kind() != reflect.Interface { + return false + } + + return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Reader]()) +} + +func isWriter(t reflect.Type) bool { + if t == nil || t.Kind() != reflect.Interface { + return false + } + + return t.NumMethod() == 1 && t.Implements(reflect.TypeFor[io.Writer]()) +} + +func compatibleTypes(t ...bind.Scalar) bool { + if len(t) == 0 { + return false + } + + if len(t) == 1 { + return true + } + + switch t[0] { + case bind.Any: + return compatibleTypes(t[1:]...) + default: + return t[0] == t[1] && compatibleTypes(t[1:]...) + } +} + +func parameters(f any) []reflect.Type { + r := reflect.ValueOf(f) + r = unpackValue(r) + if r.Kind() != reflect.Func { + return nil + } + + var p []reflect.Type + t := r.Type() + for i := 0; i < t.NumIn(); i++ { + p = append(p, t.In(i)) } return p } -func isReader(t reflect.Type) bool { - return unpack(t) == readerType +func structParameters(f any) []reflect.Type { + return filter(parameters(f), isStruct) } -func isWriter(t reflect.Type) bool { - return unpack(t) == writerType +func structFields(s reflect.Type) []bind.Field { + s = unpackType(s) + v := reflect.Zero(s) + return bind.FieldValues(v) } -func isStruct(t reflect.Type) bool { - t = unpack(t) - return t.Kind() == reflect.Struct -} - -func parseInt(s string, byteSize int) (int64, error) { - bitSize := byteSize * 8 - switch { - case strings.HasPrefix(s, "0b"): - return strconv.ParseInt(s[2:], 2, bitSize) - case strings.HasPrefix(s, "0x"): - return strconv.ParseInt(s[2:], 16, bitSize) - case strings.HasPrefix(s, "0"): - return strconv.ParseInt(s[1:], 8, bitSize) - default: - return strconv.ParseInt(s, 10, bitSize) - } -} - -func parseUint(s string, byteSize int) (uint64, error) { - bitSize := byteSize * 8 - switch { - case strings.HasPrefix(s, "0b"): - return strconv.ParseUint(s[2:], 2, bitSize) - case strings.HasPrefix(s, "0x"): - return strconv.ParseUint(s[2:], 16, bitSize) - case strings.HasPrefix(s, "0"): - return strconv.ParseUint(s[1:], 8, bitSize) - default: - return strconv.ParseUint(s, 10, bitSize) - } -} - -func canScan(t reflect.Type, s string) bool { - switch t.Kind() { - case reflect.Bool: - _, err := strconv.ParseBool(s) - return err == nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - _, err := parseInt(s, int(t.Size())) - return err == nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - _, err := parseUint(s, int(t.Size())) - return err == nil - case reflect.Float32, reflect.Float64: - _, err := strconv.ParseFloat(s, int(t.Size())*8) - return err == nil - case reflect.String: - return true - default: - return false - } -} - -func scan(t reflect.Type, s string) any { - p := reflect.New(t) - switch t.Kind() { - case reflect.Bool: - v, _ := strconv.ParseBool(s) - p.Elem().Set(reflect.ValueOf(v).Convert(t)) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - v, _ := parseInt(s, int(t.Size())) - p.Elem().Set(reflect.ValueOf(v).Convert(t)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - v, _ := parseUint(s, int(t.Size())) - p.Elem().Set(reflect.ValueOf(v).Convert(t)) - case reflect.Float32, reflect.Float64: - v, _ := strconv.ParseFloat(s, int(t.Size())*8) - p.Elem().Set(reflect.ValueOf(v).Convert(t)) - default: - p.Elem().Set(reflect.ValueOf(s).Convert(t)) +func fields(f any) []bind.Field { + var fields []bind.Field + s := structParameters(fields) + for _, si := range s { + fields = append(fields, structFields(si)...) } - return p.Elem().Interface() + return fields } -func fieldsChecked(visited map[reflect.Type]bool, s ...reflect.Type) ([]field, error) { - if len(s) == 0 { - return nil, nil +func mapFields(f any) map[string][]bind.Field { + fields := fields(fields) + m := make(map[string][]bind.Field) + for _, fi := range fields { + m[fi.Name()] = append(m[fi.Name()], fi) } - var ( - anonFields []field - plainFields []field + return m +} + +func boolFields(f []bind.Field) []bind.Field { + return filter( + fields(f), + func(f bind.Field) bool { return f.Type() == bind.Bool }, + ) +} + +func positional(f any) ([]reflect.Type, bool) { + p := filter( + parameters(f), + not(or(isReader, isWriter, isStruct)), ) - for i := 0; i < s[0].NumField(); i++ { - sf := s[0].Field(i) - sft := sf.Type - am := acceptsMultiple(sft) - sft = unpack(sft) - sfn := sf.Name - sfn = strcase.ToKebab(sfn) - switch sft.Kind() { - case reflect.Bool, - reflect.Int, - reflect.Int8, - reflect.Int16, - reflect.Int32, - reflect.Int64, - reflect.Uint, - reflect.Uint8, - reflect.Uint16, - reflect.Uint32, - reflect.Uint64, - reflect.Float32, - reflect.Float64, - reflect.String: - plainFields = append(plainFields, field{ - name: sfn, - path: []string{sf.Name}, - typ: sft, - acceptsMultiple: am, - }) - case reflect.Interface: - if sft.NumMethod() == 0 { - plainFields = append(plainFields, field{ - name: sfn, - path: []string{sf.Name}, - typ: sft, - acceptsMultiple: am, - }) - } - case reflect.Struct: - if visited[sft] { - return nil, fmt.Errorf("circular type definitions not allowed: %s", sft.Name()) - } - - if visited == nil { - visited = make(map[reflect.Type]bool) - } - - visited[sft] = true - sff, err := fieldsChecked(visited, sft) - if err != nil { - return nil, err - } - - if sf.Anonymous { - anonFields = append(anonFields, sff...) - } else { - for i := range sff { - sff[i].name = sfn + "-" + sff[i].name - sff[i].path = append([]string{sf.Name}, sff[i].path...) - sff[i].acceptsMultiple = sff[i].acceptsMultiple || am - } - - plainFields = append(plainFields, sff...) - } - } - } - - mf := make(map[string]field) - for _, fi := range anonFields { - mf[fi.name] = fi - } - - for _, fi := range plainFields { - mf[fi.name] = fi - } - - var f []field - for _, fi := range mf { - f = append(f, fi) - } - - ff, err := fieldsChecked(visited, s[1:]...) - if err != nil { - return nil, err - } - - return append(f, ff...), nil + r := reflect.ValueOf(f) + r = unpackValue(r) + t := r.Type() + return p, t.IsVariadic() } -func fields(s ...reflect.Type) []field { - f, _ := fieldsChecked(nil, s...) - return f -} - -func boolFields(f []field) []field { - var b []field - for _, fi := range f { - if fi.typ.Kind() == reflect.Bool { - b = append(b, fi) - } +func positionalIndices(f any) []int { + r := reflect.ValueOf(f) + r = unpackValue(r) + if r.Kind() != reflect.Func { + return nil } - return b -} - -func mapFields(impl any) map[string][]field { - v := reflect.ValueOf(impl) - t := v.Type() - t = unpack(t) - s := structParameters(t) - f := fields(s...) - mf := make(map[string][]field) - for _, fi := range f { - mf[fi.name] = append(mf[fi.name], fi) - } - - return mf -} - -func filterParameters(t reflect.Type, f func(reflect.Type) bool) []reflect.Type { - var s []reflect.Type + var indices []int + t := r.Type() for i := 0; i < t.NumIn(); i++ { p := t.In(i) - p = unpack(p) - if f(p) { - s = append(s, p) + if isTime(p) || isStruct(p) || isReader(p) || isWriter(p) { + continue } + + indices = append(indices, i) } - return s + return indices } -func positionalParameters(t reflect.Type) []reflect.Type { - return filterParameters(t, func(p reflect.Type) bool { - return p.Kind() != reflect.Struct - }) +func ioParameters(f any) ([]reflect.Type, []reflect.Type) { + p := parameters(f) + return filter(p, isReader), filter(p, isWriter) } -func ioParameters(p []reflect.Type) ([]int, []int) { - var ( - reader []int - writer []int - ) - - for i, pi := range p { - switch { - case isReader(pi): - reader = append(reader, i) - case isWriter(pi): - writer = append(writer, i) - } +func bindable(t reflect.Type) bool { + if t == nil { + return false } - return reader, writer -} - -func structParameters(t reflect.Type) []reflect.Type { - return filterParameters(t, func(p reflect.Type) bool { - return p.Kind() == reflect.Struct - }) -} - -func compatibleTypes(t ...reflect.Type) bool { - if len(t) < 2 { + if isTime(t) { return true } - t0 := t[0] - t1 := t[1] - switch t0.Kind() { - case reflect.Bool: - return t1.Kind() == reflect.Bool - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch t1.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return true - default: - return false - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - switch t1.Kind() { - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true - default: - return false - } - case reflect.Float32, reflect.Float64: - switch t1.Kind() { - case reflect.Float32, reflect.Float64: - return true - default: - return false - } - case reflect.String: - return t1.Kind() == reflect.String - case reflect.Interface: - return t1.Kind() == reflect.Interface && t0.NumMethod() == 0 && t1.NumMethod() == 0 - default: - return false - } -} - -func acceptsMultiple(t reflect.Type) bool { - if t.Kind() == reflect.Slice { + if isStruct(t) { return true } switch t.Kind() { - case reflect.Pointer: - return acceptsMultiple(t.Elem()) + case reflect.Bool, + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Float32, + reflect.Float64, + reflect.String: + return true + case reflect.Interface: + return t.NumMethod() == 0 + case reflect.Slice: + return bindable(t.Elem()) default: return false } } + +func scalarTypeString(t bind.Scalar) string { + r := reflect.TypeOf(t) + p := strings.Split(r.Name(), ".") + n := p[len(p) - 1] + n = strings.ToLower(n) + return n +} + +func canScan(t bind.Scalar, v any) bool { + switch t { + case bind.Any: + return true + case bind.Bool: + _, ok := bind.BindScalarCreate[bool](v) + return ok + case bind.Int: + _, ok := bind.BindScalarCreate[int](v) + return ok + case bind.Uint: + _, ok := bind.BindScalarCreate[uint](v) + return ok + case bind.Float: + _, ok := bind.BindScalarCreate[float64](v) + return ok + case bind.String: + _, ok := bind.BindScalarCreate[string](v) + return ok + case bind.Duration: + _, ok := bind.BindScalarCreate[time.Duration](v) + return ok + case bind.Time: + _, ok := bind.BindScalarCreate[time.Time](v) + return ok + default: + return false + } +} + +func canScanType(t reflect.Type, v any) bool { + if t == nil { + return false + } + + r := reflect.Zero(t) + return bind.BindScalar(r.Interface(), v) +} + +func allocate(t reflect.Type) reflect.Value { + switch t.Kind() { + case reflect.Pointer: + et := t.Elem() + v := allocate(et) + p := reflect.New(et) + p.Elem().Set(v) + return p + case reflect.Slice: + v := allocate(t.Elem()) + s := reflect.MakeSlice(t, 1, 1) + s.Index(0).Set(v) + return s + default: + return reflect.Zero(t) + } +} + +func bindScalar(receiver reflect.Value, value any) { + bind.BindScalar(receiver.Interface(), value) +} + +func bindFields(receiver reflect.Value, values map[string][]any) []string { + var f []bind.Field + for name, value := range values { + f = append(f, bind.NamedValue(name, value)) + } + + unmapped := bind.BindFields(receiver, f...) + + var names []string + for _, um := range unmapped { + names = append(names, um.Name()) + } + + return names +} diff --git a/script/docreflect/docs.go b/script/docreflect/docs.go index a07db91..ec66bf5 100644 --- a/script/docreflect/docs.go +++ b/script/docreflect/docs.go @@ -1,9 +1,9 @@ package main import ( - "code.squareroundforest.org/arpio/docreflect/generate" "log" "os" + "code.squareroundforest.org/arpio/wand/tools" ) func main() { @@ -11,7 +11,7 @@ func main() { log.Fatalln("expected package name") } - if err := generate.GenerateRegistry(os.Stdout, os.Args[1], os.Args[2:]...); err != nil { + if err := tools.Docreflect(os.Stdout, os.Args[1], os.Args[2:]...); err != nil { log.Fatalln(err) } } diff --git a/tools/exec.go b/tools/exec.go new file mode 100644 index 0000000..16f5cb8 --- /dev/null +++ b/tools/exec.go @@ -0,0 +1,45 @@ +package tools + +import ( + "io" + "strings" + "os/exec" + "os" + "bytes" +) + +func execc(stdin io.Reader, stdout, stderr io.Writer, command string, args []string, env []string) error { + c := strings.Split(command, " ") + cmd := exec.Command(c[0], append(c[1:], args...)...) + cmd.Env = append(os.Environ(), env...) + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + return cmd.Run() +} + +func execCommandDir(out io.Writer, commandDir string, env ...string) error { + stderr := bytes.NewBuffer(nil) + if err := execc(nil, out, stderr, "go run", []string{commandDir}, env); err != nil { + io.Copy(os.Stderr, stderr) + return err + } + + return nil +} + +func execInternal(command string, args ...string) error { + stdout := bytes.NewBuffer(nil) + stderr := bytes.NewBuffer(nil) + if err := execc(nil, stdout, stderr, command, args, nil); err != nil { + io.Copy(os.Stderr, stdout) + io.Copy(os.Stderr, stderr) + return err + } + + return nil +} + +func execTransparent(command string, args ...string) error { + return execc(os.Stdin, os.Stdout, os.Stderr, command, args, nil) +} diff --git a/tools/execwand.go b/tools/execwand.go new file mode 100644 index 0000000..98d3f28 --- /dev/null +++ b/tools/execwand.go @@ -0,0 +1,308 @@ +package tools + +import ( + "io" + "bufio" + "errors" + "fmt" + "unicode" + "hash/fnv" + "bytes" + "encoding/base64" + "strings" + "os" + "sort" + "path" +) + +type ExecOptions struct { + NoCache bool + ClearCache bool + CacheDir string + Import []string + InlineImport []string +} + +func commandReader(in io.Reader) func() ([]string, error) { + var ( + yieldErr error + currentArg []rune + args []string + escapeOne, escapePartial, escapeFull bool + ) + + buf := bufio.NewReader(in) + return func() ([]string, error) { + if yieldErr != nil { + return nil, yieldErr + } + + for { + r, _, err := buf.ReadRune() + if errors.Is(err, io.EOF) { + if len(currentArg) > 0 { + args = append(args, string(currentArg)) + currentArg = nil + } + + yield := args + args = nil + yieldErr = err + return yield, nil + } + + if err != nil { + yieldErr = fmt.Errorf("failed reading from input: %w", err) + return nil, yieldErr + } + + if r == unicode.ReplacementChar { + if len(currentArg) > 0 { + yieldErr = errors.New("broken unicode stream") + return nil, yieldErr + } + + continue + } + + if escapeFull { + if r == '\'' { + escapeFull = false + args = append(args, string(currentArg)) + currentArg = nil + continue + } + + currentArg = append(currentArg, r) + continue + } + + if escapeOne { + escapeOne = false + currentArg = append(currentArg, r) + continue + } + + if escapePartial { + if escapeOne { + escapeOne = false + currentArg = append(currentArg, r) + continue + } + + if r == '\\' { + escapeOne = true + continue + } + + if r == '"' { + escapePartial = false + args = append(args, string(currentArg)) + currentArg = nil + continue + } + + currentArg = append(currentArg, r) + continue + } + + if r == '\n' { + if len(currentArg) > 0 { + args = append(args, string(currentArg)) + currentArg = nil + } + + yield := args + args = nil + return yield, nil + } + + if unicode.IsSpace(r) { + if len(currentArg) > 0 { + args = append(args, string(currentArg)) + currentArg = nil + } + + continue + } + + switch r { + case '\\': + escapeOne = true + case '"': + escapePartial = true + case '\'': + escapeFull = true + default: + currentArg = append(currentArg, r) + } + } + } +} + +func hash(expression string, imports, inlineImports []string) (string, error) { + h := fnv.New128() + h.Write([]byte(expression)) + allImports := append(imports, inlineImports...) + sort.Strings(allImports) + for _, i := range allImports { + h.Write([]byte(i)) + } + + buf := bytes.NewBuffer(nil) + b64 := base64.NewEncoder(base64.RawURLEncoding, buf) + if _, err := b64.Write(h.Sum(nil)); err != nil { + return "", fmt.Errorf("failed to encode expression: %w", err) + } + + if err := b64.Close(); err != nil { + return "", fmt.Errorf("failed to complete encoding of expression: %w", err) + } + + return strings.TrimPrefix(buf.String(), "_"), nil +} + +func printGoFile(fn string, expression string, imports []string, inlineImports []string) error { + f, err := os.Create(fn) + if err != nil { + return err + } + + defer f.Close() + fprintf := func(format string, args ...any) { + if err != nil { + return + } + + _, err = fmt.Fprintf(f, format, args...) + } + + fprintf("package main\n") + for _, i := range imports { + fprintf("import \"%s\"\n", i) + } + + for _, i := range inlineImports { + fprintf("import . \"%s\"\n", i) + } + + fprintf("import \"code.squareroundforest.org/arpio/wand\"\n") + fprintf("func main() {\n") + fprintf("wand.Exec(%s)\n", expression) + fprintf("}") + return err +} + +func execWand(o ExecOptions, args []string) error { + expression, args := args[0], args[1:] + commandHash, err := hash(expression, o.Import, o.InlineImport) + if err != nil { + return err + } + + cacheDir := o.CacheDir + if cacheDir == "" { + cacheDir = path.Join(os.Getenv("HOME"), ".wand") + } + + commandDir := path.Join(cacheDir, commandHash) + if o.NoCache { + commandDir = path.Join(cacheDir, "tmp", commandHash) + } + + if o.NoCache || o.ClearCache { + if err := os.RemoveAll(commandDir); err != nil { + return fmt.Errorf("failed to clear cache: %w", err) + } + } + + if err := os.MkdirAll(commandDir, os.ModePerm); err != nil { + return fmt.Errorf("failed to ensure cache directory: %w", err) + } + + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("error identifying current directory: %w", err) + } + + goGet := func(pkg string) error { + if err := execInternal("go get", pkg); err != nil { + return fmt.Errorf("failed to get go module: %w", err) + } + + return nil + } + + if err := os.Chdir(commandDir); err != nil { + return fmt.Errorf("failed to switch to temporary directory: %w", err) + } + + defer os.Chdir(wd) + gomodPath := path.Join(commandDir, "go.mod") + if _, err := os.Stat(gomodPath); err != nil { + if err := execInternal("go mod init", commandHash); err != nil { + return fmt.Errorf("failed to initialize temporary module: %w", err) + } + + for _, pkg := range o.Import { + if err := goGet(pkg); err != nil { + return err + } + } + + for _, pkg := range o.InlineImport { + if err := goGet(pkg); err != nil { + return err + } + } + + if err := goGet("code.squareroundforest.org/arpio/wand"); err != nil { + return err + } + } + + goFile := path.Join(commandDir, fmt.Sprintf("%s.go", commandHash)) + if _, err := os.Stat(goFile); err != nil { + if err := printGoFile(goFile, expression, o.Import, o.InlineImport); err != nil { + return fmt.Errorf("failed to create temporary go file: %w", err) + } + } + + if err := execTransparent("go run", append([]string{commandDir}, args...)...); err != nil { + return err + } + + if o.NoCache { + if err := os.RemoveAll(commandDir); err != nil { + return fmt.Errorf("failed to clean cache: %w", err) + } + } + + return nil +} + +func readExec(o ExecOptions, stdin io.Reader) error { + readCommand := commandReader(stdin) + for { + args, err := readCommand() + if errors.Is(err, io.EOF) { + return nil + } + + if err != nil { + return err + } + + if err := execWand(o, args); err != nil { + fmt.Fprintln(os.Stderr, err) + } + } +} + +func Exec(o ExecOptions, stdin io.Reader, args ...string) error { + if len(args) == 0 { + return readExec(o, stdin) + } + + return execWand(o, args) +} diff --git a/tools/lib.go b/tools/lib.go new file mode 100644 index 0000000..af60a1f --- /dev/null +++ b/tools/lib.go @@ -0,0 +1,23 @@ +package tools + +import ( + "code.squareroundforest.org/arpio/docreflect/generate" + "io" + "fmt" +) + +type MarkdownOptions struct { + Level int +} + +func Docreflect(out io.Writer, packageName string, gopaths ...string) error { + return generate.GenerateRegistry(out, packageName, gopaths...) +} + +func Man(out io.Writer, commandDir string) error { + return execCommandDir(out, commandDir, "_wandgenerate=man") +} + +func Markdown(out io.Writer, o MarkdownOptions, commandDir string) error { + return execCommandDir(out, commandDir, "_wandgenerate=markdown", fmt.Sprintf("_wandmarkdownlevel=%d", o.Level)) +} diff --git a/tools/tools.go b/tools/tools.go deleted file mode 100644 index dbfd6e3..0000000 --- a/tools/tools.go +++ /dev/null @@ -1,273 +0,0 @@ -package tools - -import ( - "bytes" - "code.squareroundforest.org/arpio/docreflect/generate" - "encoding/base64" - "errors" - "fmt" - "hash/fnv" - "io" - "os" - "os/exec" - "path" - "strings" -) - -type ExecOptions struct { - NoCache bool - ClearCache bool - CacheDir string -} - -func execc(stdin io.Reader, stdout, stderr io.Writer, command string, args []string, env []string) error { - c := strings.Split(command, " ") - cmd := exec.Command(c[0], append(c[1:], args...)...) - cmd.Env = append(os.Environ(), env...) - cmd.Stdin = stdin - cmd.Stdout = stdout - cmd.Stderr = stderr - return cmd.Run() -} - -func execCommandDir(out io.Writer, commandDir string, env ...string) error { - stderr := bytes.NewBuffer(nil) - if err := execc(nil, out, stderr, "go run", []string{commandDir}, env); err != nil { - io.Copy(os.Stderr, stderr) - return err - } - - return nil -} - -func execInternal(command string, args ...string) error { - stdout := bytes.NewBuffer(nil) - stderr := bytes.NewBuffer(nil) - if err := execc(nil, stdout, stderr, command, args, nil); err != nil { - io.Copy(os.Stderr, stdout) - io.Copy(os.Stderr, stderr) - return err - } - - return nil -} - -func execTransparent(command string, args ...string) error { - return execc(os.Stdin, os.Stdout, os.Stderr, command, args, nil) -} - -func Docreflect(out io.Writer, packageName string, gopaths ...string) error { - return generate.GenerateRegistry(out, packageName, gopaths...) -} - -func Man(out io.Writer, commandDir string) error { - return execCommandDir(out, commandDir, "wandgenerate=man") -} - -func Markdown(out io.Writer, commandDir string) error { - return execCommandDir(out, commandDir, "wandgenerate=markdown") -} - -func splitFunction(function string) (pkg string, expression string, err error) { - parts := strings.Split(function, "/") - gopath := parts[:len(parts)-1] - sparts := strings.Split(parts[len(parts)-1], ".") - if len(sparts) == 1 && len(gopath) > 1 { - err = errors.New("function cannot be identified") - return - } - - if len(sparts) == 1 { - expression = sparts[0] - } else { - expression = parts[len(parts)-1] - pkg = strings.Join(append(gopath, sparts[0]), "/") - } - - return -} - -func functionHash(function string) (string, error) { - h := fnv.New128() - h.Write([]byte(function)) - buf := bytes.NewBuffer(nil) - b64 := base64.NewEncoder(base64.RawURLEncoding, buf) - if _, err := b64.Write(h.Sum(nil)); err != nil { - return "", fmt.Errorf("failed to encode function: %w", err) - } - - if err := b64.Close(); err != nil { - return "", fmt.Errorf("failed to complete encoding of function: %w", err) - } - - return strings.TrimPrefix(buf.String(), "_"), nil -} - -func findGomod(wd string) (string, bool) { - gomodDir := wd - for { - gomodPath := path.Join(gomodDir, "go.mod") - f, err := os.Stat(gomodPath) - if err == nil && !f.IsDir() { - return gomodPath, true - } - - if gomodDir == "/" { - return "", false - } - - gomodDir = path.Dir(gomodDir) - } -} - -func copyGomod(mn, dst, src string) error { - srcf, err := os.Open(src) - if err != nil { - return fmt.Errorf("failed to open file: %s; %w", src, err) - } - - defer srcf.Close() - dstf, err := os.Create(dst) - if err != nil { - return fmt.Errorf("failed to create file: %s; %w", dst, err) - } - - defer dstf.Close() - b, err := io.ReadAll(srcf) - if err != nil { - return fmt.Errorf("failed to read go.mod file %s: %w", src, err) - } - - s := string(b) - ss := strings.Split(s, "\n") - for i := range ss { - if strings.HasPrefix(ss[i], "module ") { - ss[i] = fmt.Sprintf("module %s", mn) - break - } - } - - if _, err := dstf.Write([]byte(strings.Join(ss, "\n"))); err != nil { - return fmt.Errorf("failed to write go.mod file %s: %w", dst, err) - } - - return nil -} - -func printFile(fn string, pkg, expression string) error { - f, err := os.Create(fn) - if err != nil { - return err - } - - defer f.Close() - fprintf := func(format string, args ...any) { - if err != nil { - return - } - - _, err = fmt.Fprintf(f, format, args...) - } - - fprintf("package main\n") - if pkg != "" { - fprintf("import \"%s\"\n", pkg) - } - - fprintf("import \"code.squareroundforest.org/arpio/wand\"\n") - fprintf("func main() {\n") - fprintf("wand.Exec(%s)\n", expression) - fprintf("}") - return err -} - -func Exec(o ExecOptions, function string, args ...string) error { - pkg, expression, err := splitFunction(function) - if err != nil { - return err - } - - functionHash, err := functionHash(function) - if err != nil { - return err - } - - cacheDir := o.CacheDir - if cacheDir == "" { - cacheDir = path.Join(os.Getenv("HOME"), ".wand") - } - - functionDir := path.Join(cacheDir, functionHash) - if o.NoCache { - functionDir = path.Join(cacheDir, "tmp", functionHash) - } - - if o.NoCache || o.ClearCache { - if err := os.RemoveAll(functionDir); err != nil { - return fmt.Errorf("failed to clean cache: %w", err) - } - } - - if err := os.MkdirAll(functionDir, os.ModePerm); err != nil { - return fmt.Errorf("failed to ensure cache directory: %w", err) - } - - wd, err := os.Getwd() - if err != nil { - return fmt.Errorf("error identifying current directory: %w", err) - } - - goGet := func(pkg string) error { - if err := execInternal("go get", pkg); err != nil { - return fmt.Errorf("failed to get go module: %w", err) - } - - return nil - } - - if err := os.Chdir(functionDir); err != nil { - return fmt.Errorf("failed to switch to temporary directory: %w", err) - } - - defer os.Chdir(wd) - gomodPath, hasGomod := findGomod(wd) - if hasGomod { - if err := copyGomod(functionHash, path.Join(functionDir, "go.mod"), gomodPath); err != nil { - return err - } - } else { - if err := execInternal("go mod init", functionHash); err != nil { - return fmt.Errorf("failed to initialize temporary module: %w", err) - } - } - - // non-robust way of avoiding importing standard library packages: - if strings.Contains(pkg, ".") { - if err := goGet(pkg); err != nil { - return err - } - } - - if err := goGet("code.squareroundforest.org/arpio/wand"); err != nil { - return err - } - - goFile := path.Join(functionDir, fmt.Sprintf("%s.go", functionHash)) - if _, err := os.Stat(goFile); err != nil { - if err := printFile(goFile, pkg, expression); err != nil { - return fmt.Errorf("failed to create temporary go file: %w", err) - } - } - - if err := execTransparent("go run", append([]string{functionDir}, args...)...); err != nil { - return err - } - - if o.NoCache { - if err := os.RemoveAll(functionDir); err != nil { - return fmt.Errorf("failed to clean cache: %w", err) - } - } - - return nil -}