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
@@ -1,39 +1,43 @@
1
- require_relative 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
2
4
 
3
5
  module Laser
4
6
  module Cutter
5
7
  module Geometry
6
- describe Line do
8
+ RSpec.describe Line do
7
9
  let(:p1) { Point.new(1, 1) }
8
10
  let(:p2) { Point.new(7, 11) }
9
11
  let(:center) { Point.new((7 + 1) / 2, (11 + 1) / 2) }
10
12
  let(:line) { Line.new(p1, p2) }
11
13
 
12
- context '#center' do
13
- it 'should calculate' do
14
+ describe '#center' do
15
+ it 'calculates' do
14
16
  expect(line.center).to eql(center)
15
17
  end
16
18
  end
17
19
 
18
- context '#initialize' do
20
+ describe '#initialize' do
19
21
  let(:line2) { Line.new(from: [1, 1], to: [7, 11]) }
20
22
  let(:line3) { Line.new(from: Point.new(1, 1), to: Point.new(7, 11)) }
21
- it 'should create' do
23
+
24
+ it 'creates' do
22
25
  expect(line2.p1).to eql(Point.new(1, 1))
23
26
  expect(line2.p2).to eql(Point.new(7, 11))
24
27
  end
25
- it 'should properly equal identical line' do
28
+
29
+ it 'properlies equal identical line' do
26
30
  expect(line).to eql(line2)
27
31
  expect(line).to eql(line3)
28
32
  end
29
-
30
33
  end
31
34
 
32
- context '#length' do
35
+ describe '#length' do
33
36
  let(:line1) { Line.new(Point.new(0, 0), Point.new(0, 10)) }
34
37
  let(:line2) { Line.new(Point.new(0, 0), Point.new(-10, 0)) }
35
38
  let(:line3) { Line.new(Point.new(0, 0), Point.new(3, 4)) }
36
- it 'should calculate' do
39
+
40
+ it 'calculates' do
37
41
  expect(line1.length).to be_within(0.001).of(10)
38
42
  expect(line2.length).to be_within(0.001).of(10)
39
43
  expect(line3.length).to be_within(0.001).of(5)
@@ -46,25 +50,27 @@ module Laser
46
50
  let(:l3) { Line.new(Point[0, 0], Point[11, 10]) }
47
51
  let(:l4) { Line.new(Point[20, 20], Point[1, 1]) }
48
52
  let(:l5) { Line.new(Point[11, 10], Point[0, 0]) }
49
- it 'should detect equality' do
53
+
54
+ it 'detects equality' do
50
55
  expect(l1).to eql(Line.new(l1.p1, l1.p2))
51
- expect(l1).to_not eql(Line.new(l1.p1, Point[2, 4]))
56
+ expect(l1).not_to eql(Line.new(l1.p1, Point[2, 4]))
52
57
  expect(l5).to eql(l3)
53
58
  expect(l5.hash).to eql(l3.hash)
54
59
  end
55
60
 
56
- it 'should properly compare' do
61
+ it 'properlies compare' do
57
62
  list = [l4, l3, l1, l2]
58
63
  list.sort!
59
64
  expect(list).to eql([l1, l3, l2, l4])
60
65
  end
61
66
 
62
- it 'should properly uniq' do
67
+ it 'properlies uniq' do
63
68
  list = [l4, l1, l4, l2, l3, l3, l2, l1]
64
69
  list.sort!.uniq!
65
70
  expect(list).to eql([l1, l3, l2, l4])
66
71
  end
67
72
  end
73
+
68
74
  context 'overlaps and such' do
69
75
  let(:l1) { Line.new(Point[0, 0], Point[10, 0]) }
70
76
  let(:l2) { Line.new(Point[7, 0], Point[15, 0]) }
@@ -72,9 +78,8 @@ module Laser
72
78
  let(:l4) { Line.new(Point[0, 1], Point[0, 3]) }
73
79
  let(:l5) { Line.new(Point[0, -1], Point[0, 2]) }
74
80
 
75
-
76
- context '#overlaps?' do
77
- it 'should detect overlap' do
81
+ describe '#overlaps?' do
82
+ it 'detects overlap' do
78
83
  expect(l1.overlaps?(l2)).to be_truthy
79
84
  expect(l1.overlaps?(l3)).to be_falsey
80
85
  expect(l2.overlaps?(l3)).to be_falsey
@@ -84,9 +89,10 @@ module Laser
84
89
  end
85
90
  end
86
91
 
87
- context '#xor' do
88
- let(:xor) { [ Line.new(Point[0, 0], Point[7, 0]), Line.new(Point[10, 0], Point[15, 0])]}
89
- it 'should subtract lines' do
92
+ describe '#xor' do
93
+ let(:xor) { [Line.new(Point[0, 0], Point[7, 0]), Line.new(Point[10, 0], Point[15, 0])] }
94
+
95
+ it 'subtracts lines' do
90
96
  expect(l1.xor(l2)).to eql(xor)
91
97
  expect(l1.overlaps?(l3)).to be_falsey
92
98
  expect(l2.overlaps?(l3)).to be_falsey
@@ -1,22 +1,35 @@
1
- require_relative 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
2
4
 
3
5
  module Laser
4
6
  module Cutter
5
- describe PageManager do
7
+ RSpec.describe PageManager do
6
8
  let(:pm) { Laser::Cutter::PageManager.new(units) }
7
9
 
8
10
  context 'a single value' do
9
11
  context 'to inches' do
10
12
  let(:units) { 'in' }
11
- it 'should be correct' do
12
- expect(pm.value_from_units(150)).to be_within(0.0001).of(150.0/72.0)
13
- expect(pm.value_from_units(150, 'mm')).to be_within(0.0001).of(150.0/25.4)
13
+
14
+ it 'is correct' do
15
+ expect(pm.value_from_units(150)).to be_within(0.0001).of(150.0 / 72.0)
16
+ expect(pm.value_from_units(150, 'mm')).to be_within(0.0001).of(150.0 / 25.4)
14
17
  expect(pm.value_from_units(150, 'in')).to be_within(0.0001).of(150.0)
15
18
  end
16
19
  end
20
+
21
+ context 'when units are a Symbol, as the configuration defaults them' do
22
+ let(:units) { :in }
23
+
24
+ it 'reads them as inches' do
25
+ expect(pm.value_from_units(72)).to be_within(0.0001).of(1.0)
26
+ end
27
+ end
28
+
17
29
  context 'to mm' do
18
30
  let(:units) { 'mm' }
19
- it 'should be correct' do
31
+
32
+ it 'is correct' do
20
33
  expect(pm.value_from_units(150)).to be_within(0.0001).of(25.4 * 150.0 / 72.0)
21
34
  expect(pm.value_from_units(150, 'in')).to be_within(0.0001).of(150.0 * 25.4)
22
35
  expect(pm.value_from_units(150, 'mm')).to be_within(0.0001).of(150.0)
@@ -24,18 +37,21 @@ module Laser
24
37
  end
25
38
  end
26
39
 
27
- context '#list_page_sizes' do
40
+ describe '#list_page_sizes' do
28
41
  context 'formatting of output' do
29
42
  context 'when using inches' do
30
43
  let(:units) { "in" }
31
- it 'should return the list in inches' do
32
- expect(pm.all_page_sizes).to match %r(.*B10\:\s+1\.2\s+x\s+1\.7)
44
+
45
+ it 'returns the list in inches' do
46
+ expect(pm.all_page_sizes).to match /.*B10:\s+1\.2\s+x\s+1\.7/
33
47
  end
34
48
  end
49
+
35
50
  context 'when using mm' do
36
51
  let(:units) { "mm" }
37
- it 'should return the list in mm' do
38
- expect(pm.all_page_sizes).to match %r(.*B10\:\s+31\.0\s+x\s+44\.0)
52
+
53
+ it 'returns the list in mm' do
54
+ expect(pm.all_page_sizes).to match /.*B10:\s+31\.0\s+x\s+44\.0/
39
55
  end
40
56
  end
41
57
  end
@@ -1,18 +1,22 @@
1
- require_relative 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
2
4
 
3
5
  module Laser
4
6
  module Cutter
5
7
  module Notching
6
- describe PathGenerator do
8
+ RSpec.describe PathGenerator do
7
9
  let(:notch) { 2 }
8
10
  let(:thickness) { 1 }
9
11
  let(:center_out) { true }
10
12
  let(:corners) { true }
11
13
 
12
- let(:options) { {notch_width: notch,
13
- thickness: thickness,
14
- center_out: center_out,
15
- corners: corners} }
14
+ let(:options) {
15
+ { notch_width: notch,
16
+ thickness: thickness,
17
+ center_out: center_out,
18
+ corners: corners }
19
+ }
16
20
 
17
21
  let(:outside) { Geometry::Line.new(from: [0, 0], to: [10, 0]) }
18
22
  let(:inside) { Geometry::Line.new(from: [1, 1], to: [9, 1]) }
@@ -20,14 +24,16 @@ module Laser
20
24
  let(:generator) { PathGenerator.new(edge) }
21
25
 
22
26
  context 'edge' do
23
- it 'should properly calculate notch size' do
27
+ it 'properlies calculate notch size' do
24
28
  expect(edge.notch_width).to be_within(0.001).of(1.6)
25
29
  end
30
+
26
31
  context 'edge cases with the edge :)' do
27
32
  let(:notch) { 15 } # too big
28
- it 'should properly handle edge cases' do
33
+
34
+ it 'properlies handle edge cases' do
29
35
  # 3 is the minimum number of notches we support per side
30
- expect(edge.notch_width).to be_within(0.001).of(8.0/3.0)
36
+ expect(edge.notch_width).to be_within(0.001).of(8.0 / 3.0)
31
37
  end
32
38
  end
33
39
  end
@@ -35,7 +41,8 @@ module Laser
35
41
  context 'alternating iterator' do
36
42
  let(:a) { "hello" }
37
43
  let(:b) { "again" }
38
- let(:iterator) { InfiniteIterator.new([a,b]) }
44
+ let(:iterator) { InfiniteIterator.new([a, b]) }
45
+
39
46
  it 'returns things in alternating order' do
40
47
  expect(iterator.next).to eq(a)
41
48
  expect(iterator.next).to eq(b)
@@ -44,7 +51,6 @@ module Laser
44
51
  end
45
52
 
46
53
  context 'shift definition' do
47
-
48
54
  it 'correctly defines shifts' do
49
55
  shifts = generator.send(:define_shifts)
50
56
  expect(edge.outside.length).to eql(10.0)
@@ -55,7 +61,6 @@ module Laser
55
61
  end
56
62
  end
57
63
 
58
-
59
64
  context 'path generation' do
60
65
  # let(:outside) { Line.new(
61
66
  # from: inside.p1.plus(-thickness, -thickness),
@@ -63,7 +68,7 @@ module Laser
63
68
 
64
69
  context 'center out' do
65
70
  it 'generates correct path vertices' do
66
- expect(inside.p1).to_not eql(inside.p2)
71
+ expect(inside.p1).not_to eql(inside.p2)
67
72
  lines = generator.generate
68
73
  expect(lines.size).to be > 5
69
74
 
@@ -81,7 +86,6 @@ module Laser
81
86
  end
82
87
  end
83
88
  end
84
-
85
89
  end
86
90
  end
87
91
  end
@@ -1,25 +1,28 @@
1
- require_relative 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
2
4
 
3
5
  module Laser
4
6
  module Cutter
5
7
  module Geometry
6
- describe Point do
8
+ RSpec.describe Point do
7
9
  let(:p1) { Point.new(1, 2) }
10
+
8
11
  context 'creation' do
9
12
  context 'from string' do
10
- it 'should instantiate correctly' do
11
- expect(Point.new "1,2").to eql(p1)
13
+ it 'instantiates correctly' do
14
+ expect(Point.new("1,2")).to eql(p1)
12
15
  end
13
16
  end
14
17
 
15
18
  context 'from a point' do
16
- it 'should instantiate correctly' do
19
+ it 'instantiates correctly' do
17
20
  expect(Point.new(Point.new("1,2"))).to eql(p1)
18
21
  end
19
22
  end
20
23
 
21
24
  context 'from an array' do
22
- it 'should properly duplicate underlying coordinates and not bug out' do
25
+ it 'properlies duplicate underlying coordinates and not bug out' do
23
26
  a = [1, 2]
24
27
  p2 = Point.new(a)
25
28
  expect(p2).to eql(p1)
@@ -28,25 +31,26 @@ module Laser
28
31
  expect(p2).to eql(p1)
29
32
  end
30
33
 
31
- it 'should create point from class method' do
32
- expect(Point[1,2]).to eql(p1)
34
+ it 'creates point from class method' do
35
+ expect(Point[1, 2]).to eql(p1)
33
36
  end
34
37
  end
35
38
 
36
39
  context 'from a hash' do
37
- it 'should instantiate correctly' do
40
+ it 'instantiates correctly' do
38
41
  expect(Point.new(x: 1, y: 2)).to eql(p1)
39
42
  end
40
43
  end
41
44
  end
42
45
 
43
46
  context 'move by' do
44
- it 'should move properly' do
47
+ it 'moves properly' do
45
48
  p = p1.plus(10, -2)
46
49
  expect(p.x).to be_within(0.001).of(11)
47
50
  expect(p.y).to be_within(0.001).of(0)
48
51
  end
49
- it 'should move cloned version properly' do
52
+
53
+ it 'moves cloned version properly' do
50
54
  p2 = p1.clone.plus(10, -2)
51
55
  expect(p2.x).to be_within(0.001).of(11)
52
56
  expect(p2.y).to be_within(0.001).of(0)
@@ -54,20 +58,21 @@ module Laser
54
58
  end
55
59
 
56
60
  context 'ordering and equality' do
57
- let(:p1) { Point[0,0]}
58
- let(:p2) { Point[10,0]}
59
- let(:p3) { Point[0,10]}
60
- let(:p4) { Point[10,10]}
61
- let(:p5) { Point[-1,-1]}
62
- it 'should propertly sort' do
63
- expect([p3,p2,p1,p4,p5].sort).to eql([p5,p1,p3,p2,p4])
61
+ let(:p1) { Point[0, 0] }
62
+ let(:p2) { Point[10, 0] }
63
+ let(:p3) { Point[0, 10] }
64
+ let(:p4) { Point[10, 10] }
65
+ let(:p5) { Point[-1, -1] }
66
+
67
+ it 'propertlies sort' do
68
+ expect([p3, p2, p1, p4, p5].sort).to eql([p5, p1, p3, p2, p4])
64
69
  end
65
- it 'should detect equality' do
66
- expect(p1).to_not eql(p2)
67
- expect(p2).to eql(Point[10,0])
70
+
71
+ it 'detects equality' do
72
+ expect(p1).not_to eql(p2)
73
+ expect(p2).to eql(Point[10, 0])
68
74
  end
69
75
  end
70
-
71
76
  end
72
77
  end
73
78
  end
@@ -1,10 +1,12 @@
1
- require_relative 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
2
4
 
3
5
  module Laser
4
6
  module Cutter
5
7
  module Geometry
6
- describe Rect do
7
- let(:p1) { Point[ 1.0, 3.0] }
8
+ RSpec.describe Rect do
9
+ let(:p1) { Point[1.0, 3.0] }
8
10
  let(:p2) { Point[11.0, 23.0] }
9
11
 
10
12
  let(:rect1) { Rect.new(p1, p2) }
@@ -22,14 +24,16 @@ module Laser
22
24
  expect(rect1.position).to eql(rect1.vertices[0])
23
25
  expect(rect1.p2).to eql(rect1.vertices[2])
24
26
  end
25
- it 'it generates four side lines' do
27
+
28
+ it 'generates four side lines' do
26
29
  expect(rect1.sides.size).to eql(4)
27
- expect(rect1.sides.first).to be_kind_of(Line)
30
+ expect(rect1.sides.first).to be_a(Line)
28
31
  expect(rect1.sides.first.p1).to eql(p1)
29
32
  expect(rect1.sides[0].p1.to_s).to eql("{1.00000,3.00000}")
30
33
  expect(rect1.sides[1].p1.to_s).to eql("{11.00000,3.00000}")
31
34
  expect(rect1.sides[2].p1).to eql(p1.plus(10, 20))
32
35
  end
36
+
33
37
  it 'can be moved' do
34
38
  expect(rect1.sides[0].p1.to_s).to eql("{1.00000,3.00000}")
35
39
  rect1.x = 1000
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ module Laser
6
+ module Cutter
7
+ module Renderer
8
+ RSpec.describe LayoutRenderer do
9
+ describe '#render' do
10
+ let(:renderer) { LayoutRenderer.new(config) }
11
+ let(:file) { File.expand_path("../../../laser-cutter-pdf-test.#{$$}.pdf", __FILE__) }
12
+
13
+ def render_file(filename)
14
+ real_file = ENV['RSPEC_SAVE_PDF'] ? true : false
15
+ config.validate!
16
+ expect(!File.exist?(filename)) if real_file
17
+ renderer.render
18
+ expect(File.exist?(filename)) if real_file
19
+ expect(File.size(filename) > 0) if real_file
20
+ rescue Exception => e
21
+ warn e.backtrace.join("\n")
22
+ fail e.message
23
+ ensure
24
+ if real_file
25
+ begin
26
+ File.delete(filename)
27
+ rescue StandardError
28
+ nil
29
+ end
30
+ expect(!File.exist?(filename))
31
+ end
32
+ end
33
+
34
+ before do
35
+ unless ENV['RSPEC_SAVE_PDF']
36
+ expect_any_instance_of(Prawn::Document).to receive(:render_file).once
37
+ end
38
+ end
39
+
40
+ context 'metric' do
41
+ let(:config) {
42
+ Laser::Cutter::Configuration.new(
43
+ 'width' => 50,
44
+ 'height' => 60,
45
+ 'depth' => 20,
46
+ 'thickness' => 6,
47
+ 'margin' => 5,
48
+ 'padding' => 3,
49
+ 'notch' => 10,
50
+ 'file' => file
51
+ )
52
+ }
53
+
54
+ it 'is able to generate a PDF file' do
55
+ render_file file
56
+ end
57
+ end
58
+
59
+ context 'imperial' do
60
+ context 'margins and padding provided' do
61
+ let(:config) {
62
+ Laser::Cutter::Configuration.new(
63
+ 'width' => 2.5,
64
+ 'height' => 3.5,
65
+ 'depth' => 2.0,
66
+ 'thickness' => 0.125,
67
+ 'margin' => 0,
68
+ 'padding' => 0.125,
69
+ 'notch' => 0.25,
70
+ 'file' => file,
71
+ 'units' => 'in'
72
+ )
73
+ }
74
+
75
+ it 'is able to generate a PDF file' do
76
+ render_file file
77
+ end
78
+ end
79
+
80
+ context 'margins and padding are defaults' do
81
+ let(:config) {
82
+ Laser::Cutter::Configuration.new(
83
+ 'width' => 2.5,
84
+ 'height' => 2,
85
+ 'depth' => 2.0,
86
+ 'thickness' => 0.125,
87
+ 'notch' => 0.25,
88
+ 'file' => file,
89
+ 'units' => 'in'
90
+ )
91
+ }
92
+
93
+ it 'is able to generate a PDF file' do
94
+ render_file file
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tmpdir'
4
+
5
+ RSpec.describe Laser::Cutter::Renderer::SvgRenderer do
6
+ subject(:renderer) { described_class.new(config) }
7
+
8
+ let(:dir) { Dir.mktmpdir }
9
+ let(:file) { File.join(dir, 'box.svg') }
10
+ let(:settings) { { box: '4x3x2/0.125/0.5', file: file } }
11
+ let(:config) { Laser::Cutter::Configuration.new(settings) }
12
+ let(:svg) { File.read(file) }
13
+
14
+ after { FileUtils.remove_entry(dir) }
15
+
16
+ its(:total) { is_expected.to eq(328) }
17
+
18
+ it 'draws every line, yielding each' do
19
+ yielded = 0
20
+ renderer.render { yielded += 1 }
21
+ expect(yielded).to eq(328)
22
+ expect(svg.scan('<line').size).to eq(328)
23
+ end
24
+
25
+ it 'sizes the page in the units of the box' do
26
+ renderer.render
27
+ expect(svg).to include('width="9.2012in" height="11.5512in"')
28
+ end
29
+
30
+ # Kerf moves the outline out by half its width, into the margin.
31
+ it 'keeps every line on the page' do
32
+ renderer.render
33
+ coordinates = svg.scan(/[xy][12]="([\d.-]+)"/).flatten.map(&:to_f)
34
+ expect(coordinates.min).to be_within(0.002).of(0.125)
35
+ expect(coordinates.max).to be_within(0.002).of(11.5512 - 0.125)
36
+ end
37
+
38
+ it 'describes the box' do
39
+ renderer.render
40
+ expect(svg).to match(/<desc>\s*Made with laser-cutter.*thickness: 0\.125/)
41
+ end
42
+
43
+ context 'without metadata' do
44
+ let(:settings) { super().merge(metadata: false) }
45
+
46
+ it 'leaves the description out' do
47
+ renderer.render
48
+ expect(svg).not_to include('<desc>')
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Laser::Cutter::Renderer do
4
+ let(:config) { Laser::Cutter::Configuration.new(box: '4x3x2/0.125', file: 'box') }
5
+
6
+ describe '.for' do
7
+ it 'writes a PDF with the layout renderer' do
8
+ expect(described_class.for('PDF', config)).to be_a(described_class::LayoutRenderer)
9
+ end
10
+
11
+ it 'writes an SVG with the SVG renderer' do
12
+ expect(described_class.for(:svg, config)).to be_a(described_class::SvgRenderer)
13
+ end
14
+
15
+ it 'raises on a format it does not write' do
16
+ expect { described_class.for('dxf', config) }.to raise_error(Laser::Cutter::Error, /unknown format/)
17
+ end
18
+ end
19
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,28 +1,41 @@
1
- # This file was generated by the `rspec --init` command. Conventionally, all
2
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
- # Require this file using `require "spec_helper"` to ensure that it is only
4
- # loaded once.
5
- #
6
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
1
+ # frozen_string_literal: true
7
2
 
8
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __FILE__)
9
- require 'rubygems'
10
- require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
11
- require 'simplecov'
12
- SimpleCov.start
13
- require 'laser-cutter'
3
+ # SimpleCov must start before the gem's own code loads, or that code counts as
4
+ # never run. The badge lands in docs/badges/coverage_badge.svg for the README.
5
+ require "simplecov"
6
+ require "coverage/badge"
7
+ require "fileutils"
14
8
 
9
+ SimpleCov.start do
10
+ # SimpleCov 1.x gives project-relative paths without a leading slash; 0.22 kept it.
11
+ skip %r{\A/?(spec|test)/}
12
+ minimum_coverage 95
13
+ formatter SimpleCov::Formatter::MultiFormatter.new(
14
+ [SimpleCov::Formatter::HTMLFormatter, Coverage::Badge::Formatter]
15
+ )
16
+ end
15
17
 
18
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
16
19
 
17
- RSpec.configure do |config|
18
- #config.treat_symbols_as_metadata_keys_with_true_values = true
19
- config.run_all_when_everything_filtered = true
20
- config.filter_run :focus
21
-
22
- # Run specs in random order to surface order dependencies. If you find an
23
- # order dependency and want to debug it, you can fix the order by providing
24
- # the seed, which is printed after each run.
25
- # --seed 1234
26
- config.order = 'random'
20
+ SimpleCov.at_exit do
21
+ SimpleCov.result.format!
22
+ puts "Coverage: #{SimpleCov.result.covered_percent.round(2)}%" # rubocop:disable RSpec/Output
23
+ FileUtils.mkdir_p("docs/badges")
24
+ FileUtils.mv("coverage/badge.svg", "docs/badges/coverage_badge.svg")
27
25
  end
28
26
 
27
+ require "rspec/its"
28
+ require "laser/cutter"
29
+
30
+ Dir[File.expand_path("support/**/*.rb", __dir__)].each { |file| require file }
31
+
32
+ RSpec.configure do |config|
33
+ config.example_status_persistence_file_path = ".rspec_status"
34
+ config.disable_monkey_patching!
35
+ config.order = :random
36
+ Kernel.srand config.seed
37
+
38
+ config.expect_with :rspec do |c|
39
+ c.syntax = :expect
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aruba/rspec"
4
+
5
+ # In-process: the suite builds a Launcher per example instead of forking, so
6
+ # the CLI's own code counts towards coverage.
7
+ Aruba.configure do |config|
8
+ config.command_launcher = :in_process
9
+ config.main_class = Laser::Cutter::Launcher
10
+ end