laminar 0.3.0
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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +22 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +362 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/laminar.gemspec +42 -0
- data/lib/laminar.rb +13 -0
- data/lib/laminar/callbacks.rb +53 -0
- data/lib/laminar/context.rb +45 -0
- data/lib/laminar/flow.rb +148 -0
- data/lib/laminar/flow/branch.rb +57 -0
- data/lib/laminar/flow/flow_error.rb +9 -0
- data/lib/laminar/flow/options_validator.rb +39 -0
- data/lib/laminar/flow/specification.rb +55 -0
- data/lib/laminar/flow/step.rb +87 -0
- data/lib/laminar/particle.rb +69 -0
- data/lib/laminar/particle_stopped.rb +13 -0
- data/lib/laminar/version.rb +5 -0
- metadata +140 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Laminar
|
4
|
+
module Flow
|
5
|
+
# Specification for a flow (chained sequence of particles).
|
6
|
+
class Specification
|
7
|
+
attr_accessor :steps, :first_step
|
8
|
+
|
9
|
+
def initialize(_args = {}, &spec)
|
10
|
+
@steps = {}
|
11
|
+
instance_eval(&spec) if spec
|
12
|
+
end
|
13
|
+
|
14
|
+
def step(name, options = {}, &gotos)
|
15
|
+
step = add_step(name, options, &gotos)
|
16
|
+
|
17
|
+
# backport a default next step onto the previous step to point to
|
18
|
+
# the current one, unless this is the first step. Allows for simple
|
19
|
+
# case where execution just falls through to the next step where they
|
20
|
+
# haven't specified any explicit branching or none of the branch
|
21
|
+
# conditions get met.
|
22
|
+
@prev_step&.branch(step.name)
|
23
|
+
@prev_step = step
|
24
|
+
end
|
25
|
+
|
26
|
+
def before_each(*args, &block)
|
27
|
+
before_each_callbacks.concat(args)
|
28
|
+
before_each_callbacks << block if block
|
29
|
+
end
|
30
|
+
|
31
|
+
def after_each(*args, &block)
|
32
|
+
after_each_callbacks.concat(args)
|
33
|
+
after_each_callbacks << block if block
|
34
|
+
end
|
35
|
+
|
36
|
+
def before_each_callbacks
|
37
|
+
@before_each_callbacks ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_each_callbacks
|
41
|
+
@after_each_callbacks ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def add_step(name, options = {}, &gotos)
|
47
|
+
raise ArgumentError, "Step #{name} defined twice" if @steps.key?(name)
|
48
|
+
|
49
|
+
step = Step.new(name, options, &gotos)
|
50
|
+
@first_step ||= step.name
|
51
|
+
@steps[step.name] = step
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'laminar/flow/options_validator'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
|
7
|
+
module Laminar
|
8
|
+
module Flow
|
9
|
+
# Specification for an executable step in a Flow.
|
10
|
+
class Step
|
11
|
+
include OptionsValidator
|
12
|
+
attr_reader :name, :branches, :class_name
|
13
|
+
|
14
|
+
valid_options %i[class].freeze
|
15
|
+
|
16
|
+
def initialize(name, options = {}, &block)
|
17
|
+
unless name.class.method_defined?(:to_sym)
|
18
|
+
raise ArgumentError, 'invalid name'
|
19
|
+
end
|
20
|
+
|
21
|
+
validate_options(options)
|
22
|
+
@class_name = (options[:class] || name).to_s.camelize
|
23
|
+
@name = name.to_sym
|
24
|
+
@branches = []
|
25
|
+
|
26
|
+
instance_eval(&block) if block
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return class instance of the associated particle.
|
30
|
+
def particle
|
31
|
+
class_name.constantize
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add a branch specification. This is typically called as
|
35
|
+
# part of a flow specification:
|
36
|
+
#
|
37
|
+
# flow do
|
38
|
+
# step :step1
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
def branch(target, options = {})
|
42
|
+
branches << Branch.new(target, options)
|
43
|
+
end
|
44
|
+
alias goto branch
|
45
|
+
|
46
|
+
# Find the next rule in the flow. Examines the branches associated
|
47
|
+
# with the current rule and returns the name of the first branch
|
48
|
+
# that satisfies its condition.
|
49
|
+
def next_step_name(impl_context)
|
50
|
+
branch = first_applicable_branch(impl_context)
|
51
|
+
return if branch.nil?
|
52
|
+
|
53
|
+
branch.name
|
54
|
+
end
|
55
|
+
|
56
|
+
# Defines a callback to run before the flow executes the step.
|
57
|
+
def before(*args, &block)
|
58
|
+
before_callbacks.concat(args)
|
59
|
+
before_callbacks << block if block
|
60
|
+
end
|
61
|
+
|
62
|
+
# Defines a callback to run after the flow executes the step.
|
63
|
+
def after(*args, &block)
|
64
|
+
after_callbacks.concat(args)
|
65
|
+
after_callbacks << block if block
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the list of before callbacks.
|
69
|
+
def before_callbacks
|
70
|
+
@before_callbacks ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return the list of after callbacks.
|
74
|
+
def after_callbacks
|
75
|
+
@after_callbacks ||= []
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the first branch that satisfies its condition.
|
79
|
+
def first_applicable_branch(target)
|
80
|
+
branches.each do |branch|
|
81
|
+
return branch if branch.meets_condition?(target)
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Laminar
|
4
|
+
# Base methods for a logic particle. Particles can be invoked
|
5
|
+
# by themselves or as part of a Flow. Classes should include
|
6
|
+
# this module rather than inherit.
|
7
|
+
module Particle
|
8
|
+
def self.included(klass)
|
9
|
+
klass.class_eval do
|
10
|
+
extend ClassMethods
|
11
|
+
include Callbacks
|
12
|
+
include InstanceMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :context
|
16
|
+
end
|
17
|
+
|
18
|
+
# Laminar::Particle class methods and attributes.
|
19
|
+
module ClassMethods
|
20
|
+
def call(context = {})
|
21
|
+
new(context).invoke
|
22
|
+
end
|
23
|
+
|
24
|
+
def call!(context = {})
|
25
|
+
new(context).invoke!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def initialize(context = {})
|
31
|
+
@context = Context.build(context)
|
32
|
+
end
|
33
|
+
|
34
|
+
def invoke
|
35
|
+
invoke!
|
36
|
+
rescue ParticleStopped
|
37
|
+
context
|
38
|
+
end
|
39
|
+
|
40
|
+
def invoke!
|
41
|
+
run_before_callbacks
|
42
|
+
return context if context.halted?
|
43
|
+
|
44
|
+
param_list = context_slice
|
45
|
+
param_list.empty? ? call : call(context_slice)
|
46
|
+
run_after_callbacks
|
47
|
+
context
|
48
|
+
end
|
49
|
+
|
50
|
+
def call; end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def context_slice
|
55
|
+
context.select { |k, _v| introspect_params.include?(k) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns an array of keyword parameters that the instance expects
|
59
|
+
# or accepts. If the signature includes a 'splat' (:keyrest) to catch
|
60
|
+
# a variable set of arguments, returns the current context keys.
|
61
|
+
def introspect_params
|
62
|
+
params = self.class.instance_method(:call).parameters
|
63
|
+
return context.keys if params.map(&:first).include?(:keyrest)
|
64
|
+
|
65
|
+
params.map(&:last)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Laminar
|
4
|
+
# Raised when someone calls fail!() on a Laminar::Context.
|
5
|
+
class ParticleStopped < StandardError
|
6
|
+
attr_reader :context
|
7
|
+
|
8
|
+
def initialize(context = nil)
|
9
|
+
@context = context
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: laminar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Lockerd
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-10-01 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: '4.2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
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.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.16'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.16'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- rmlockerd@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
91
|
+
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
92
|
+
- ".gitignore"
|
93
|
+
- ".rspec"
|
94
|
+
- ".travis.yml"
|
95
|
+
- CHANGELOG.md
|
96
|
+
- CODE_OF_CONDUCT.md
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- bin/console
|
102
|
+
- bin/setup
|
103
|
+
- laminar.gemspec
|
104
|
+
- lib/laminar.rb
|
105
|
+
- lib/laminar/callbacks.rb
|
106
|
+
- lib/laminar/context.rb
|
107
|
+
- lib/laminar/flow.rb
|
108
|
+
- lib/laminar/flow/branch.rb
|
109
|
+
- lib/laminar/flow/flow_error.rb
|
110
|
+
- lib/laminar/flow/options_validator.rb
|
111
|
+
- lib/laminar/flow/specification.rb
|
112
|
+
- lib/laminar/flow/step.rb
|
113
|
+
- lib/laminar/particle.rb
|
114
|
+
- lib/laminar/particle_stopped.rb
|
115
|
+
- lib/laminar/version.rb
|
116
|
+
homepage: https://github.com/rmlockerd/laminar
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.6.11
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Simple, composable business objects & workflow
|
140
|
+
test_files: []
|