calculi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50862d54a9637f48c9e2e8b6930598ace106926b
4
+ data.tar.gz: 4e2d75db644a1ec3d7a39e304f262a28ed748a68
5
+ SHA512:
6
+ metadata.gz: 969cf0229ef0ffd07d80441f43232e2af5c0999673454096892670864d1fd900a0218047fda379e401b7947c66a67def459c2b383e4673a11b2b1e87285e7aac
7
+ data.tar.gz: c3f8c31e64520e3c14a39407a71fca4a5a7462546e457267eb749a7e96bbb3ec88d00944470d8b25ad16f414effe82ad10d556f689de7d7e43029bb23969e1b3
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ tags
data/.pryrc ADDED
@@ -0,0 +1,5 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'calculi'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in calculi.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Alexa Grey
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Calculi
2
+
3
+ Undergoing development. API may change drastically.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'calculi'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install calculi
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'calculi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "calculi"
8
+ spec.version = Calculi::VERSION
9
+ spec.authors = ["Alexa Grey"]
10
+ spec.email = ["devel@mouse.vc"]
11
+ spec.description = %q{Calculated functions for metaprogramming.}
12
+ spec.summary = %q{Calculated functions for metaprogramming.}
13
+ spec.homepage = "https://github.com/scryptmouse/calculi"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'activesupport', '>= 3.2.0'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,40 @@
1
+ require 'tsort'
2
+
3
+ require 'active_support'
4
+ require 'active_support/callbacks'
5
+ require 'active_support/concern'
6
+ require 'active_support/core_ext/module/delegation'
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/object/try'
9
+ require 'active_support/core_ext/array/extract_options'
10
+ require 'active_support/core_ext/hash/indifferent_access'
11
+ require 'active_support/core_ext/date/calculations'
12
+ require 'active_support/core_ext/time/calculations'
13
+ require 'active_support/core_ext/string/inflections'
14
+ require 'active_support/core_ext/numeric/time'
15
+
16
+ module Calculi
17
+ require 'calculi/version'
18
+ # Your code goes here...
19
+ end
20
+
21
+ require 'calculi/utility'
22
+ require 'calculi/recursion'
23
+
24
+ require 'calculi/attribute'
25
+
26
+ %w[abstract boolean duration integer procable string].each do |type|
27
+ require "calculi/attribute/#{type}"
28
+ end
29
+
30
+ require 'calculi/option_set'
31
+ require 'calculi/has_option_set'
32
+
33
+ require 'calculi/attributes'
34
+
35
+ require 'calculi/base'
36
+ require 'calculi/caller'
37
+ require 'calculi/function'
38
+ require 'calculi/function_set'
39
+
40
+ require 'calculi/has_function_set'
@@ -0,0 +1,23 @@
1
+ module Calculi::Attribute
2
+ class << self
3
+ def lookup(type)
4
+ if valid_type?(type)
5
+ type
6
+ elsif type.kind_of?(String) || type.kind_of?(Symbol)
7
+ type_from_string type
8
+ else
9
+ raise "Unknown attribute type: #{type}"
10
+ end
11
+ end
12
+
13
+ def valid_type?(type_klass)
14
+ type_klass.kind_of?(Module) && type < Calculi::Attribute::Abstract
15
+ end
16
+
17
+ def type_from_string(type_name)
18
+ type_name = type_name.to_s.classify.demodulize
19
+
20
+ "Calculi::Attribute::#{type_name}".constantize
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,122 @@
1
+ # Typed attributes with defaults.
2
+ # @abstract
3
+ class Calculi::Attribute::Abstract < Module
4
+ include Calculi::Utility
5
+
6
+ # @!attribute [r] default
7
+ # Default value for the attribute
8
+ # @return [Boolean]
9
+ attr_reader :default
10
+
11
+ # @!attribute [r] ivar_name
12
+ # Name of the instance variable used to store the attribute's value.
13
+ # @return [String]
14
+ attr_reader :ivar_name
15
+
16
+ # @!attribute [r] name
17
+ # Name of the attribute
18
+ # @return [String]
19
+ attr_reader :name
20
+
21
+ # @!attribute [r] setter
22
+ # Setter to use
23
+ # @return [Proc]
24
+ attr_reader :setter
25
+
26
+ NO_DEFAULT = Object.new
27
+
28
+ NO_DEFAULT.instance_eval do
29
+ def to_proc
30
+ -> { nil }
31
+ end
32
+ end
33
+
34
+ PASSTHRU = ->(o) { o }
35
+
36
+ def initialize(name, options = {}, &setter)
37
+ @name = name.to_s
38
+
39
+ @ivar_name = at_prefixed name
40
+
41
+ @default = options.fetch :default, NO_DEFAULT
42
+
43
+ @readonly = !!options.fetch(:readonly, false)
44
+
45
+ @setter = block_given? ? setter : options.fetch(:setter) { default_setter }
46
+
47
+ define_attribute_methods!
48
+ end
49
+
50
+ # @abstract
51
+ # @return [Proc]
52
+ def default_setter
53
+ PASSTHRU
54
+ end
55
+
56
+ def default?
57
+ default != NO_DEFAULT
58
+ end
59
+
60
+ def inspect
61
+ "#{self.class}(:name, :default => #{default.inspect})"
62
+ end
63
+
64
+ def readonly?
65
+ @readonly
66
+ end
67
+
68
+ private
69
+ def default_name
70
+ @default_name ||= :"default_#{name}"
71
+ end
72
+
73
+ def getter_name
74
+ @getter_name ||= :"#{name}"
75
+ end
76
+
77
+ def predicate_name
78
+ @predicate_name ||= :"#{name}?"
79
+ end
80
+
81
+ def setter_name
82
+ @setter_name ||= :"#{name}="
83
+ end
84
+
85
+ def define_attribute_methods!
86
+ define_default!
87
+ define_getter!
88
+ define_predicate!
89
+ define_setter! unless readonly?
90
+ end
91
+
92
+ def define_default!
93
+ method_body = procable?(default) ? default : constantly(default)
94
+
95
+ define_method(default_name, &method_body)
96
+ end
97
+
98
+ def define_getter!
99
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
100
+ def #{getter_name}
101
+ defined?(#{ivar_name}) ? #{ivar_name} : (#{ivar_name} = #{default_name})
102
+ end
103
+ RUBY
104
+ end
105
+
106
+ def define_predicate!
107
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
108
+ def #{predicate_name}
109
+ !#{getter_name}.nil?
110
+ end
111
+ RUBY
112
+ end
113
+
114
+ def define_setter!
115
+ ivar = ivar_name
116
+ cast_value = setter
117
+
118
+ define_method setter_name do |new_value|
119
+ instance_variable_set ivar, cast_value.(new_value)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,15 @@
1
+ class Calculi::Attribute::Boolean < Calculi::Attribute::Abstract
2
+ # @!attribute [r] default
3
+ # Default value for the attribute
4
+ # @return [Boolean]
5
+
6
+ # @override
7
+ def default_setter
8
+ ->(o) { !!o }
9
+ end
10
+
11
+ private
12
+ def define_predicate!
13
+ alias_method predicate_name, getter_name
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class Calculi::Attribute::Duration < Calculi::Attribute::Abstract
2
+ def default_setter
3
+ ->(new_duration) do
4
+ case new_duration
5
+ when Integer then new_duration
6
+ when Numeric then new_duration.to_i
7
+ when :hour, :hourly then 1.hour
8
+ when :day, :daily then 1.day
9
+ when :week, :weekly then 1.week
10
+ when Proc then coerce(value.call)
11
+ else 1.day
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,5 @@
1
+ class Calculi::Attribute::Integer < Calculi::Attribute::Abstract
2
+ def default_setter
3
+ ->(o) { o.try(:to_i) || 0 }
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Calculi::Attribute::Procable < Calculi::Attribute::Abstract
2
+ def default_setter
3
+ lambda do |o|
4
+ raise TypeError, "#{o.inspect} isn't procable" unless procable? o
5
+
6
+ o
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Calculi::Attribute::String < Calculi::Attribute::Abstract
2
+ def default_setter
3
+ ->(o) { o.to_s }
4
+ end
5
+ end
@@ -0,0 +1,66 @@
1
+ module Calculi::Attributes
2
+ extend ActiveSupport::Concern
3
+
4
+ include ActiveSupport::Callbacks
5
+ include Calculi::Utility
6
+ include Calculi::HasOptionSet
7
+
8
+ included do
9
+ define_callbacks :initialize, :configure
10
+ end
11
+
12
+ def initialize(options = {}, &configurator)
13
+ process_options! options
14
+
15
+ run_callbacks :initialize
16
+
17
+ configure(&configurator) if block_given?
18
+ end
19
+
20
+ def configure(&configurator)
21
+ run_callbacks :configure do
22
+ instance_eval(&configurator)
23
+ end
24
+ end
25
+
26
+ private
27
+ module ClassMethods
28
+ def calculi_attr(name, type, options = {}, &custom_setter)
29
+ type = Calculi::Attribute.lookup(type)
30
+
31
+ include type.new(name, options, &custom_setter)
32
+ end
33
+
34
+ def calculi_boolean(name, options = {})
35
+ options = { default: true } if options == true
36
+
37
+ calculi_attr name, :boolean, options
38
+ end
39
+
40
+ alias_method :calculi_bool, :calculi_boolean
41
+
42
+ def calculi_computed(name, &computer_block)
43
+ define_method(name) do
44
+ instance_variable_compute(name, &computer_block)
45
+ end
46
+ end
47
+
48
+ def calculi_duration(name, options = {})
49
+ calculi_attr name, :duration, options
50
+ end
51
+
52
+ def calculi_integer(name, options = {})
53
+ calculi_attr name, :integer, options
54
+ end
55
+
56
+ alias_method :calculi_int, :calculi_integer
57
+
58
+ def calculi_procable(name, options = {})
59
+ calculi_attr name, :procable, options
60
+ end
61
+
62
+ def calculi_string(name, options = {})
63
+ calculi_attr name, :string, options
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,6 @@
1
+ # Shared logic for {Calculi::Function}s and {Calculi::FunctionSet}s.
2
+ module Calculi::Base
3
+ extend ActiveSupport::Concern
4
+
5
+ include Calculi::Attributes
6
+ end
@@ -0,0 +1,40 @@
1
+ class Calculi::Caller
2
+ # @!attribute [r] originator
3
+ # @return [Calculi::Function]
4
+ attr_reader :originator
5
+
6
+ # @!attribute [r] context
7
+ # @return [Object]
8
+ attr_reader :context
9
+
10
+ delegate :key, :function_set, :recursive?, to: :originator
11
+ delegate :fetch, to: :function_set
12
+
13
+ def initialize(originator, context)
14
+ @originator = originator
15
+ @context = context
16
+ end
17
+
18
+ def call(other_function_key, *args)
19
+ prevent_recursion! other_function_key
20
+
21
+ fetch(other_function_key).call(context, *args)
22
+ end
23
+
24
+ alias_method :[], :call
25
+
26
+ def chain(*other_functions)
27
+ other_functions.reduce(context) do |last_context, other_function_key|
28
+ other_fn = fetch(other_function_key)
29
+
30
+ other_fn.call last_context
31
+ end
32
+ end
33
+
34
+ private
35
+ def prevent_recursion!(other_function_key)
36
+ if other_function_key == key && !recursive?
37
+ raise Calculi::Recursion, other_function_key
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,106 @@
1
+ # A calculated function.
2
+ class Calculi::Function
3
+ include Calculi::Base
4
+
5
+ # @!attribute [r] memoize
6
+ # @return [Boolean]
7
+ calculi_bool :memoize
8
+
9
+ # @!attribute [r] recursive
10
+ # @note currently no effect
11
+ # @todo implement trampoline or some pseudostack to recur?
12
+ # @return [Boolean]
13
+ calculi_bool :recursive
14
+
15
+ options! :key, :function_set, memoize: false
16
+
17
+ # @param context the object that will actually be _calling_ the function, i.e.
18
+ # what the value of `self` should be.
19
+ # @param args any arguments to pass to the body of the function
20
+ def call(context, *args)
21
+ fn_caller = Calculi::Caller.new(self, context)
22
+
23
+ context.instance_exec(target, fn_caller, args, &body)
24
+ end
25
+
26
+ def eql?(other)
27
+ other.is_a?(Calculi::Function) && other.hash == hash
28
+ end
29
+
30
+ def hash
31
+ [function_set.hash, key.hash].hash
32
+ end
33
+
34
+ def inspect
35
+ "Calculi::Function(:#{key}, :dependencies => #{inspect_dependency_names})"
36
+ end
37
+
38
+ def inspect_dependency_names
39
+ mapped = dependency_names.map do |name|
40
+ ":#{name}"
41
+ end.join(', ')
42
+
43
+ "[#{mapped}]"
44
+ end
45
+
46
+ alias_method :to_s, :inspect
47
+
48
+ # @!group DSL methods
49
+ def body(&body_proc)
50
+ if block_given?
51
+ @body = body_proc
52
+ end
53
+
54
+ @body
55
+ end
56
+
57
+ def name(&name_proc)
58
+ if block_given?
59
+ @name = name_proc
60
+ end
61
+
62
+ @name
63
+ end
64
+
65
+ def requires(*deps)
66
+ dependency_names.merge deps.flatten.map(&:to_s)
67
+ end
68
+ # @!endgroup
69
+
70
+ def realized_name
71
+ @realized_name ||= target.instance_eval(&@name)
72
+ end
73
+
74
+ def realized_body
75
+ #@realized_body ||= begin
76
+ #__fn = self
77
+
78
+ #->(*args) { __fn.call(self, *args) }
79
+ #end
80
+ instance_variable_compute 'realized_body' do
81
+ __fn = self
82
+
83
+ ->(*args) { __fn.call(self, *args) }
84
+ end
85
+ end
86
+
87
+ # @!group Attributes
88
+ # @!attribute [r] dependency_names
89
+ # @api private
90
+ # @return [Set<String>]
91
+ def dependency_names
92
+ @dependency_names ||= Set.new
93
+ end
94
+
95
+ # @!attribute [r] function_set
96
+ # @return [Calculi::FunctionSet]
97
+ attr_reader :function_set
98
+
99
+ # @!attribute [r] key
100
+ attr_reader :key
101
+
102
+ # @!attribute [r] target
103
+ # @return [Class]
104
+ delegate :target, to: :function_set
105
+ # @!endgroup
106
+ end
@@ -0,0 +1,81 @@
1
+ # A set that can hold arbitrarily-computed {Calculi::Function}s
2
+ # with a dependency graph
3
+ class Calculi::FunctionSet < Module
4
+ include Calculi::Base
5
+ include Enumerable
6
+ include TSort
7
+
8
+ DEPENDENCIES_NOT_CALCULATED = Object.new
9
+
10
+ # @!attribute [r] target
11
+ # Where the generated functions should be accessible.
12
+ # @return [Class]
13
+ attr_reader :target
14
+
15
+ options! :target
16
+
17
+ delegate :each, to: :functions
18
+
19
+ set_callback :configure, :after, :define_functions!
20
+
21
+ def [](function_key)
22
+ case function_key
23
+ when Calculi::Function
24
+ functions.detect { |fn, deps| fn.eql? function_key }
25
+ when Symbol, String
26
+ function_key = function_key.to_s
27
+
28
+ functions.detect { |fn, deps| fn.key.to_s == function_key }
29
+ else
30
+ raise KeyError, "Don't know how to find function with `#{function_key.inspect}'"
31
+ end
32
+ end
33
+
34
+ def fetch(fn)
35
+ self[fn].try(:first) or raise KeyError, "Unknown function: #{fn}"
36
+ end
37
+
38
+ def functions
39
+ @functions ||= {}
40
+ end
41
+
42
+ def function_names
43
+ @function_names ||= OpenStruct.new
44
+ end
45
+
46
+ def function(key, options = {}, &function_configurator)
47
+ options.merge! function_set: self, key: key
48
+
49
+ new_function = Calculi::Function.new(options, &function_configurator)
50
+
51
+ functions[new_function] = DEPENDENCIES_NOT_CALCULATED
52
+ end
53
+
54
+ def inspect
55
+ function_hash = functions.each_key.each_with_object({}) do |fn, fn_hsh|
56
+ fn_hsh[fn.key] = fn.inspect_dependency_names
57
+ end
58
+
59
+ "Calculi::FunctionSet(#{function_hash.inspect})"
60
+ end
61
+
62
+ def tsort_each_node(&block)
63
+ functions.each_key(&block)
64
+ end
65
+
66
+ def tsort_each_child(node, &block)
67
+ fetch(node).dependency_names.each do |dependency_name|
68
+ yield fetch(dependency_name)
69
+ end
70
+ end
71
+
72
+ def define_functions!
73
+ tsort.each do |fn|
74
+ function_names[fn.key] = fn.realized_name
75
+
76
+ redefine_method(fn.realized_name, &fn.realized_body)
77
+ end
78
+
79
+ target.send :include, self
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ module Calculi::HasFunctionSet
2
+ extend ActiveSupport::Concern
3
+
4
+ include Calculi::Utility
5
+ include Calculi::HasOptionSet
6
+ include Calculi::Attributes
7
+
8
+ included do
9
+ set_callback :initialize, :after, :realize_calculi_function_set!
10
+
11
+ delegate :function_names, to: :function_set
12
+ end
13
+
14
+ def realize_calculi_function_set!
15
+ calculi_function_set.configure(&stored_calculi_function_set_configurator)
16
+ end
17
+
18
+ # @!attribute [r] calculi_function_set
19
+ # @api private
20
+ # @return [Calculi::FunctionSet]
21
+ def calculi_function_set
22
+ @calculi_function_set ||= Calculi::FunctionSet.new target: self
23
+ end
24
+
25
+ def stored_calculi_function_set_configurator
26
+ self.class.stored_calculi_function_set_configurator
27
+ end
28
+
29
+ module ClassMethods
30
+ # @!attribute [rw] stored_calculi_function_set_configurator
31
+ attr_accessor :stored_calculi_function_set_configurator
32
+
33
+ # @api private
34
+ def calculi_functions(&configurator)
35
+ @stored_calculi_function_set_configurator = configurator if block_given?
36
+ end
37
+
38
+
39
+ # @api private
40
+ def calculi_function_set
41
+ @calculi_function_set ||= Calculi::FunctionSet.new target: self
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ module Calculi::HasOptionSet
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ #delegate :option_set, to: :class
6
+ end
7
+
8
+ def option_set
9
+ self.class.option_set
10
+ end
11
+
12
+ private
13
+ # @param [Hash] options
14
+ # @return [void]
15
+ def process_options!(options = {})
16
+ option_set.process!(self, options)
17
+ end
18
+
19
+ module ClassMethods
20
+ def option_set
21
+ @option_set ||= Calculi::OptionSet.new
22
+ end
23
+
24
+ def option!(name, default_value = Calculi::OptionSet::REQUIRED, &default_proc)
25
+ option_set[name] = block_given? ? default_proc : default_value
26
+ end
27
+
28
+ def options!(*names)
29
+ names = names.flatten
30
+
31
+ names_with_defaults = names.extract_options!
32
+
33
+ names_with_defaults.each do |name, default|
34
+ option_set[name] = default
35
+ end
36
+
37
+ option_set.add(*names)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,87 @@
1
+ class Calculi::OptionSet
2
+ include Calculi::Utility
3
+ include Enumerable
4
+
5
+ REQUIRED = Object.new
6
+
7
+ # @!attribute [r] defined_options
8
+ # @api private
9
+ # @return [Hash]
10
+ attr_reader :defined_options
11
+
12
+ delegate :each, to: :sorted_options
13
+
14
+ def initialize
15
+ @defined_options = {}
16
+ end
17
+
18
+ def add(*option_names)
19
+ option_names.flatten.each do |name|
20
+ define name
21
+ end
22
+
23
+ return self
24
+ end
25
+
26
+ def <<(option_name)
27
+ define option_name
28
+
29
+ return self
30
+ end
31
+
32
+ def define(option_name, default_value = REQUIRED)
33
+ defined_options.store(option_name, default_value)
34
+
35
+ clear_sorted!
36
+
37
+ return self
38
+ end
39
+
40
+ alias_method :[]=, :define
41
+
42
+ def fetch(option_name)
43
+ defined_options.fetch option_name
44
+ end
45
+
46
+ alias_method :[], :fetch
47
+
48
+ def process!(context, options)
49
+ options = options.with_indifferent_access
50
+
51
+ each do |name, default_value|
52
+ value = options.fetch name do
53
+ eval_or_value default_value, context: context
54
+ end
55
+
56
+ raise ArgumentError, "Missing required attribute: #{name}" if required?(value)
57
+
58
+ set_attribute(name, value, context: context)
59
+ end
60
+ end
61
+
62
+ private
63
+ def clear_sorted!
64
+ @sorted_options = nil
65
+ end
66
+
67
+ def required?(value)
68
+ REQUIRED === value
69
+ end
70
+
71
+ def sorted_options
72
+ @sorted_options ||= Hash[sort_options]
73
+ end
74
+
75
+ def sort_options
76
+ defined_options.sort do |(k1, v1), (k2, v2)|
77
+ r1 = required? v1
78
+ r2 = required? v2
79
+
80
+ if r1 ^ r2
81
+ r1 ? -1 : 1
82
+ else
83
+ k1 <=> k2
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,6 @@
1
+ # Raised when a non-recursive function tries to call itself
2
+ class Calculi::Recursion < StandardError
3
+ def initialize(fn_name)
4
+ super("Non-recursive `#{fn_name}' tried to call itself")
5
+ end
6
+ end
@@ -0,0 +1,73 @@
1
+ module Calculi::Utility
2
+ NON_IVAR = /\A([^@].+)\z/
3
+ NULL = Object.new
4
+
5
+ # @param [#to_s] s what to prefix
6
+ # @return [String] `@s`
7
+ def at_prefixed(s)
8
+ s.to_s.sub NON_IVAR, '@\1'
9
+ end
10
+
11
+ # @param [Object] o
12
+ # @return [Proc] f(o) = -> o
13
+ def constantly(o)
14
+ -> { o }
15
+ end
16
+
17
+ # @param [#call] thing
18
+ # @return [Boolean] whether the provided `thing` is callable
19
+ def callable?(thing)
20
+ thing.respond_to? :call
21
+ end
22
+
23
+ # @param [#to_proc] callable
24
+ # @return
25
+ def eval_or_value(callable, *args)
26
+ options = args.extract_options!
27
+
28
+ context = options.fetch :context, self
29
+
30
+ if callable.respond_to?(:to_proc)
31
+ args.length.nonzero? ? context.instance_exec(*args, &callable) : context.instance_eval(&callable)
32
+ else
33
+ callable
34
+ end
35
+ end
36
+
37
+ def instance_variable_compute(ivar_name, computed_value = NULL, &compute_value)
38
+ ivar_name = at_prefixed ivar_name
39
+
40
+ computed_value_given = computed_value != NULL
41
+ value_computer_given = block_given?
42
+
43
+ if instance_variable_defined? ivar_name
44
+ return instance_variable_get ivar_name
45
+ end
46
+
47
+ if computed_value_given ^ value_computer_given
48
+ instance_variable_set ivar_name, eval_or_value(compute_value)
49
+ elsif computed_value_given && value_computer_given
50
+ raise ArgumentError, "Cannot have both computer and computed value"
51
+ else
52
+ raise ArgumentError, "Missing compute_value block or computed_value"
53
+ end
54
+ end
55
+
56
+ # @param [#to_proc] thing
57
+ # @return [Boolean] whether the provided `thing` can be converted to a proc
58
+ def procable?(thing)
59
+ thing.respond_to? :to_proc
60
+ end
61
+
62
+ def set_attribute(name, value, options = {})
63
+ context = options.fetch :context, self
64
+
65
+ setter = "#{name}="
66
+
67
+ if context.respond_to? setter
68
+ context.__send__(setter, value)
69
+ else
70
+ context.instance_variable_set at_prefixed(name), value
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module Calculi
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: calculi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexa Grey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Calculated functions for metaprogramming.
56
+ email:
57
+ - devel@mouse.vc
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .pryrc
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - calculi.gemspec
69
+ - lib/calculi.rb
70
+ - lib/calculi/attribute.rb
71
+ - lib/calculi/attribute/abstract.rb
72
+ - lib/calculi/attribute/boolean.rb
73
+ - lib/calculi/attribute/duration.rb
74
+ - lib/calculi/attribute/integer.rb
75
+ - lib/calculi/attribute/procable.rb
76
+ - lib/calculi/attribute/string.rb
77
+ - lib/calculi/attributes.rb
78
+ - lib/calculi/base.rb
79
+ - lib/calculi/caller.rb
80
+ - lib/calculi/function.rb
81
+ - lib/calculi/function_set.rb
82
+ - lib/calculi/has_function_set.rb
83
+ - lib/calculi/has_option_set.rb
84
+ - lib/calculi/option_set.rb
85
+ - lib/calculi/recursion.rb
86
+ - lib/calculi/utility.rb
87
+ - lib/calculi/version.rb
88
+ homepage: https://github.com/scryptmouse/calculi
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.1.4
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Calculated functions for metaprogramming.
112
+ test_files: []
113
+ has_rdoc: