engineering 0.2 → 0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: edf0dffae8854098af2df322958e8540d05676a2
4
- data.tar.gz: f1a408fab212a5268b6a10f81117dd9c5be3c6c1
3
+ metadata.gz: 77f2d000480b81c235ca318065fa10b03ae24c53
4
+ data.tar.gz: 7079726edaaa3a2c4861f91170a2522b298a0882
5
5
  SHA512:
6
- metadata.gz: 8427c54453c06709181a6ac2f8c7480af92bab90c4a59dd8482bdc6f5d20770acf13204b82cc1facb74259cd39ef16255796ce37e081725f78b640f8fd6ae042
7
- data.tar.gz: 406eb9aa3eb061de7b520f6e6c1ddbced71fa279aec59c7c2e18312f9a6a9c2457cb3e94cbbafb6d3cd65dfd965fbb53efa779f2f647b1e8bcce675fc05447d5
6
+ metadata.gz: 27e226858c03ed4213db6d169386886f6492aec85c8ca60d9d9ad70d0148391af909741443359ef3481ba816e9cc36c4a1c3dae21030e3e68126fe022b839fe1
7
+ data.tar.gz: dd254bd2ede34af0ab21f9635c3e4c92b4e1ac32be27423736f401641df720bad14484e3a4dd4857d20a4a369e23da3876897a77568cbab3ad0e5743e6d8452a
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ deploy:
5
+ provider: rubygems
6
+ api_key:
7
+ secure: VCPQ9R1RYDhW68CT/P84RvNFbMzI3PsU8K0dFThhr5D2QcpRZTZTW6OeQIpToMdpvFny6Dpu7xa9I+wNFEuMYGIXXlLNirPkhNpu8iBCgarP1QoW173sNNhT8gCjhJrY2QmWFMjH//smMSZHfoJ0cO06/7246/9qK9171C8Lo0E=
8
+ gem: engineering
9
+ on:
10
+ tags: true
11
+ repo: bfoz/engineering
data/Gemfile CHANGED
@@ -1,7 +1,11 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in engineering.gemspec
4
3
  gemspec
5
4
 
6
5
  gem 'model', github: 'bfoz/model'
7
- gem 'units', github: 'bfoz/units-ruby
6
+ gem 'sketch', github: 'bfoz/sketch'
7
+ gem 'units', github: 'bfoz/units-ruby'
8
+
9
+ group :test do
10
+ gem 'rake'
11
+ end
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rake/testtask'
3
3
 
4
+ task :default => :test
5
+
4
6
  Rake::TestTask.new do |t|
5
7
  t.libs.push "lib"
6
8
  t.test_files = FileList['test/**/*.rb']
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "engineering"
6
- s.version = '0.2'
6
+ s.version = '0.3'
7
7
  s.authors = ["Brandon Fosdick"]
8
8
  s.email = ["bfoz@bfoz.net"]
9
9
  s.homepage = "http://github.com/bfoz/engineering"
@@ -17,9 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_dependency 'dxf', '~> 0.2'
21
- s.add_dependency 'geometry', '~> 6.1'
22
- s.add_dependency 'model', '~> 0.1'
23
- s.add_dependency 'sketch', '~> 0.2'
24
- s.add_dependency 'units', '~> 2.2'
20
+ s.add_dependency 'dxf', '~> 0.3'
21
+ s.add_dependency 'geometry', '~> 6.4'
22
+ s.add_dependency 'model', '~> 0.2'
23
+ s.add_dependency 'sketch', '~> 0.4'
24
+ s.add_dependency 'units', '~> 2.4'
25
+
26
+ s.required_ruby_version = '>= 2.0'
25
27
  end
@@ -0,0 +1,189 @@
1
+ require 'model/extrusion'
2
+
3
+ require_relative 'sketch'
4
+
5
+ module Engineering
6
+ module Builder
7
+ # Build an {Extrusion} subclass
8
+ class Extrusion
9
+ include ::Sketch::DSL
10
+
11
+ # Convenience method for creating a new builder and evaluating a block
12
+ def self.build(&block)
13
+ self.new.build(&block)
14
+ end
15
+
16
+ def initialize
17
+ @attribute_defaults = {}
18
+ end
19
+
20
+ # Evaluate a block in the context of an {Extrusion} and a {Skecth}
21
+ # Use the trick found here http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
22
+ # to allow the DSL block to call methods in the enclosing *lexical* scope
23
+ def build(&block)
24
+ @klass = Class.new(::Model::Extrusion)
25
+ @sketch_klass = Class.new(::Sketch)
26
+
27
+ @klass.singleton_class.send :attr_accessor, :sketch
28
+ @klass.instance_variable_set('@sketch', @sketch_klass)
29
+
30
+ if block_given?
31
+ # So that #push has something to append to
32
+ @sketch_klass.singleton_class.send :attr_accessor, :elements
33
+ @sketch_klass.instance_variable_set('@elements', [])
34
+
35
+ @self_before_instance_eval = block.binding.eval('self')
36
+ self.instance_eval(&block)
37
+
38
+ # Instance variable values for read-only attributes need special handling
39
+ setter_defaults = @attribute_defaults.select {|k,v| @sketch_klass.respond_to? k.to_s + '=' } # Find the ones that can be set normally
40
+ instance_variable_defaults = @attribute_defaults.reject {|k,v| @sketch_klass.respond_to? k.to_s + '=' } # These must be set directly
41
+
42
+ # The new Sketch subclass needs an initializer too
43
+ @sketch_klass.send :define_method, :initialize do |*args, &block|
44
+ # Directly set the read-only instance variables
45
+ instance_variable_defaults.each {|k,v| instance_variable_set('@' + k.to_s, v) }
46
+
47
+ super(*args, &block)
48
+
49
+ # Push the default geometry
50
+ self.class.instance_variable_get(:@elements).each do |a|
51
+ if a.is_a? Array
52
+ push a.first.new(*a.last)
53
+ else
54
+ push a
55
+ end
56
+ end
57
+ end
58
+
59
+ @klass.send :define_method, :initialize do |options={}, &block|
60
+ raise ArgumentError, "Can't initialize with a length when #{self} already has a length attribute" if self.class.length and options.key?(:length)
61
+ raise ArgumentError, "Can't initialize with a Sketch when #{self} already has a Sketch attribute" if self.class.sketch and options.key?(:sketch)
62
+
63
+ # Sketch doesn't support any Transformation options
64
+ sketch_options = options.reject {|k,v| [:angle, :origin, :translate, :x, :y].include? k }
65
+ # More things that Sketch can't handle
66
+ sketch_options.reject! {|k,v| [:length, :sketch].include? k }
67
+
68
+ # Evaluate any blocks in the passed arguments and dupe the options
69
+ # hash as a side effect so that the caller's hash isn't mutated
70
+ options = (options.map {|k,v| { k => (v.respond_to?(:call) ? v.call : v) } }).reduce(:merge) || {}
71
+
72
+ # Create a new instance of the Sketch subclass
73
+ options[:sketch] = self.class.sketch.new(setter_defaults.merge(sketch_options)) if self.class.sketch
74
+ options[:length] = self.class.length if self.class.length
75
+
76
+ super options
77
+ end
78
+ end
79
+
80
+ @klass.singleton_class.send :attr_accessor, :length
81
+ @klass.instance_variable_set('@length', @length)
82
+
83
+ @klass
84
+ end
85
+
86
+ # The second half of the instance_eval delegation trick mentioned at
87
+ # http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
88
+ def method_missing(method, *args, &block)
89
+ if @klass.respond_to? method
90
+ @klass.send method, *args, &block
91
+ elsif @sketch_klass.respond_to? method
92
+ @sketch_klass.send method, *args, &block
93
+ else
94
+ @self_before_instance_eval.send method, *args, &block
95
+ end
96
+ end
97
+
98
+ # @group DSL support methods
99
+
100
+ private
101
+
102
+ # Set the length attribute of the {Extrusion}
103
+ # @param length [Number] the new length
104
+ def length(length=nil)
105
+ @length = length if length
106
+ @length
107
+ end
108
+
109
+ # Create a {Group} with an optional transformation
110
+ def build_group(*args, &block)
111
+ [Builder::Sketch.new.build(::Sketch::Group, &block), args]
112
+ end
113
+
114
+ # Create a {Layout}
115
+ # @param direction [Symbol] The layout direction (either :horizontal or :vertical)
116
+ # @option options [Symbol] alignment :top, :bottom, :left, or :right
117
+ # @option options [Number] spacing The spacing between each element
118
+ def build_layout(direction, alignment, spacing, *args, &block)
119
+ [Builder::Sketch.new.build(::Sketch::Layout, &block), args]
120
+ end
121
+
122
+ # Use the given block to build a {Polyline}
123
+ def build_polyline(**options, &block)
124
+ ::Sketch::Builder::Polyline.new(**options).evaluate(&block)
125
+ end
126
+
127
+ # Build a {Polygon} from a block
128
+ # @return [Polygon]
129
+ def build_polygon(**options, &block)
130
+ ::Sketch::Builder::Polygon.new(**options).evaluate(&block)
131
+ end
132
+
133
+ # Define an attribute with the given name and optional default value (or block)
134
+ # @param name [String] The attribute's name
135
+ # @param value An optional default value
136
+ def define_attribute_reader(name, value=nil, &block)
137
+ name, value = name.flatten if name.is_a?(Hash)
138
+ name = name.to_sym
139
+
140
+ # Class accessor that forwards to the Sketch
141
+ @klass.class_eval "class << self; def #{name}; sketch.#{name}; end; end"
142
+
143
+ # Instance accessor that forwards to the Sketch
144
+ @klass.send :define_method, name.to_sym do
145
+ sketch.send name
146
+ end
147
+
148
+ # Instance accessor on the new Sketch
149
+ @sketch_klass.send :attr_reader, name
150
+
151
+ if value || block_given?
152
+ # Class accessor on the new Sketch subclass
153
+ @sketch_klass.singleton_class.send :attr_reader, name
154
+
155
+ # Set the ivar on the Sketch subclass
156
+ @sketch_klass.instance_variable_set('@' + name.to_s, value || instance_eval(&block))
157
+ @attribute_defaults[name] = value || block
158
+ end
159
+ end
160
+
161
+ # Define an attribute with the given name
162
+ # @param name [String,Symbol] the name of the attribute
163
+ def define_attribute_writer(name)
164
+ method_name = name.to_s + '='
165
+
166
+ # Class accessor that forwards to the Sketch
167
+ @klass.class_eval "class << self; def #{method_name}(value); sketch.#{method_name} value; end; end"
168
+
169
+ # Instance accessor that forwards to the Sketch
170
+ @klass.send :define_method, method_name.to_sym do |value|
171
+ sketch.send method_name, value
172
+ end
173
+
174
+ # Instance accessor on the new Sketch
175
+ @sketch_klass.send :attr_writer, name.to_sym
176
+ end
177
+
178
+ # Append a new object (with optional transformation) to the {Sketch}
179
+ def push(element, *args)
180
+ if element.is_a? Class
181
+ @sketch_klass.instance_variable_get(:@elements).push [element, args]
182
+ else
183
+ @sketch_klass.instance_variable_get(:@elements).push element
184
+ end
185
+ end
186
+ # @endgroup
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,120 @@
1
+ require 'model'
2
+
3
+ require_relative '../model/dsl'
4
+ require_relative 'extrusion'
5
+
6
+ module Engineering
7
+ module Builder
8
+ # Build a {Model} subclass
9
+ class Model
10
+ include ::Model::DSL
11
+
12
+ # Convenience method for creating a new builder and evaluating a block
13
+ def self.build(&block)
14
+ self.new.build(&block)
15
+ end
16
+
17
+ def initialize
18
+ @attribute_defaults = {}
19
+ end
20
+
21
+ # Evaluate a block and return a new {Model} subclass
22
+ # Use the trick found here http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
23
+ # to allow the DSL block to call methods in the enclosing *lexical* scope
24
+ def build(super_class=::Model, &block)
25
+ @klass = Class.new(super_class)
26
+ if block_given?
27
+ @klass.singleton_class.send :attr_reader, :elements
28
+ @klass.instance_variable_set(:@elements, [])
29
+
30
+ @self_before_instance_eval = block.binding.eval('self')
31
+ self.instance_eval(&block)
32
+
33
+ # Instance variable values for read-only attributes need special handling
34
+ options = @attribute_defaults.select {|k,v| @klass.respond_to? k.to_s + '=' } # Find the ones that can be set normally
35
+ instance_variable_defaults = @attribute_defaults.reject {|k,v| @klass.respond_to? k.to_s + '=' } # These must be set directly
36
+
37
+ @klass.send :define_method, :initialize do |*args, &block|
38
+ # Directly set the read-only instance variables
39
+ instance_variable_defaults.each {|k,v| instance_variable_set('@' + k.to_s, v) }
40
+
41
+ # Handle the others normally, while evaluating any blocks
42
+ super(*(options.map {|k,v| { k => (v.respond_to?(:call) ? v.call : v) } }), *args, &block)
43
+
44
+ # Push the default geometry
45
+ self.class.instance_variable_get(:@elements).each do |a|
46
+ if a.is_a? Array
47
+ push a.first.new(*a.last)
48
+ else
49
+ push a
50
+ end
51
+ end
52
+ end
53
+ end
54
+ @klass
55
+ end
56
+
57
+ # The second half of the instance_eval delegation trick mentioned at
58
+ # http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
59
+ def method_missing(method, *args, &block)
60
+ if @klass.respond_to? method
61
+ @klass.send method, *args, &block
62
+ else
63
+ @self_before_instance_eval.send method, *args, &block
64
+ end
65
+ end
66
+
67
+ # @group DSL support methods
68
+ private
69
+
70
+ # Build a new {Extrusion} subclass
71
+ # @param length [Number] the length of the extrusion
72
+ # @param sketch [Sketch] a {Sketch} subclass to extrude (or nil)
73
+ # @param parent [Object] a parent context to use while building
74
+ # @param options [Hash] anything that needs to be passed to the new {Extrusion} instance
75
+ def build_extrusion(length, sketch, parent, options={}, &block)
76
+ [Builder::Extrusion.build(&block), [options.merge(length:length)]]
77
+ end
78
+
79
+ # Build a new {Group} subclass
80
+ def build_group(*args, &block)
81
+ [self.class.new.build(::Model::Group, &block), args]
82
+ end
83
+
84
+ # Define an attribute with the given name and optional default value (or block)
85
+ # @param name [String] The attribute's name
86
+ # @param value An optional default value
87
+ def define_attribute_reader(name, value=nil, &block)
88
+ name, value = name.flatten if name.is_a?(Hash)
89
+ ivar_name = '@' + name.to_s
90
+ name = name.to_sym
91
+
92
+ # Class accessor
93
+ @klass.singleton_class.send :attr_reader, name
94
+
95
+ @klass.send :attr_reader, name.to_sym # Instance accessor
96
+ if value || block_given?
97
+ @klass.instance_variable_set(ivar_name, value || instance_eval(&block))
98
+ @attribute_defaults[name] = value || block
99
+ end
100
+ end
101
+
102
+ # Define an attribute with the given name
103
+ # @param name [String,Symbol] the name of the attribute
104
+ def define_attribute_writer(name)
105
+ @klass.send :attr_writer, name.to_sym # Instance accessor
106
+ end
107
+
108
+ def push(element, *args)
109
+ if element.is_a? Class
110
+ @klass.instance_variable_get(:@elements).push [element, args]
111
+ elsif element.is_a?(Array) and element.first.is_a?(Class)
112
+ @klass.instance_variable_get(:@elements).push element
113
+ else
114
+ raise ArgumentError, "Can't push instances while building a class"
115
+ end
116
+ end
117
+ # @endgroup
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,119 @@
1
+ require 'sketch'
2
+
3
+ module Engineering
4
+ module Builder
5
+ class Sketch
6
+ include ::Sketch::DSL
7
+
8
+ # Convenience method for creating a new builder and evaluating a block
9
+ def self.build(&block)
10
+ self.new.build(&block)
11
+ end
12
+
13
+ def initialize
14
+ @attribute_defaults = {}
15
+ end
16
+
17
+ # Evaluate a block and return a new {Model} subclass
18
+ # Use the trick found here http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
19
+ # to allow the DSL block to call methods in the enclosing *lexical* scope
20
+ def build(super_class=::Sketch, &block)
21
+ @klass = Class.new(super_class)
22
+ if block_given?
23
+ @klass.singleton_class.send :attr_accessor, :elements
24
+ @klass.instance_variable_set('@elements', [])
25
+
26
+ @self_before_instance_eval = block.binding.eval('self')
27
+ self.instance_eval(&block)
28
+
29
+ # Instance variable values for read-only attributes need special handling
30
+ setter_defaults = @attribute_defaults.select {|k,v| @klass.respond_to? k.to_s + '=' } # Find the ones that can be set normally
31
+ instance_variable_defaults = @attribute_defaults.reject {|k,v| @klass.respond_to? k.to_s + '=' } # These must be set directly
32
+
33
+ @klass.send :define_method, :initialize do |*args, &block|
34
+ # Directly set the read-only instance variables
35
+ instance_variable_defaults.each {|k,v| instance_variable_set('@' + k.to_s, v) }
36
+
37
+ super(setter_defaults, *args, &block)
38
+
39
+ # Push the default geometry
40
+ self.class.instance_variable_get(:@elements).each do |a|
41
+ if a.is_a? Array
42
+ push a.first.new(*a.last)
43
+ else
44
+ push a
45
+ end
46
+ end
47
+ end
48
+ end
49
+ @klass
50
+ end
51
+
52
+ # The second half of the instance_eval delegation trick mentioned at
53
+ # http://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
54
+ def method_missing(method, *args, &block)
55
+ @self_before_instance_eval.send method, *args, &block
56
+ end
57
+
58
+ # @group DSL support methods
59
+ private
60
+
61
+ # Create a {Group} with an optional transformation
62
+ def build_group(*args, &block)
63
+ [self.class.new.build(::Sketch::Group, &block), args]
64
+ end
65
+
66
+ # Create a {Layout}
67
+ # @param direction [Symbol] The layout direction (either :horizontal or :vertical)
68
+ # @option options [Symbol] alignment :top, :bottom, :left, or :right
69
+ # @option options [Number] spacing The spacing between each element
70
+ def build_layout(direction, alignment, spacing, *args, &block)
71
+ [Builder::Sketch.new.build(::Sketch::Layout, &block), args]
72
+ end
73
+
74
+ # Use the given block to build a {Polyline}
75
+ def build_polyline(**options, &block)
76
+ ::Sketch::Builder::Polyline.new(**options).evaluate(&block)
77
+ end
78
+
79
+ # Build a {Polygon} from a block
80
+ # @return [Polygon]
81
+ def build_polygon(**options, &block)
82
+ ::Sketch::Builder::Polygon.new(**options).evaluate(&block)
83
+ end
84
+
85
+ # Define an attribute with the given name and optional default value (or block)
86
+ # @param name [String] The attribute's name
87
+ # @param value An optional default value
88
+ def define_attribute_reader(name, value=nil, &block)
89
+ name, value = name.flatten if name.is_a?(Hash)
90
+ ivar_name = '@' + name.to_s
91
+ name = name.to_sym
92
+
93
+ # Class accessor
94
+ @klass.class_eval "class << self; attr_reader :#{name}; end"
95
+
96
+ @klass.send :attr_reader, name.to_sym # Instance accessor
97
+ if value || block_given?
98
+ @klass.instance_variable_set(ivar_name, value || instance_eval(&block))
99
+ @attribute_defaults[name] = value || block
100
+ end
101
+ end
102
+
103
+ # Define an attribute with the given name
104
+ # @param name [String,Symbol] the name of the attribute
105
+ def define_attribute_writer(name)
106
+ @klass.send :attr_writer, name.to_sym # Instance accessor
107
+ end
108
+
109
+ def push(element, *args)
110
+ if element.is_a? Class
111
+ @klass.instance_variable_get(:@elements).push [element, args]
112
+ else
113
+ @klass.instance_variable_get(:@elements).push element
114
+ end
115
+ end
116
+ # @endgroup
117
+ end
118
+ end
119
+ end