agrippa 0.0.1

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.
data/lib/agrippa.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "agrippa/utilities"
2
+ require "agrippa/version"
3
+ require "agrippa/methods"
4
+ require "agrippa/delegation"
5
+ require "agrippa/proxy"
6
+ require "agrippa/state"
7
+ require "agrippa/maybe"
8
+ require "agrippa/mutable"
9
+ require "agrippa/mutable_hash"
10
+ Agrippa::Utilities.try_require "agrippa/immutable"
@@ -0,0 +1,28 @@
1
+ module Agrippa
2
+ module AccessorMethods
3
+ def state_reader(*args)
4
+ options = args.last.is_a?(Hash) ? args.pop : {}
5
+ args.flatten.each do |arg|
6
+ __define_state_reader(arg, caller, options)
7
+ end
8
+ self
9
+ end
10
+
11
+ def state_writer(*args)
12
+ options = args.last.is_a?(Hash) ? args.pop : {}
13
+ args.flatten.each do |arg|
14
+ __define_state_writer(arg, caller, options)
15
+ end
16
+ self
17
+ end
18
+
19
+ def state_accessor(*args)
20
+ options = args.last.is_a?(Hash) ? args.pop : {}
21
+ args.flatten.each do |arg|
22
+ __define_state_reader(arg, caller, options)
23
+ __define_state_writer(arg, caller, options)
24
+ end
25
+ self
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ require "agrippa/methods"
2
+
3
+ module Agrippa
4
+ module Delegation
5
+ def self.included(base)
6
+ base.send(:include, Agrippa::Methods)
7
+ base.send(:extend, ClassMethods)
8
+ end
9
+
10
+ def self.interpolate(string, vars)
11
+ output = string.gsub(/[A-Z]{3,}/) do |match|
12
+ vars.fetch(match.downcase.to_sym)
13
+ end
14
+ output.lines.map(&:strip).join("; ")
15
+ end
16
+
17
+ def self.build(base, delegate_caller, methods, definition)
18
+ options = methods.last.is_a?(Hash) ? methods.pop.dup : {}
19
+ target = options.delete(:to)
20
+ target = "self.class" if (target.to_sym == :class)
21
+ raise(ArgumentError) unless target
22
+
23
+ options[:prefix] = target if (options[:prefix] == true)
24
+ options[:suffix] = target if (options[:suffix] == true)
25
+
26
+ file, line = delegate_caller.first.split(':', 2)
27
+ line = line.to_i
28
+
29
+ methods.each do |method|
30
+ name = ::Agrippa::Methods.name(method, options)
31
+ generated = ::Agrippa::Delegation.interpolate(definition,
32
+ name: name, target: target, method: method)
33
+ base.send(:module_eval, generated, file, line)
34
+ end
35
+ self
36
+ end
37
+
38
+ module ClassMethods
39
+ def delegate(*methods)
40
+ ::Agrippa::Delegation.build(self, caller, methods, <<-END)
41
+ def NAME(*args, &block)
42
+ TARGET.METHOD(*args, &block)
43
+ end
44
+ END
45
+ self
46
+ end
47
+
48
+ def class_delegate(*methods)
49
+ ::Agrippa::Delegation.build(self, caller, methods, <<-END)
50
+ def self.NAME(*args, &block)
51
+ TARGET.METHOD(*args, &block)
52
+ end
53
+ END
54
+ self
55
+ end
56
+
57
+ def delegate_command(*methods)
58
+ ::Agrippa::Delegation.build(self, caller, methods, <<-END)
59
+ def NAME(*args, &block)
60
+ store('TARGET', TARGET.METHOD(*args, &block))
61
+ end
62
+ END
63
+ self
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ require "agrippa/utilities"
2
+ require "agrippa/methods"
3
+ require "agrippa/accessor_methods"
4
+
5
+ Agrippa::Utilities.try_require("hamster/hash",
6
+ "Agrippa::Immutable requires the Hamster gem.")
7
+
8
+ module Agrippa
9
+ module Immutable
10
+ def self.included(base)
11
+ base.send(:include, Agrippa::Methods)
12
+ return if base.respond_to?(:state_reader)
13
+ base.send(:extend, ClassMethods)
14
+ base.send(:include, InstanceMethods)
15
+ base.send(:mark_as_commands, :chain, :store)
16
+ base.send(:private, :__symbolize_keys)
17
+ end
18
+
19
+ module ClassMethods
20
+ include AccessorMethods
21
+
22
+ def __define_state_reader(key, original_caller, options)
23
+ name = ::Agrippa::Methods.name(key, options)
24
+ file, line = original_caller.first.split(':', 2)
25
+ line = line.to_i
26
+ spec = "def #{name}; fetch(:#{key}); end"
27
+ module_eval(spec, file, line)
28
+ self
29
+ end
30
+
31
+ def __define_state_writer(key, original_caller, options)
32
+ name = ::Agrippa::Methods.name(key, options)
33
+ name = ::Agrippa::Methods.name(name, prefix: "set") \
34
+ unless (options[:prefix] == false)
35
+ file, line = original_caller.first.split(':', 2)
36
+ line = line.to_i
37
+ spec = "def #{name}(v); store(:#{key}, v); end"
38
+ module_eval(spec, file, line)
39
+ mark_as_command(name)
40
+ self
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ def initialize(state = Hamster::Hash.new, apply_default = true)
46
+ raise(ArgumentError, "#{self.class}#new requires a hash.") \
47
+ unless state.respond_to?(:each_pair)
48
+ if(apply_default and respond_to?(:default_state))
49
+ @state = Hamster::Hash.new(default_state)
50
+ @state = @state.merge(state) unless state.nil?
51
+ elsif(state.is_a?(Hamster::Hash))
52
+ @state = state
53
+ else
54
+ @state = Hamster::Hash.new(__symbolize_keys(state))
55
+ end
56
+ end
57
+
58
+ def chain(updates)
59
+ raise(ArgementError, "#set requires a Hash") \
60
+ unless updates.respond_to?(:each_pair)
61
+ self.class.new(@state.merge(__symbolize_keys(updates)), false)
62
+ end
63
+
64
+ def store(key, value)
65
+ self.class.new(@state.store(key.to_sym, value), false)
66
+ end
67
+
68
+ def fetch(key, default = nil)
69
+ @state.fetch(key.to_sym, default)
70
+ end
71
+
72
+ def __symbolize_keys(input)
73
+ output = input.dup
74
+ input.keys { |k| output[k.to_sym] = output.delete(k) \
75
+ unless k.is_a?(Symbol) }
76
+ output
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,18 @@
1
+ require "agrippa/proxy"
2
+
3
+ module Agrippa
4
+ class Maybe < Proxy
5
+ def method_missing(method, *args, &block)
6
+ return(self) if @value.nil?
7
+ output = begin
8
+ @value.send(method, *args, &block)
9
+ rescue ::NoMethodError
10
+ nil
11
+ end
12
+ return(output) unless proxied_method?(method)
13
+ @value = output
14
+ __chain(@value)
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,52 @@
1
+ module Agrippa
2
+ class MethodRedefinitionError < StandardError
3
+ def initialize(klass, method)
4
+ @klass, @method = klass, method
5
+ end
6
+
7
+ def to_s
8
+ "Redefinition of #{@klass}##{@method}; use the 'redefine' option if you really want to overwrite the existing method."
9
+ end
10
+ end
11
+
12
+ module Methods
13
+ def self.included(base)
14
+ return if base.respond_to?(:command_methods)
15
+ base.send(:extend, ClassMethods)
16
+ base.send(:include, InstanceMethods)
17
+ end
18
+
19
+ def self.name(name, options = {})
20
+ result = name
21
+ suffix = options[:suffix]
22
+ result = "#{result}_#{suffix}".to_sym if suffix
23
+ prefix = options[:prefix]
24
+ result = "#{prefix}_#{result}".to_sym if prefix
25
+ result
26
+ end
27
+
28
+ module ClassMethods
29
+ def mark_as_commands(*args)
30
+ args.flatten.each do |arg|
31
+ command_methods[arg.to_sym] = true
32
+ end
33
+ end
34
+
35
+ alias_method :mark_as_command, :mark_as_commands
36
+
37
+ def command_methods
38
+ @__command_methods ||= {}
39
+ end
40
+
41
+ def command_method?(name)
42
+ command_methods.has_key?(name.to_sym)
43
+ end
44
+ end
45
+
46
+ module InstanceMethods
47
+ def command_methods
48
+ self.class.command_methods
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,62 @@
1
+ require "agrippa/methods"
2
+ require "agrippa/accessor_methods"
3
+
4
+ module Agrippa
5
+ module Mutable
6
+ def self.included(base)
7
+ base.send(:include, Agrippa::Methods)
8
+ return if base.respond_to?(:state_reader)
9
+ base.send(:extend, ClassMethods)
10
+ base.send(:include, InstanceMethods)
11
+ base.send(:mark_as_commands, :chain, :store)
12
+ end
13
+
14
+ module ClassMethods
15
+ include AccessorMethods
16
+
17
+ def __define_state_reader(key, original_caller, options)
18
+ name = ::Agrippa::Methods.name(key, options)
19
+ file, line = original_caller.first.split(':', 2)
20
+ line = line.to_i
21
+ spec = "def #{name}; @#{key}; end"
22
+ module_eval(spec, file, line)
23
+ self
24
+ end
25
+
26
+ def __define_state_writer(key, original_caller, options)
27
+ name = ::Agrippa::Methods.name(key, options)
28
+ name = ::Agrippa::Methods.name(name, prefix: "set") \
29
+ unless (options[:prefix] == false)
30
+ file, line = original_caller.first.split(':', 2)
31
+ line = line.to_i
32
+ spec = "def #{name}(value); @#{key} = value; self; end"
33
+ module_eval(spec, file, line)
34
+ mark_as_command(name)
35
+ self
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+ def initialize(state = nil)
41
+ chain(default_state) if respond_to?(:default_state)
42
+ chain(state) unless state.nil?
43
+ end
44
+
45
+ def chain(state)
46
+ raise(ArgumentError, "#set requires a Hash") \
47
+ unless state.respond_to?(:each_pair)
48
+ state.each_pair { |key, value| store(key, value) }
49
+ self
50
+ end
51
+
52
+ def store(key, value)
53
+ instance_variable_set("@#{key}", value)
54
+ self
55
+ end
56
+
57
+ def fetch(key, default = nil)
58
+ instance_variable_get("@#{key}") || default
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,72 @@
1
+ require "agrippa/methods"
2
+ require "agrippa/accessor_methods"
3
+
4
+ module Agrippa
5
+ module MutableHash
6
+ def self.included(base)
7
+ base.send(:include, Agrippa::Methods)
8
+ return if base.respond_to?(:state_reader)
9
+ base.send(:extend, ClassMethods)
10
+ base.send(:include, InstanceMethods)
11
+ base.send(:mark_as_commands, :chain, :store)
12
+ base.send(:private, :__apply_default_state)
13
+ end
14
+
15
+ module ClassMethods
16
+ include AccessorMethods
17
+
18
+ def __define_state_reader(key, original_caller, options)
19
+ name = ::Agrippa::Methods.name(key, options)
20
+ file, line = original_caller.first.split(':', 2)
21
+ line = line.to_i
22
+ spec = "def #{name}; @state[:#{key}]; end"
23
+ module_eval(spec, file, line)
24
+ self
25
+ end
26
+
27
+ def __define_state_writer(key, original_caller, options)
28
+ name = ::Agrippa::Methods.name(key, options)
29
+ name = ::Agrippa::Methods.name(name, prefix: "set") \
30
+ unless (options[:prefix] == false)
31
+ file, line = original_caller.first.split(':', 2)
32
+ line = line.to_i
33
+ spec = "def #{name}(v); @state[:#{key}] = v; self; end"
34
+ module_eval(spec, file, line)
35
+ mark_as_command(name)
36
+ self
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ def initialize(state = nil)
42
+ raise(ArgumentError, "#{self.class}#new requires a hash.") \
43
+ unless (state.nil? or state.respond_to?(:each_pair))
44
+ __apply_default_state
45
+ chain(state) if (state.respond_to?(:each_pair))
46
+ end
47
+
48
+ def chain(updates)
49
+ raise(ArgumentError, "#set requires a Hash") \
50
+ unless updates.respond_to?(:each_pair)
51
+ updates.each_pair { |key, value| store(key, value) }
52
+ self
53
+ end
54
+
55
+ def store(key, value)
56
+ @state.store(key.to_sym, value)
57
+ self
58
+ end
59
+
60
+ def fetch(key, default = nil)
61
+ @state.fetch(key.to_sym, default)
62
+ end
63
+
64
+ def __apply_default_state
65
+ return(self) unless @state.nil?
66
+ @state = default_state if respond_to?(:default_state)
67
+ @state ||= {}
68
+ self
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,77 @@
1
+ require "agrippa/methods"
2
+
3
+ module Agrippa
4
+ class Proxy < BasicObject
5
+ include Methods
6
+
7
+ instance_methods.each do |method|
8
+ next if (method =~ /(^__|^nil\?$|^send$|^object_id$)/)
9
+ undef_method(method)
10
+ end
11
+
12
+ attr_reader :proxied_methods
13
+
14
+ def initialize(value, *methods)
15
+ @value, @proxied_methods = value, {}
16
+ __build_method_lookup_table(value, methods.flatten)
17
+ end
18
+
19
+ def _value
20
+ @value
21
+ end
22
+
23
+ def _
24
+ @value
25
+ end
26
+
27
+ def _deep_value
28
+ return(@value) unless @value.respond_to?(:_value)
29
+ @value._value
30
+ end
31
+
32
+ def respond_to?(method, include_private = false)
33
+ return(true) if (method == :_value)
34
+ return(true) if (method == :proxied_methods)
35
+ return(true) if (method == :proxied_method?)
36
+ @value.respond_to?(method, include_private)
37
+ end
38
+
39
+ def is_a?(klass)
40
+ @value.is_a?(klass) || (klass == ::Agrippa::Proxy)
41
+ end
42
+
43
+ def proxied_method?(method)
44
+ @proxied_methods.empty? \
45
+ or @proxied_methods.has_key?(method.to_sym)
46
+ end
47
+
48
+ def method_missing(method, *args, &block)
49
+ ::Kernel.raise(::NoMethodError,
50
+ "Implement method_missing in a subclass.")
51
+ end
52
+
53
+ def __set_proxied_methods(lookup_hash)
54
+ @proxied_methods = lookup_hash
55
+ self
56
+ end
57
+
58
+ private
59
+
60
+ def __class
61
+ @__class ||= (class << self; self end).superclass
62
+ end
63
+
64
+ def __chain(value)
65
+ __class.new(value, false).__set_proxied_methods(@proxied_methods)
66
+ end
67
+
68
+ def __build_method_lookup_table(value, methods)
69
+ return(self) if (methods.first == false)
70
+ @proxied_methods.merge!(value.command_methods) \
71
+ if value.respond_to?(:command_methods)
72
+ methods.each { |method| @proxied_methods[method.to_sym] = true }
73
+ self
74
+ end
75
+ end
76
+ end
77
+