gorillib 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@ require 'pathname'
2
2
  require 'date'
3
3
  require 'set'
4
4
  require 'gorillib/factories'
5
+ require 'gorillib/type/ip_address'
5
6
 
6
7
  class ::Long < ::Integer ; end
7
8
  class ::Double < ::Float ; end
@@ -0,0 +1,153 @@
1
+ module IpAddresslike
2
+ ONES = 0xFFFFFFFF
3
+
4
+ # Masks off all but the `bitness` most-significant-bits
5
+ #
6
+ # @example /24 keeps only the first three quads
7
+ # IpAddress.new('1.2.3.4').bitness_min(24) # '1.2.3.0'
8
+ #
9
+ def bitness_min(bitness)
10
+ raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
11
+ lsbs = 32 - bitness
12
+ (packed >> lsbs) << lsbs
13
+ end
14
+
15
+ # Masks off all but the `bitness` most-significant-bits, filling with ones
16
+ #
17
+ # @example /24 fills the last quad
18
+ # IpAddress.new('1.2.3.4').bitness_min(24) # '1.2.3.255'
19
+ #
20
+ def bitness_max(bitness)
21
+ raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
22
+ packed | (ONES >> bitness)
23
+ end
24
+
25
+ def hex_str
26
+ packed.to_s(16)
27
+ end
28
+
29
+ def to_s
30
+ dotted
31
+ end
32
+
33
+ end
34
+
35
+ class ::IpAddress < ::String
36
+ include IpAddresslike
37
+
38
+ def dotted
39
+ self
40
+ end
41
+
42
+ def to_i
43
+ packed
44
+ end
45
+
46
+ # @returns [Integer] the 32-bit integer for this IP address
47
+ def packed
48
+ ip_a, ip_b, ip_c, ip_d = quads
49
+ ((ip_a << 24) + (ip_b << 16) + (ip_c << 8) + (ip_d))
50
+ end
51
+
52
+ def quads
53
+ self.split(".", 4).map{|qq| Integer(qq) }
54
+ end
55
+
56
+ # === class methods ===
57
+
58
+ def self.from_packed(pi)
59
+ str = [ (pi >> 24) & 0xFF, (pi >> 16) & 0xFF, (pi >> 8) & 0xFF, (pi) & 0xFF ].join(".")
60
+ new(str)
61
+ end
62
+
63
+ def self.from_dotted(str)
64
+ new(str)
65
+ end
66
+ end
67
+
68
+ # Stores an IP address in numeric form.
69
+ #
70
+ # IpNumeric instances are immutable, and memoize most of their methods.
71
+ class ::IpNumeric
72
+ include IpAddresslike
73
+ include Comparable
74
+
75
+ def receive(val)
76
+ new(val)
77
+ end
78
+
79
+ def initialize(addr)
80
+ @packed = addr.to_int
81
+ end
82
+
83
+ def to_i ; packed ; end
84
+ def to_int ; packed ; end
85
+ def ==(other) ; packed == other.to_int ; end
86
+ def <=>(other) ; packed <=> other.to_int ; end
87
+ def +(int) ; self.class.new(to_int + int) ; end
88
+
89
+
90
+ def packed ; @packed ; end
91
+
92
+ def dotted
93
+ @dotted ||= quads.join('.').freeze
94
+ end
95
+
96
+ def quads
97
+ @quads ||= [ (@packed >> 24) & 0xFF, (@packed >> 16) & 0xFF, (@packed >> 8) & 0xFF, (@packed) & 0xFF ].freeze
98
+ end
99
+
100
+ # === class methods ===
101
+
102
+ def self.from_packed(pi)
103
+ new(pi)
104
+ end
105
+
106
+ def self.from_dotted(dotted)
107
+ ip_a, ip_b, ip_c, ip_d = quads = dotted.split(".", 4).map(&:to_i)
108
+ obj = new((ip_a << 24) + (ip_b << 16) + (ip_c << 8) + (ip_d))
109
+ obj.instance_variable_set('@dotted', dotted.freeze)
110
+ obj.instance_variable_set('@quads', quads.freeze)
111
+ obj
112
+ end
113
+ end
114
+
115
+ class ::IpRange < Range
116
+
117
+ def initialize(min_or_range, max=nil, exclusive=false)
118
+ if max.nil?
119
+ min = min_or_range.min
120
+ max = min_or_range.max
121
+ exclusive = min_or_range.exclude_end?
122
+ else
123
+ min = min_or_range
124
+ end
125
+ raise ArgumentError, "Only inclusive #{self.class.name}s are implemented" if exclusive
126
+ super( IpNumeric.new(min), IpNumeric.new(max), false )
127
+ end
128
+
129
+ def bitness_blocks(bitness)
130
+ raise ArgumentError, "IP addresses have only 32 bits (got #{bitness.inspect})" unless (0..32).include?(bitness)
131
+ return [] if min.nil?
132
+ lsbs = 32 - bitness
133
+ middle_min = min.bitness_max(bitness) + 1
134
+ return [[min, max]] if max < middle_min
135
+ middle_max = max.bitness_min(bitness)
136
+ blks = []
137
+ stride = 1 << lsbs
138
+ #
139
+ blks << [min, IpNumeric.new(middle_min-1)]
140
+ (middle_min ... middle_max).step(stride){|beg| blks << [IpNumeric.new(beg), IpNumeric.new(beg+stride-1)] }
141
+ blks << [IpNumeric.new(middle_max), max]
142
+ blks
143
+ end
144
+
145
+ CIDR_RE = %r{\A(\d+\.\d+\.\d+\.\d+)/([0-3]\d)\z}
146
+
147
+ def self.from_cidr(cidr_str)
148
+ cidr_str =~ CIDR_RE or raise ArgumentError, "CIDR string should look like an ip address and bitness, eg 1.2.3.4/24 (got #{cidr_str})"
149
+ bitness = $2.to_i
150
+ ip_address = IpNumeric.from_dotted($1)
151
+ new( ip_address.bitness_min(bitness), ip_address.bitness_max(bitness) )
152
+ end
153
+ end
data/notes/HOWTO.md ADDED
@@ -0,0 +1,22 @@
1
+ # Walkthrough
2
+
3
+ **note (2012-06): you must use the dev branch, `version_1` of Gorillib** -- https://github.com/infochimps-labs/gorillib/tree/version_1/
4
+
5
+ 1. Here's a set of examples of Gorillib records in action.
6
+ * [examples of `Gorillib::Model`](https://github.com/infochimps-labs/gorillib/tree/version_1/examples/model)
7
+ * see example gorillib records in the /examples directories
8
+
9
+ __________________________________________________________________________
10
+
11
+ 2. Walkthrough dataflow for a new customer:
12
+ * models to represent records in a dataflow (gorillib/examples)
13
+ * describe the macro dataflow (wukong/examples)
14
+ * simulate the dataflow at commandline
15
+ * project the dataflow using Flume
16
+ * export the dataflow using graphviz
17
+
18
+ https://github.com/infochimps-labs/wukong/wiki
19
+ https://github.com/infochimps-labs/gorillib/wiki
20
+ Plus source files (or you can instead see the Yard Docs)
21
+
22
+ 3. flume event model shim
data/notes/bucket.md ADDED
@@ -0,0 +1,155 @@
1
+ # gorillib/bucket (WIP -- NOT READY YET) -- freeform storage with definable access
2
+
3
+ * A `Go::Bucket` fulfills the contract of `Go::Model`
4
+
5
+ ## Arbitrary read/writes with `[]/[]=`, defined access with `.foo/.foo=`
6
+
7
+ Overriding `method_missing` is uncouth. It screws up your stack traces, muddies your interface and leads to unassertive code:
8
+
9
+ ```ruby
10
+ Settings.defcon = 3
11
+ # ... elsewhere ...
12
+ if Settings.def_con.to_i <= 1
13
+ WOPR.launch_nukes!
14
+ end
15
+ ```
16
+
17
+ Here, mispelling `def_con` as `defcon` (along with being wussy about the attribute's type and negligent about prescribing a default value) leads to the destruction of humanity.
18
+
19
+ Nonetheless, for several reasons (prudent laziness, handling configuration of an external component, etc) it's important to handle arbitrary attributes uniformly.
20
+
21
+ So the rule is that you can get or set anything you like using `[]` and `[]=` respectively, no need to define it first:
22
+
23
+ Settings[:whatever] = 3
24
+ Settings[:whatever] #=> 3
25
+
26
+ You don't get any magic, and the ugly accessor leaves you in no doubt that you're being lazy (ain't judging, just sayin'). When you define a setting you get accessors for that attribute and all associated magic:
27
+
28
+ Settings.option :defcon, Integer, :doc => 'Current NORAD defense condition', :default => 5, :validates => { :in => 1..5 }
29
+ Settings.defcon #=> 5
30
+ Settings.defcon = 0
31
+ Settings.validate! # raises a validation exception
32
+
33
+ Defined fields' magic works whichever form of access you use -- here, type-converting the value on assignment:
34
+
35
+ Settings[:defcon] = '5' #=> 5
36
+ Settings.defcon = '5' #=> 5
37
+ Settings.receive_defcon('5') #=> 5
38
+
39
+ ## Keys
40
+
41
+ Keys be lower-cased identifiers: they should match `/\A([a-z][a-z0-9\_]*)\z/`.
42
+
43
+ ## Deep Hash
44
+
45
+ Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}
46
+
47
+ ### Dot addressing
48
+
49
+ Can retrieve keys as 'x.y.z' meaning 'foo[x][y][z]'.
50
+
51
+ * On a `get`, will
52
+ - return the value, if `foo[:x][:y][:z]` exists
53
+ - return nil, if either `foo[:x]` or `foo[:x][:y]` is *unset*. It will not create `foo[:x]` or `foo[:x][:y]`.
54
+ - raise an error if either `foo[:x]` or `foo[:x][:y]` is set to a value that does not respond to `[]`
55
+
56
+ * On a `set`, will
57
+ - raise an error if either `foo[:x]` or `foo[:x][:y]` is set to a value that doesn't respond to `[]`
58
+ - set and return the value; any intermediate buckets (`foo[:x]` and `foo[:x][:y]`) will be created if they don't exist.
59
+
60
+
61
+ __________________________________________________________________________
62
+
63
+
64
+ ## Examples
65
+
66
+ ### how hash-like is this?
67
+
68
+ * obj.deep_get(:foo, :bar, :baz) #
69
+ * obj.deep_set(:foo, :bar, :baz, val) # sets foo bar baz.
70
+ - where property has a type, it uses that type
71
+ - otherwise it uses Mash, and calls [] on it
72
+
73
+ * TD votes NO on magic recursive hash behavior
74
+
75
+
76
+ {
77
+ :buck1 => {
78
+ :buck2 => { :k3 => 33, :ehsh => {}, :arr => [11, 12, 13] },
79
+ :cars => [{ :model => 'ford', :cylinders => 8 }, { :model => 'buick', :cylinders => 6 }],
80
+ }
81
+ :buck3 => {
82
+ :buck4 => { :k4 => 44 }
83
+ :k5 => nil,
84
+ }
85
+ :k6 => 69
86
+ }
87
+
88
+ * `obj[:buck3]` # b{ :buck4=>b{ :k4=>44 },:k5=>nil } -- it's a bucket
89
+ * `obj[:buck5] = Hash.new`
90
+ * `obj[:buck5].class` # Bucket -- it's converted to bucket
91
+ * `obj[:xxx]` # nil -- it's not there
92
+ * `obj[:xxx][:yyy][:zzz]` # fails -- it doesn't try to index into the nil `obj[:xxx]` cell.
93
+ * `obj[:xxx]` # nil -- it didn't create the `obj[:xxx]` object when we tried to read on the previous line.
94
+
95
+ c.options_for 'wheels', 'interior.fabric', 'interior.carpeting'
96
+
97
+ c[:wheels] # c{ }
98
+ c[:wheels][:whitewall] # nil
99
+ c[:wheels][:whitewall] = true # true
100
+
101
+ c.interior.fabric.color
102
+
103
+
104
+ * `obj[:k6][:bomb]` # raises ArgumentError; `obj[:k6]` is not a bucket.
105
+
106
+ * `obj[:'hello-there'] # undefined; `'hello-there'` is not a valid identifier.
107
+
108
+ * `obj[:buck3][:buck4][:k5] = 55`
109
+ `obj[:buck3]` # b{ :buck4=>b{ :k4=>44 },:k5 => 55}
110
+ * `obj[:f][:g][:h] = 7`
111
+ `obj[:f]` # b{ :g => b{ :h => 7 } }
112
+
113
+ * `obj[:foo][:bar][:baz] ||= 1` -- **hard** ?I think?
114
+
115
+ * `obj[:foo]` -- nil
116
+ * `obj[:foo][:bar]` -- raise
117
+ * `obj[:foo][:bar] = 3` --
118
+ - now obj[:foo][:bar] is 3
119
+ - and obj[:foo] is a ?dsl_object?? but
120
+
121
+ `obj[:foo][:bar]`
122
+
123
+ Suppose `obj[:foo]` is set to a
124
+
125
+ Seems clear these should do the right thing:
126
+
127
+ * `obj.merge`
128
+ * `obj.reverse_merge`
129
+ * `obj.keys`
130
+ * `obj.values`
131
+ * ... and a few more
132
+
133
+ Also:
134
+
135
+ * `obj.to_a`?
136
+ * `obj.each`?
137
+ * other crazy Enumerable properties?
138
+
139
+ ### TODO
140
+
141
+ * figure out the method structure for
142
+ - read/write/unset of attributes when Hash vs Accessors vs Instance Variables
143
+ - reader/writer: raw vs. hooks, dirty, etc.
144
+
145
+ ## Configliere Settings
146
+
147
+
148
+ Configliere lets you define arbitrary attributes of a param, notably:
149
+
150
+ [:description] Documentation for the param, used in the --help message
151
+ [:default] Sets a default value (applied immediately)
152
+ [:env_var] Environment variable to adopt (applied immediately, and after +:default+)
153
+ [:type] Converts param's value to the given type, just before the finally block is called
154
+ [:finally] Block of code to postprocess settings or handle complex configuration.
155
+ [:required] Raises an error if, at the end of calling resolve!, the param's value is nil.
data/notes/builder.md ADDED
@@ -0,0 +1,170 @@
1
+ # gorillib/builder -- construct elegant ruby Domain-Specific Languages (DSLs).
2
+
3
+ `Gorillib::Builder` provides foundation models for elegant ruby DSLs (Domain-Specific Languages). A builder block's relaxed ruby syntax enables highly-readable specification of complex behavior:
4
+
5
+ ```ruby
6
+ garage do
7
+ car :ford_tudor do
8
+ manufacturer 'Ford Motor Company'
9
+ car_model 'Tudor'
10
+ year 1939
11
+ doors 2
12
+ style :sedan
13
+ engine do
14
+ volume 350
15
+ cylinders 8
16
+ end
17
+ end
18
+
19
+ car :wildcat do
20
+ manufacturer 'Buick'
21
+ car_model 'Wildcat'
22
+ year 1968
23
+ doors 2
24
+ style :convertible
25
+ engine do
26
+ volume 455
27
+ cylinders 8
28
+ end
29
+ end
30
+ end
31
+ ```
32
+
33
+ ### Defining a builder
34
+
35
+ To make a class be a builder, simply `include Gorillib::Builder`:
36
+
37
+
38
+ ```ruby
39
+ class Garage
40
+ include Gorillib::Builder
41
+ collection :car
42
+ end
43
+
44
+ class Car
45
+ include Gorillib::Builder
46
+ field :name, String
47
+ field :manufacturer, String
48
+ field :car_model, String
49
+ field :year, Integer
50
+ field :doors, Integer
51
+ field :style, Symbol, :validates => { :inclusion_in => [:sedan, :coupe, :convertible] }
52
+ member :engine
53
+ belongs_to :garage
54
+ end
55
+
56
+ class Engine
57
+ include Gorillib::Builder
58
+ field :volume, Integer
59
+ field :cylinders, Integer
60
+ belongs_to :car
61
+ end
62
+ ```
63
+
64
+ ### getset accessors
65
+
66
+ Fields of a Builder class create a single "getset" accessor (and not the familiar 'foo/foo=' pair):
67
+
68
+ * `car_model.foo` -- returns value of foo
69
+ * `car_model.foo(val)` -- sets foo to `val`, which can be any value, even `nil`.
70
+
71
+ ### member fields
72
+
73
+ A builder class can have `member` fields; the type of a member field should be a builder class itself
74
+
75
+ With no arguments, the accessor returns the value of engine attribute, creating if necessary:
76
+
77
+ ```ruby
78
+ car.engine #=> #<Engine volume=~ cylinders=~>
79
+ ```
80
+
81
+ If you pass in a hash of values, the engine is created if necessary and then asked to `receive!` the hash.
82
+
83
+ ```ruby
84
+ car.engine #=> #<Engine volume=~ cylinders=~>
85
+ car.engine(:cylinders => 8) #=> #<Engine volume=~ cylinders=8>
86
+ car.engine.cylinders #=> 8
87
+ ```
88
+
89
+ If you provide a no-args block, it is `instance_eval`ed in the context of the member object:
90
+
91
+ ```ruby
92
+ car :ford_tudor do
93
+ engine(:cylinders => 8) do
94
+ self #=> #<Engine volume=~ cylinders=8>
95
+ volume 455
96
+ cylinders self.cylinders - 2
97
+ self #=> #<Engine volume=455 cylinders=6>
98
+ end
99
+ engine #=> #<Engine volume=455 cylinders=6>
100
+ end
101
+ ```
102
+
103
+ Some people disapprove of `instance_eval`, as they consider it unseemly to mess around with `self`. If you instead provide a one-arg block, the member object is passed in:
104
+
105
+ ```ruby
106
+ car :ford_tudor do |c|
107
+ c.engine(:cylinders => 8) do |eng|
108
+ self #=> #<Car ...>
109
+ eng.volume 455
110
+ eng.cylinders eng.cylinders - 2
111
+ eng #=> #<Engine volume=455 cylinders=6>
112
+ end
113
+ c.engine #=> #<Engine volume=455 cylinders=6>
114
+ end
115
+ ```
116
+
117
+ ### collections
118
+
119
+ A builder class can also have `collection` fields, to contain named builder objects.
120
+
121
+ ```ruby
122
+ garage do
123
+ car(:ford_tudor) #=> #<Car name="ford tudor" ...>
124
+ cars #=> { :ford_tudor => #<Car ...>, :wildcat => #<Car ...> }
125
+ end
126
+ ```
127
+
128
+ The collected items must respond to `id` (FIXME:). The singular accessor accepts arguments just like a `member` accessor:
129
+
130
+ ```ruby
131
+ garage do
132
+ car(:ford_tudor, :year => 1939)
133
+ #=> #<Car name=`<:ford_tudor year=1939 doors=~ ...>
134
+ car(:ford_tudor, :year => 1939) do
135
+ doors 2
136
+ style :convertible
137
+ end
138
+ #=> #<Car name="ford tudor" year=1939 doors=2 ...>
139
+ car(:wildcat, :year => 1968) do |c|
140
+ c.doors 2
141
+ c.style :convertible
142
+ end
143
+ #=> #<Car name= year=1939 doors=2 ...>
144
+
145
+
146
+
147
+
148
+ ### Model methods
149
+
150
+ builders have the following:
151
+
152
+ * `Model::Defaults`
153
+ * `Model::Naming`
154
+ * `Model::Conversion`
155
+
156
+
157
+
158
+ ### SubclassRegistry
159
+
160
+ When a subclass happens, decorates class with `saucepot(...)`, equivalent to
161
+
162
+ update_or_create
163
+ registers
164
+
165
+ * At class level, `registry_for(CookingUtensil)` gives
166
+ - `add_cooking_utensil()` and so forth for adding and getting
167
+ - Protected `cooking_utensils` hash class attribute
168
+ * Enlisting a resource class (Kitchen.register(FryingPan) and gives you a magic `frying_pan` factory method with signature `frying_pan(name, *args, &block)`
169
+ - If resource does not exist, calls `FryingPan.new` with full set of params and block. Add it to `cooking_utensils` registry.
170
+ - If resource with that name exists, retrieve it. call `merge()` with the parameters, and `run_in_scope` with the block.