engineering 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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