laser-cutter 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +2 -0
  3. data/.github/workflows/lint.yml +22 -0
  4. data/.github/workflows/rspec.yml +22 -0
  5. data/.gitignore +5 -5
  6. data/.plans/001.00-dry-cli-migration/plan.md +29 -0
  7. data/.relaxed_rubocop.yml +153 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +31 -0
  10. data/.rubocop_todo.yml +90 -0
  11. data/.ruby-version +1 -0
  12. data/CLAUDE.md +62 -0
  13. data/CONTRIBUTING.md +2 -4
  14. data/Gemfile +14 -0
  15. data/README.md +110 -87
  16. data/Rakefile +27 -13
  17. data/docs/badges/coverage_badge.svg +21 -0
  18. data/exe/laser-cutter +8 -0
  19. data/exe/lc +1 -0
  20. data/justfile +97 -0
  21. data/laser-cutter.gemspec +26 -14
  22. data/lib/laser/cutter/aggregator.rb +101 -0
  23. data/lib/laser/cutter/box.rb +173 -0
  24. data/lib/laser/cutter/cli/command.rb +46 -0
  25. data/lib/laser/cutter/cli/completion.rb +21 -0
  26. data/lib/laser/cutter/cli/config_file.rb +36 -0
  27. data/lib/laser/cutter/cli/examples.rb +42 -0
  28. data/lib/laser/cutter/cli/generate.rb +90 -0
  29. data/lib/laser/cutter/cli/help.rb +20 -0
  30. data/lib/laser/cutter/cli/page_sizes.rb +19 -0
  31. data/lib/laser/cutter/cli/version.rb +15 -0
  32. data/lib/laser/cutter/cli.rb +49 -0
  33. data/lib/laser/cutter/configuration.rb +101 -0
  34. data/lib/{laser-cutter → laser/cutter}/geometry/dimensions.rb +2 -3
  35. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/line.rb +19 -18
  36. data/lib/{laser-cutter → laser/cutter}/geometry/point.rb +4 -2
  37. data/lib/{laser-cutter/geometry/shape → laser/cutter/geometry}/rect.rb +6 -7
  38. data/lib/{laser-cutter → laser/cutter}/geometry/shape.rb +7 -7
  39. data/lib/{laser-cutter → laser/cutter}/geometry/tuple.rb +40 -36
  40. data/lib/laser/cutter/launcher.rb +40 -0
  41. data/lib/laser/cutter/missing_option.rb +7 -0
  42. data/lib/laser/cutter/notching/base.rb +19 -0
  43. data/lib/{laser-cutter → laser/cutter}/notching/edge.rb +23 -22
  44. data/lib/laser/cutter/notching/infinite_iterator.rb +27 -0
  45. data/lib/{laser-cutter → laser/cutter}/notching/path_generator.rb +76 -94
  46. data/lib/laser/cutter/notching/shift.rb +17 -0
  47. data/lib/laser/cutter/page_manager.rb +50 -0
  48. data/lib/{laser-cutter → laser/cutter}/renderer/base.rb +3 -2
  49. data/lib/laser/cutter/renderer/box_renderer.rb +40 -0
  50. data/lib/laser/cutter/renderer/layout_renderer.rb +61 -0
  51. data/lib/laser/cutter/renderer/line_renderer.rb +21 -0
  52. data/lib/laser/cutter/renderer/meta_renderer.rb +83 -0
  53. data/lib/{laser-cutter → laser/cutter}/renderer/rect_renderer.rb +3 -1
  54. data/lib/laser/cutter/renderer/svg_renderer.rb +65 -0
  55. data/lib/laser/cutter/renderer.rb +24 -0
  56. data/lib/laser/cutter/units_converter.rb +15 -0
  57. data/lib/laser/cutter/version.rb +7 -0
  58. data/lib/laser/cutter/zero_value_not_allowed.rb +7 -0
  59. data/lib/laser/cutter.rb +29 -0
  60. data/lib/laser-cutter.rb +2 -14
  61. data/lib/laser_cutter.rb +3 -0
  62. data/spec/laser/cutter/aggregator_spec.rb +70 -0
  63. data/spec/laser/cutter/box_spec.rb +61 -0
  64. data/spec/laser/cutter/cli/config_file_spec.rb +31 -0
  65. data/spec/laser/cutter/cli_spec.rb +186 -0
  66. data/spec/laser/cutter/configuration_spec.rb +101 -0
  67. data/spec/{dimensions_spec.rb → laser/cutter/dimensions_spec.rb} +8 -6
  68. data/spec/laser/cutter/edge_spec.rb +64 -0
  69. data/spec/{line_spec.rb → laser/cutter/line_spec.rb} +26 -20
  70. data/spec/{page_manager_spec.rb → laser/cutter/page_manager_spec.rb} +27 -11
  71. data/spec/{path_generator_spec.rb → laser/cutter/path_generator_spec.rb} +18 -14
  72. data/spec/{point_spec.rb → laser/cutter/point_spec.rb} +27 -22
  73. data/spec/{rect_spec.rb → laser/cutter/rect_spec.rb} +9 -5
  74. data/spec/laser/cutter/renderer/layout_renderer_spec.rb +102 -0
  75. data/spec/laser/cutter/renderer/svg_renderer_spec.rb +51 -0
  76. data/spec/laser/cutter/renderer_spec.rb +19 -0
  77. data/spec/spec_helper.rb +35 -22
  78. data/spec/support/aruba.rb +10 -0
  79. metadata +202 -76
  80. data/.travis.yml +0 -26
  81. data/BOXMAKER.md +0 -51
  82. data/LICENSE +0 -22
  83. data/bin/laser-cutter +0 -19
  84. data/lib/laser-cutter/aggregator.rb +0 -57
  85. data/lib/laser-cutter/box.rb +0 -183
  86. data/lib/laser-cutter/cli/opt_parser.rb +0 -131
  87. data/lib/laser-cutter/cli/serializer.rb +0 -51
  88. data/lib/laser-cutter/configuration.rb +0 -83
  89. data/lib/laser-cutter/face.rb +0 -18
  90. data/lib/laser-cutter/geometry.rb +0 -11
  91. data/lib/laser-cutter/notching.rb +0 -9
  92. data/lib/laser-cutter/page_manager.rb +0 -43
  93. data/lib/laser-cutter/renderer/box_renderer.rb +0 -36
  94. data/lib/laser-cutter/renderer/layout_renderer.rb +0 -59
  95. data/lib/laser-cutter/renderer/line_renderer.rb +0 -17
  96. data/lib/laser-cutter/renderer/meta_renderer.rb +0 -77
  97. data/lib/laser-cutter/renderer.rb +0 -14
  98. data/lib/laser-cutter/version.rb +0 -5
  99. data/spec/aggregator_spec.rb +0 -65
  100. data/spec/box_spec.rb +0 -39
  101. data/spec/configuration_spec.rb +0 -81
  102. data/spec/edge_spec.rb +0 -43
  103. data/spec/renderer_spec.rb +0 -72
  104. /data/docs/{comparison.jpg → images/comparison.jpg} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3a3d7b723ed7171e84a93783545f037adeeeb122
4
- data.tar.gz: d3571947154cda05176e1d1bbb84d8d2ee140c02
2
+ SHA256:
3
+ metadata.gz: 4cb71396bbb2f8c49f903912ebe28e43ccf2fa278f7fc390bb68f4002dd678a5
4
+ data.tar.gz: 51bee7e480b3fd7578a6668a9f9c82ef3894e2f388bc3db336b76eefcff89098
5
5
  SHA512:
6
- metadata.gz: 870c36a4b07b2d4190076c524917f4322d599c58f5ff9b226b47e00a011dd63f722a7153ed885c192cbe0caefbda17fb150269a7822f4e36a001f4913a435d19
7
- data.tar.gz: 67c264109139865d62292cb8cea2573ec5dbc569fec92316b00a14f942c0ea15d096d0391d1a5a275e3eaf78c7f9f8f179e95331f93859df982f7c7a80589e08
6
+ metadata.gz: a146752a70a7b9f73c6cf46c0472ec5a7456b50e2623188c7665e057c0cf98bd687d05946137c968995783b569a03fb7c99afca3528e084f712d46298e9ef261
7
+ data.tar.gz: e8b3eecd4f8fe18078599f989f3a14e94bb9c4df0ff6504619f196849b1c016ebbcb48dad5153465e810cbcce38566cf00d5e2c67ce9bb8fbfca9aa4d80b0455
data/.envrc ADDED
@@ -0,0 +1,2 @@
1
+ PATH_add bin
2
+ PATH_add exe
@@ -0,0 +1,22 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby-version: [4.0.6]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby ${{ matrix.ruby-version }}
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby-version }}
18
+ bundler-cache: true
19
+ - name: Run Specs
20
+ run: |
21
+ bundle check || bundle install -j 4
22
+ bundle exec rubocop
@@ -0,0 +1,22 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby-version: [4.0.6]
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby ${{ matrix.ruby-version }}
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby-version }}
18
+ bundler-cache: true
19
+ - name: Run Specs
20
+ run: |
21
+ bundle check || bundle install -j 4
22
+ bundle exec rspec --format documentation
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  **.pdf
2
2
  **.DS_Store
3
+ .rspec_status
3
4
  **/*.swp
4
5
  *.gem
5
6
  *.rbc
@@ -9,8 +10,6 @@
9
10
  Gemfile.lock
10
11
  InstalledFiles
11
12
  _yardoc
12
- coverage
13
- lib/bundler/man
14
13
  pkg
15
14
  rdoc
16
15
  spec/reports
@@ -23,6 +22,7 @@ tmp
23
22
  *.a
24
23
  mkmf.log
25
24
  .idea/
26
- **output.pdf
27
- .ruby_version
28
- doc
25
+ .ruby-lsp
26
+ /coverage
27
+ !docs/coverage/
28
+ /doc
@@ -0,0 +1,29 @@
1
+ # 001.00 Migrate the CLI to dry-cli, load with Zeitwerk
2
+
3
+ Branch `kig/migrate-to-dry-cli`, off `kig/2.0.0-alpha`; the PR targets `kig/2.0.0-alpha`.
4
+
5
+ ## Decisions
6
+
7
+ - `lib/laser_cutter/` moves to `lib/laser/cutter/`, loaded by `Zeitwerk::Loader.for_gem_extension(Laser)`, eager-loaded. `lib/laser-cutter.rb` and `lib/laser_cutter.rb` remain as shims.
8
+
9
+ - `generate` takes height as `-H` / `--height`: dry-cli owns `-h`, and `-v` means verbose. `version` answers to `-V` / `--version`.
10
+
11
+ - Ruby 4.0.6, pinned in `.ruby-version`.
12
+
13
+ - The executables move from `bin/` to `exe/` and call `Laser::Cutter::Launcher`.
14
+
15
+ - Help and boxes share one width, 90 columns or fewer; a progress bar is 60 cells.
16
+
17
+ - `--inside-box` draws the unkerfed outline, which `--debug` used to do by accident; `--debug` is gone, `--verbose` prints the backtrace.
18
+
19
+ ## Work
20
+
21
+ - [x] Zeitwerk: one constant per file (split `configuration.rb` and `path_generator.rb`, move `geometry/shape/{line,rect}.rb` up), eager load
22
+ - [x] dry-cli with dry-cli-help, dry-cli-ui and dry-cli-autocomplete; commands `generate`, `page-sizes`, `examples`, `help`, `version`, `completion`
23
+ - [x] `generate -f/--format pdf|svg`, PDF by default
24
+ - [x] Green progress bar, at most 60 cells wide, advancing once per line drawn
25
+ - [x] SVG output through `victor`
26
+ - [x] Launcher plus in-process Aruba suite
27
+ - [x] Bug: a notch that divides the sides exactly (4x3x2, notch 0.5) makes adjacent sides mismatch
28
+ - [x] README and CLAUDE.md updated
29
+ - [x] Bug: PageManager read the default Symbol units as millimeters
@@ -0,0 +1,153 @@
1
+ # Relaxed.Ruby.Style
2
+ ## Version 2.5
3
+
4
+ Style/Alias:
5
+ Enabled: false
6
+ StyleGuide: https://relaxed.ruby.style/#stylealias
7
+
8
+ Style/AsciiComments:
9
+ Enabled: false
10
+ StyleGuide: https://relaxed.ruby.style/#styleasciicomments
11
+
12
+ Style/BeginBlock:
13
+ Enabled: false
14
+ StyleGuide: https://relaxed.ruby.style/#stylebeginblock
15
+
16
+ Style/BlockDelimiters:
17
+ Enabled: false
18
+ StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
19
+
20
+ Style/CommentAnnotation:
21
+ Enabled: false
22
+ StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
23
+
24
+ Style/Documentation:
25
+ Enabled: false
26
+ StyleGuide: https://relaxed.ruby.style/#styledocumentation
27
+
28
+ Layout/DotPosition:
29
+ Enabled: false
30
+ StyleGuide: https://relaxed.ruby.style/#layoutdotposition
31
+
32
+ Style/DoubleNegation:
33
+ Enabled: false
34
+ StyleGuide: https://relaxed.ruby.style/#styledoublenegation
35
+
36
+ Style/EndBlock:
37
+ Enabled: false
38
+ StyleGuide: https://relaxed.ruby.style/#styleendblock
39
+
40
+ Style/FormatString:
41
+ Enabled: false
42
+ StyleGuide: https://relaxed.ruby.style/#styleformatstring
43
+
44
+ Style/IfUnlessModifier:
45
+ Enabled: false
46
+ StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
47
+
48
+ Style/Lambda:
49
+ Enabled: false
50
+ StyleGuide: https://relaxed.ruby.style/#stylelambda
51
+
52
+ Style/ModuleFunction:
53
+ Enabled: false
54
+ StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
55
+
56
+ Style/MultilineBlockChain:
57
+ Enabled: false
58
+ StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
59
+
60
+ Style/NegatedIf:
61
+ Enabled: false
62
+ StyleGuide: https://relaxed.ruby.style/#stylenegatedif
63
+
64
+ Style/NegatedWhile:
65
+ Enabled: false
66
+ StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
67
+
68
+ Style/NumericPredicate:
69
+ Enabled: false
70
+ StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
71
+
72
+ Style/ParallelAssignment:
73
+ Enabled: false
74
+ StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
75
+
76
+ Style/PercentLiteralDelimiters:
77
+ Enabled: false
78
+ StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
79
+
80
+ Style/PerlBackrefs:
81
+ Enabled: false
82
+ StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
83
+
84
+ Style/Semicolon:
85
+ Enabled: false
86
+ StyleGuide: https://relaxed.ruby.style/#stylesemicolon
87
+
88
+ Style/SignalException:
89
+ Enabled: false
90
+ StyleGuide: https://relaxed.ruby.style/#stylesignalexception
91
+
92
+ Style/SingleLineBlockParams:
93
+ Enabled: false
94
+ StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
95
+
96
+ Style/SingleLineMethods:
97
+ Enabled: false
98
+ StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
99
+
100
+ Layout/SpaceBeforeBlockBraces:
101
+ Enabled: false
102
+ StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
103
+
104
+ Layout/SpaceInsideParens:
105
+ Enabled: false
106
+ StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
107
+
108
+ Style/SpecialGlobalVars:
109
+ Enabled: false
110
+ StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
111
+
112
+ Style/StringLiterals:
113
+ Enabled: false
114
+ StyleGuide: https://relaxed.ruby.style/#stylestringliterals
115
+
116
+ Style/TrailingCommaInArguments:
117
+ Enabled: false
118
+ StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
119
+
120
+ Style/TrailingCommaInArrayLiteral:
121
+ Enabled: false
122
+ StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
123
+
124
+ Style/TrailingCommaInHashLiteral:
125
+ Enabled: false
126
+ StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
127
+
128
+ Style/SymbolArray:
129
+ Enabled: false
130
+ StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
131
+
132
+ Style/WhileUntilModifier:
133
+ Enabled: false
134
+ StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
135
+
136
+ Style/WordArray:
137
+ Enabled: false
138
+ StyleGuide: https://relaxed.ruby.style/#stylewordarray
139
+
140
+ Lint/AmbiguousRegexpLiteral:
141
+ Enabled: false
142
+ StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
143
+
144
+ Lint/AssignmentInCondition:
145
+ Enabled: false
146
+ StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
147
+
148
+ Layout/LineLength:
149
+ Enabled: false
150
+
151
+ Metrics:
152
+ Enabled: false
153
+
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --format progress
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,31 @@
1
+ inherit_from:
2
+ - .rubocop_todo.yml
3
+ - .relaxed_rubocop.yml
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ SuggestExtensions: false
8
+ TargetRubyVersion: 4.0
9
+
10
+ Layout/LineLength:
11
+ Max: 120
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - "spec/**/*"
16
+
17
+ Lint/ConstantDefinitionInBlock:
18
+ Enabled: false
19
+
20
+ Lint/StructNewOverride:
21
+ Enabled: false
22
+
23
+ Layout/HashAlignment:
24
+ EnforcedColonStyle: table
25
+ EnforcedHashRocketStyle: table
26
+
27
+ Layout/ArgumentAlignment:
28
+ EnforcedStyle: with_first_argument
29
+
30
+ Naming/MethodParameterName:
31
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,90 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-09-29 22:33:21 UTC using RuboCop version 1.91.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ Gemspec/RequiredRubyVersion:
11
+ Exclude:
12
+ - 'laser-cutter.gemspec'
13
+
14
+ # Offense count: 1
15
+ # This cop supports safe autocorrection (--autocorrect).
16
+ Lint/CopDirectiveSyntax:
17
+ Exclude:
18
+ - 'spec/spec_helper.rb'
19
+
20
+ # Offense count: 1
21
+ # Configuration parameters: DelegatingMethods, AllowedCrossFilePaths.
22
+ # DelegatingMethods: delegate
23
+ Lint/DuplicateMethods:
24
+ Exclude:
25
+ - 'lib/laser/cutter/geometry/shape.rb'
26
+
27
+ # Offense count: 2
28
+ # Configuration parameters: AllowedParentClasses.
29
+ Lint/MissingSuper:
30
+ Exclude:
31
+ - 'lib/laser/cutter/geometry/line.rb'
32
+ - 'lib/laser/cutter/renderer/meta_renderer.rb'
33
+
34
+ # Offense count: 1
35
+ # This cop supports safe autocorrection (--autocorrect).
36
+ Lint/RedundantCopDisableDirective:
37
+ Exclude:
38
+ - 'spec/spec_helper.rb'
39
+
40
+ # Offense count: 3
41
+ Lint/RescueException:
42
+ Exclude:
43
+ - 'lib/laser/cutter/cli/serializer.rb'
44
+ - 'spec/laser/cutter/renderer/layout_renderer_spec.rb'
45
+
46
+ # Offense count: 1
47
+ # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
48
+ # CheckDefinitionPathHierarchyRoots: lib, spec, test, src
49
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
50
+ Naming/FileName:
51
+ Exclude:
52
+ - 'Rakefile.rb'
53
+ - 'lib/laser-cutter.rb'
54
+
55
+ # Offense count: 3
56
+ # Configuration parameters: ForbiddenDelimiters.
57
+ # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$))
58
+ Naming/HeredocDelimiterNaming:
59
+ Exclude:
60
+ - 'lib/laser/cutter/cli/opt_parser.rb'
61
+ - 'lib/laser/cutter/renderer/meta_renderer.rb'
62
+
63
+ # Offense count: 3
64
+ # This cop supports safe autocorrection (--autocorrect).
65
+ # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.
66
+ # SupportedStyles: annotated, template, unannotated
67
+ Style/FormatStringToken:
68
+ EnforcedStyle: unannotated
69
+
70
+ # Offense count: 1
71
+ # This cop supports unsafe autocorrection (--autocorrect-all).
72
+ # Configuration parameters: EnforcedStyle.
73
+ # SupportedStyles: always, always_true, never
74
+ Style/FrozenStringLiteralComment:
75
+ Exclude:
76
+ - '**/*.arb'
77
+ - 'lib/laser/cutter/page_manager.rb'
78
+
79
+ # Offense count: 1
80
+ # This cop supports unsafe autocorrection (--autocorrect-all).
81
+ Style/MapToHash:
82
+ Exclude:
83
+ - 'lib/laser/cutter/configuration.rb'
84
+
85
+ # Offense count: 1
86
+ # This cop supports unsafe autocorrection (--autocorrect-all).
87
+ # Configuration parameters: Mode.
88
+ Style/StringConcatenation:
89
+ Exclude:
90
+ - 'lib/laser/cutter/renderer/meta_renderer.rb'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.6
data/CLAUDE.md ADDED
@@ -0,0 +1,62 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `laser-cutter` is a Ruby gem and CLI that generates a PDF of the six notched panels of a box. You cut the panels on a laser cutter and they snap together. Prawn renders the PDF. The notch algorithm favors **symmetric** panels over identical notch lengths.
8
+
9
+ ## Commands
10
+
11
+ The `justfile` drives everything. Its recipes wrap `bundle exec` in `eval "$(rbenv init - bash)"`. `.ruby-version` and CI pin Ruby **4.0.6**; dry-cli-help and dry-cli-ui need 4.0.
12
+
13
+ ```bash
14
+ just lint # rubocop
15
+ just format # rubocop -a, plus mdformat on every *.md
16
+ just test # rspec (progress format, random order)
17
+ just test spec/laser/cutter/edge_spec.rb:42 # one file or example
18
+ just test-docs # rspec --format documentation
19
+ just ci # lint + test-coverage
20
+ just doc # YARD docs
21
+ ```
22
+
23
+ - `bundle exec rspec`, `bundle exec rubocop` and `bundle exec rake` (default task `spec`) also work.
24
+ - Every rspec run rewrites `docs/badges/coverage_badge.svg` through the SimpleCov `at_exit` hook in `spec/spec_helper.rb`. That file shows up in `git status` after any test run.
25
+ - `spec_helper.rb` fails the run under 95% line coverage. The Aruba suite in `spec/laser/cutter/cli_spec.rb` counts towards it.
26
+
27
+ ## Architecture
28
+
29
+ Everything lives under `Laser::Cutter`, in `lib/laser/cutter/`. `lib/laser/cutter.rb` requires the third-party gems, then sets up `Zeitwerk::Loader.for_gem_extension(Laser)` and **eager-loads** it. So: one constant per file, the file name matches the constant, and no `require` between the gem's own files. `lib/laser-cutter.rb` and `lib/laser_cutter.rb` are shims.
30
+
31
+ Pipeline, from config to file:
32
+
33
+ 1. **`Configuration`** is a `Hashie::Mash` with symbolized keys. It parses the `--box WxHxD/T[/N]` shorthand, casts numeric strings to floats, and merges per-unit defaults for kerf, margin, padding and stroke. Notch defaults to `3 × thickness`. `validate!` raises `MissingOption` / `ZeroValueNotAllowed`. `units` defaults to the Symbol `:in`, and arrives as a String from the command line, so compare it with `to_s` or `to_sym`.
34
+ 1. **`Renderer.for(format, config)`** picks `LayoutRenderer` (PDF, Prawn) or `SvgRenderer` (SVG, Victor). Both answer `total` (lines to draw) and `render { |line| }`, which yields after each line. That block drives the progress bar.
35
+ - `LayoutRenderer` draws a `BoxRenderer`, a `MetaRenderer` when `config.metadata` is set, and a second red `BoxRenderer` without kerf when `config.debug` is set. It sizes the page from the box enclosure unless `page_size` is given.
36
+ - `SvgRenderer` fits the page to the box, flips y (SVG counts down from the top), and writes the metadata as a `<desc>`.
37
+ 1. **`Box`** models the six faces as `Geometry::Rect`s. `position_faces!` lays them out in a cross (see the ASCII diagram in that method). `generate_notches` pairs each side of a face with the matching side of its outer bounding rect (face grown by `thickness`) as a **`Notching::Edge`**. The `conf` table sets per-face alignment: `valign`/`halign` decide whether a side's center notch points `:out` or `:in`. `corners` plus `pick_corners_face` decide which face fills the corner squares.
38
+ 1. **`Notching::Edge`** holds the inside and outside lines of one side, both shifted by `kerf / 2`. `calculate_notch_width!` forces an **odd** notch count of at least 3 and recomputes the real notch width, so the requested notch is only a guide. It rounds `length / notch` to `RATIO_DIGITS` before `ceil`: two panels meeting at a joint must get the same count, and float noise used to split them when the notch divided the side exactly.
39
+ 1. **`Notching::PathGenerator`** turns an `Edge` into `Geometry::Line`s. It zigzags between the inside and outside lines using `Shift` deltas from two alternating `InfiniteIterator`s. It widens or narrows notches by `kerf` and adds the corner boxes.
40
+ 1. **`Aggregator`** merges the lines of a face into the outline to cut. Neighbouring edges draw shared stretches twice, and a shared stretch runs through the material, so each collinear group is combined as a symmetric difference: a point is cut when an odd number of lines cover it. It groups lines by axis and offset and sweeps each group once, so a face takes n log n; the old pairwise version made a 100×80×60 box take 55 seconds.
41
+
42
+ Geometry is unitless, in the config's units. Conversion to PDF points happens only at render time (`value.send(:in)` / `.send(:mm)`). `PageManager#value_from_units` converts PDF points back.
43
+
44
+ ### Command line
45
+
46
+ - `exe/laser-cutter` (and `exe/lc`) call `Laser::Cutter::Launcher.new(ARGV).execute!`. The Launcher takes argv, the three streams and `kernel`, and exits only through `kernel.exit`. Aruba runs it in-process (`spec/support/aruba.rb`), so commands must write to `out` and `err`, never to `$stdout`.
47
+ - `CLI` (`cli.rb`) is the dry-cli registry and the `Dry::CLI::Help.configure` block. Commands live in `cli/`: `generate`, `page-sizes`, `examples`, `help`, `version`, `completion`.
48
+ - Every message goes through `ui`: errors in `ui.error` boxes on STDERR (the Launcher draws them), `generate` opens with `ui.info` and closes with `ui.success`.
49
+ - `CLI::Command` is the base: it includes `Dry::CLI::UI`, declares `-v`, and offers `progress(label, total:)`, a green bar of 60 cells. Help and boxes share one width, `CLI.help_width`: the terminal's less 6, and 90 at most.
50
+ - `-h` belongs to help, so height is `-H`.
51
+
52
+ ## Known gaps
53
+
54
+ - `just build` is an empty recipe, so `just publish` builds nothing. `rake build` is the real build.
55
+ - The `Rakefile` YARD title is copied from another project, and it references a `CHANGELOG.md` that does not exist.
56
+ - The gemspec lists `tty-*` and `pastel` directly, though only dry-cli-ui uses them.
57
+ - `Box` still carries the comment "badly needs refactoring and tests".
58
+
59
+ ## Conventions
60
+
61
+ - `.rubocop.yml` inherits `.relaxed_rubocop.yml` and `.rubocop_todo.yml`, with a 120-column line limit and table-aligned hashes.
62
+ - Specs mirror `lib/` under `spec/laser/cutter/` and use `rspec-its`, `disable_monkey_patching!` and random order.
data/CONTRIBUTING.md CHANGED
@@ -1,5 +1,4 @@
1
- Contributing to Laser-Cutter
2
- ======================
1
+ # Contributing to Laser-Cutter
3
2
 
4
3
  You're encouraged to submit [pull requests](https://github.com/kigster/laser-cutter/pulls), [propose features and discuss issues](https://github.com/kigster/laser-cutter/issues).
5
4
 
@@ -34,8 +33,7 @@ bundle exec rake
34
33
 
35
34
  #### Write Tests
36
35
 
37
- Try to write a test that reproduces the problem you're trying to fix or describes a
38
- feature that you want to build. Add to [spec/laser-cutter](spec/laser-cutter).
36
+ Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/laser-cutter](spec/laser-cutter).
39
37
 
40
38
  We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.
41
39
 
data/Gemfile CHANGED
@@ -1,5 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in laser-cutter-cutter.gemspec
4
6
  gemspec
5
7
 
8
+ group 'development', 'test' do
9
+ gem 'aruba'
10
+ gem 'coverage-badge'
11
+ gem 'rake'
12
+ gem 'rspec'
13
+ gem 'rspec-its'
14
+ gem 'rubocop', require: false
15
+ gem 'rubocop-rake', require: false
16
+ gem 'rubocop-rspec', require: false
17
+ gem 'simplecov'
18
+ gem 'yard'
19
+ end