calculi 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.
@@ -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: