lab42_state_machine 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9cb027906651d393d34115d3caeb20cbbc1d21fe
4
- data.tar.gz: 7aaf1ebbe0a829426fb37af2ae98c0b90f20e540
2
+ SHA256:
3
+ metadata.gz: 9c9d1c0162351343ecc3460ed1b6b83373d80eb76690014ab7e2c0cc93f45107
4
+ data.tar.gz: ba351f35f4d8672660bfb40c9b7047c2ec6fe6027703291b071f013045d42052
5
5
  SHA512:
6
- metadata.gz: ba307dc46b61393a908927b31672083af04411b425cd55d3347bf68c2db987c8a6d76d0672f2e4aa7be80c8bd85c70770fec7c5910dfa39f1970d92edb201887
7
- data.tar.gz: 4e5990cf6a562b00ed0aed7c1176f1906750972461c4e951cb4ea91f0dcf9739d9e55f9ea42ac00c6309c7161a2103828643abe49a4c1669d36f54dc71f64c7f
6
+ metadata.gz: e98313b5a11d968b6d41c8521847e446ac9f517a13554f2bd9c4a5177fd003c21f503b148e9857c54c2dd84735e5b572f351c2910f8442d934ad26674f7ba798
7
+ data.tar.gz: 75fc35f6072842d0fa12f86ca49c0e1f56a5b91b36d7bf34fdd2a3b6ad23424a6b214906b82d9c3560cb3d602741e9fca6c19b6e4cda87d559536cf82cef3281
data/LICENSE CHANGED
@@ -1,20 +1,201 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2013 Robert Dober
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
- the Software, and to permit persons to whom the Software is furnished to do so,
10
- subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md CHANGED
@@ -1,213 +1,74 @@
1
- # lab42_state_machine
2
1
 
3
- A simple State Machine
2
+ # Lab42::StateMachine
4
3
 
5
- ## Design Principles
4
+ [![Build Status](https://travis-ci.org/RobertDober/lab42_stat_machine.svg?branch=master)](https://travis-ci.org/RobertDober/lab42_stat_machine)
5
+ [![Gem Version](https://badge.fury.io/rb/lab42_stat_machine.svg)](http://badge.fury.io/rb/lab42_stat_machine)
6
+ [![Code Climate](https://codeclimate.com/github/RobertDober/lab42_stat_machine/badges/gpa.svg)](https://codeclimate.com/github/RobertDober/lab42_stat_machine)
7
+ [![Issue Count](https://codeclimate.com/github/RobertDober/lab42_stat_machine/badges/issue_count.svg)](https://codeclimate.com/github/RobertDober/lab42_stat_machine)
8
+ [![Test Coverage](https://codeclimate.com/github/RobertDober/lab42_stat_machine/badges/coverage.svg)](https://codeclimate.com/github/RobertDober/lab42_stat_machine)
6
9
 
7
- * A minimalistic but powerful API
10
+ ## Usage
8
11
 
9
- * Protection of the SM's inner state through delegation from the API to the implementation
12
+ ### The DSL
10
13
 
11
- * Run SM against Enumerables or Enumerators
14
+ Define a State Machine by giving it a name and defining states with their transitions in a block:
12
15
 
13
- * Define a `subject` in the SM's contsructor and run will return ths `subject`, defaulting to nil, in case you are aiming for side effects, but you are surely not, right ;)?
16
+ Example: A Simple Definition
14
17
 
15
-
16
- ## Examples
17
-
18
- See also spec/acceptance/readme\_spec.rb
19
-
20
-
21
- A counter
22
-
23
- ```ruby
24
- require 'lab42/state_machine'
25
-
26
- Lab42::StateMachine.new 0 do # self.subject <-- 0
27
- state :init do
28
- self.subject += 1
18
+ ```ruby :example
19
+ Lab42::StateMachine.new "my machine" do
20
+ state :start do
21
+ trigger %r{\A[aeiou]} do |acc, _|
22
+ [nil, [acc.first+1, acc.last]]
23
+ end
24
+ end
29
25
  end
30
- end.run %w{a b c} # ===> 3 (self.subject)
31
26
  ```
32
27
 
33
- This simple example demonstrates the following essential rules:
34
-
35
- * An easy way to setup the State Machine is by providing a block to its constructor. This block
36
- is instance evalled and thus has access to the whole API (see below). But of course the public API
37
- methods can be accessed in a more detached style too:
38
- ```state_machine.state :reset do @counter = 0 end```
39
-
40
- * The initial state of the State Machine defaults to `:init`
28
+ Be aware that a trigger block needs to accept at least one argument
41
29
 
42
- The next example will demonstrate more API methods
30
+ Example: This raises an ArgumentError
43
31
 
44
- ```ruby
45
- SM = Lab42::StateMachine
46
-
47
- SM.new do
48
- # Before anything is run
49
- setup do
50
- @index = 0
51
- self.subject = Hash.new{ |h, k| h[k]=[] }
52
- end
53
- # After each transition
54
- after do
55
- @index += 1
56
- end
57
-
58
- # Before each transition
59
- before do | input |
60
- state input # sets state to input {true, false} in our case
61
- end
32
+ ```ruby :example
33
+ machine =
34
+ Lab42::StateMachine.new "my machine" do
35
+ state :start do
36
+ trigger %r{\A[aeiou]} do
37
+ [nil, nil]
38
+ end
39
+ end
40
+ end
41
+ expect{ machine.run(nil, ["abc"]) }.to raise_error(ArgumentError)
42
+ ```
62
43
 
63
- # In this case input and new_state are known to be true of course
64
- transition init: true, false => true do | input, old_state, new_state |
65
- subject[true] << [@index]
66
- subject[false].last << @index if subject[false].last
67
- end
44
+ Also there is one state you must not defined, the `:stop` state
68
45
 
69
- # We show a different variation for the to false transitions here
70
- # much more elegant IMHO
71
- transition init: false do
72
- subject[false] << [0]
73
- end
74
- transition true => false do
75
- subject[false] << [@index]
76
- subject[true].last << @index
77
- end
46
+ Example: Defining the `:stop` state
78
47
 
79
- # There is no transition towards the end state (yet?).
80
- teardown do | last_state |
81
- subject[last_state].last << @index.succ if subject[last_state].last # Take care of empty input here
48
+ ```ruby :example
49
+ expect do
50
+ Lab42::StateMachine.new "booom, don't stop" do
51
+ state :stop do
52
+ trigger true, :start
82
53
  end
83
-
84
- end.run [true, false, false, true, true, true, false]
85
- # ===> { true => [[0,1],[3,6]], false => [[1,3], [6,7]] }
86
- ```
87
-
88
- In particular we are allowed to use instance variables (and their sexier accessor implementations) to transport
89
- state between the states of the State Machine. This is without danger as the API hides all behind the controller
90
- and does not contain *any* instance variable (not even `@controller`).
91
-
92
- Of course it might be worthwile to consider closing over some local variables like in the following example if appropriate.
93
-
94
- ```ruby
95
- # Sometime is is preferble to use the State Machine with a state implementing object
96
- result = SomeObject.new
97
- StateMachine.new do
98
- before do | input |
99
- state = some_function_of input
100
- end
101
- transition a: :b do | input |
102
- result.update from: :a, to: :b, with: :input
103
54
  end
104
- teardown do | last_input, old_state |
105
- result.update from: old_state, to: result.end, with: last_input
106
- end
107
- end.run ...
55
+ end.to raise_error(FrozenError)
56
+
108
57
  ```
109
58
 
110
- ## API
111
-
112
- ### Setup Methods and Handler Definitions
113
-
114
- The following methods can be used inside the constructor block without explicit revceiver, or just invoked on a `StateMachine` instance.
115
-
116
- #### after
117
-
118
- `after do | input, old_state, new_state |` will be run after each input record has been processed. As all handlers they will be executed
119
- in order of definition.
120
-
121
- #### before
122
-
123
- `before do | input, old_state, new_state |` will be run before each input record has been processed. As all handlers they will be executed
124
- in order of definition.
125
-
126
-
127
- #### setup
128
-
129
- `setup do |input|` The StateMachine peeks into the lazy enumerator and provides the first value (or nil for empty) as parameter. These handlers are the first to be run before any other and *before* the first input record will be
130
- fetched. Yet they give access to the Runtime API, notably they allow to set the initial state to something else as `:init` by means of the one parameter form to the state call
131
- ```state :new_initial_state```
132
59
 
133
- #### state
134
60
 
135
- This is the (overloaded - for the sake of a slim API) workerbee of the State Machine, it comes in three forms:
136
61
 
137
- ##### state querying form (0 params)
138
62
 
139
- Really part of the *Runtime API*
140
-
141
- `state` currrent_state of the StateMachine
142
-
143
- ##### state setter form (1 param)
144
-
145
- Really part of the *Runtime API*
146
-
147
- `state new_state` You'll never guess what this one does.
148
-
149
- ##### state handler definition form (1 param and block)
150
-
151
- `state some_state do |input, old_state, current_state| ...` These blocks will be called for each input record processed in some\_state
152
-
153
- #### teardown
154
-
155
- `teardown do | last_input, last_state |` Will be called at the very end of the processing cycle.
156
-
157
- #### transition
158
-
159
- `transition a: :b, b: :c do |input, old_state, new_state|` These are execute when the state changes from :a to :b, or :b to :a. Of course only one transition can be indicated (and mostly will be). These handlers are executed *after* the state handlers fot the new states, that is :b or :c in our case.
160
-
161
- ### Runtime API
162
-
163
- #### halt\_machine
164
-
165
- ```halt_machine``` stops the execution and makes run return the current `subject`
166
-
167
- Raises a `StopIteration` and does therefore not make any sense in `setup` or `teardown` handlers
168
-
169
-
170
- #### state
171
-
172
- As already mentioned in the Definition and Setup API
173
-
174
- This is the (overloaded - for the sake of a slim API) workerbee of the State Machine, it comes in three forms:
175
-
176
- ##### state querying form (0 params)
177
-
178
-
179
- `state` Query currrent_state of the StateMachine
180
-
181
- ##### state setter form (1 param)
182
-
183
- ```state new_state``` Set new state of the StateMachine
184
-
185
- ### Advanced Stream API
186
-
187
- This part of the API is exposed by `controller` and allows the StateMachine to implement some stream operations like `rewind` or `drop_while`.
188
-
189
- Unless indicated otherwise the invocation of a method in this API does not interrupt the normal flow of the StateMachine but the bang version
190
- also implies a `skip`. Thusly in general
191
-
192
- ```ruby
193
- controller.<advanced_stream_api_method>!
194
- ```
195
-
196
- is the same as
197
-
198
- ```ruby
199
- controller.<advanced_stream_api_method>
200
- skip
201
- ```
202
63
 
203
- Also unless indicated otherwise the result of the invocation replaces the internal stream of the controller (having effect on input from the **next step only**)
64
+ ## Author
204
65
 
205
- In order to better understand this API we recommand to have a look at the [corresponding specs]( https://github.com/RobertDober/lab42_state_machine/blob/master/spec/units/stream_api_spec.rb)
66
+ Copyright © 2020 Robert Dober
67
+ mailto: robert.dober@gmail.com
206
68
 
207
- #### drop\_until( &condition)
69
+ # LICENSE
208
70
 
209
- Works like ```drop_while{ |ele| !condition.( ele ) }```
71
+ Same as Elixir -- &#X1F609; --, which is Apache License v2.0. Please refer to [LICENSE](LICENSE) for details.
210
72
 
211
- #### drop\_while( &condition )
73
+ SPDX-License-Identifier: Apache-2.0
212
74
 
213
- Forwarded to the internal stream which is replaced by this result.
@@ -1,53 +1,72 @@
1
- require 'lab42/state_machine/controller'
2
- require 'forwarder'
3
-
1
+ require_relative 'state_machine/state.rb'
2
+ require_relative 'state_machine/dsl.rb'
4
3
  module Lab42
5
4
  class StateMachine
6
- extend Forwarder
7
- attr_accessor :subject
8
5
 
9
- forward_all :after, :before, :setup, :skip, :state, :teardown, :transition, to: :controller
6
+ attr_accessor :current, :input, :output
7
+ attr_reader :designation, :options, :states
8
+
9
+
10
+ def add(state, trigger, action, new_state=nil)
11
+ s = states[state]
12
+ s.add(trigger, action, new_state)
13
+ end
10
14
 
11
- def behavior name, &behave
12
- class << self; self end. module_eval do
13
- define_method name do behave end
15
+ def run(accumulator=nil, input=nil)
16
+ if input
17
+ @input = input
18
+ @input = input.enum_for(:each) unless Enumerator === input
14
19
  end
20
+ loop do
21
+ accumulator = _transition(accumulator)
22
+ end
23
+ accumulator
15
24
  end
16
25
 
17
- def clone new_subject=nil, &redefinition
18
- old_controller = controller
19
- copy = self.class.new( new_subject || subject.clone ){
20
- controller.send :inherit_from!, old_controller
26
+
27
+ private
28
+
29
+ def initialize(name=nil, copy_on_nil: false, &blk)
30
+ @options = {
31
+ copy_on_nil: copy_on_nil,
21
32
  }
22
- copy.instance_eval( &redefinition ) if redefinition
23
- copy
33
+ @designation = name
34
+ _init_vars
35
+ _define_machine blk
24
36
  end
25
37
 
26
- def halt_machine
27
- raise StopIteration, "#{self} halted"
38
+ def _init_stop_state
39
+ add(:stop, true, ->(_){ raise StopIteration })
40
+ @states[:stop].freeze
28
41
  end
29
42
 
30
- def run input=[]
31
- controller.run input
32
- subject
33
- end
34
-
35
- private
43
+ def _init_vars
44
+ @output = []
45
+ @current = :start
46
+ @states = Hash.new{ |h, k| h[k] = State.new(k) }
47
+ _init_stop_state
48
+ end
36
49
 
37
- def initialize subject=nil, &define_machine
38
- self.subject = subject
39
- __make_new_controller__
50
+ def _current_state
51
+ states[current]
52
+ end
40
53
 
41
- instance_eval( &define_machine ) if define_machine
54
+ def _define_machine blk
55
+ return unless blk
56
+ DSL.new(self, &blk)
42
57
  end
43
58
 
44
- def __make_new_controller__
45
- controller = Controller.new self
46
- class << self; self end.module_eval do
47
- define_method( :controller ){ controller }
59
+ def _transition(accumulator)
60
+ next_line = input.next
61
+ output, accumulator, next_state = _current_state.transition(accumulator, next_line)
62
+ @current = next_state
63
+ if output
64
+ @output << output
65
+ elsif options[:copy_on_nil]
66
+ @output << next_line
48
67
  end
68
+ accumulator
49
69
  end
50
70
 
51
-
52
- end # class StateMachine
53
- end # module Lab42
71
+ end
72
+ end
@@ -0,0 +1,27 @@
1
+ module Lab42
2
+ class StateMachine
3
+ class DSL
4
+ IllegalMonitorState = Class.new RuntimeError
5
+
6
+ attr_reader :current_state, :machine
7
+
8
+ def state designation, &blk
9
+ @current_state = designation
10
+ instance_eval(&blk)
11
+ end
12
+
13
+ def trigger trigger_exp, new_state = nil, &blk
14
+ raise IllegalMonitorState unless current_state
15
+ machine.add(current_state, trigger_exp, blk, new_state)
16
+ end
17
+
18
+
19
+ private
20
+
21
+ def initialize machine, &blk
22
+ @machine = machine
23
+ instance_eval(&blk)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ require "lab42/match"
2
+ module Lab42
3
+ class StateMachine
4
+ class State
5
+ attr_reader :designation, :triggers
6
+
7
+ def add(trigger, action, new_state)
8
+ trigger = Lab42::Match.new(trigger) if Regexp === trigger
9
+ new_state ||= designation
10
+ @triggers << [trigger, action, new_state]
11
+ end
12
+
13
+ def transition(accumulator, input)
14
+ triggers.each do |trigger, action, new_state|
15
+ match = _match input, trigger
16
+ next unless match
17
+ output, new_acc, new_state1 = _apply(match, accumulator, input: input, to: action)
18
+ output = output.string if Lab42::Match === output
19
+ return [output, new_acc || accumulator, new_state1 || new_state || designation]
20
+ end
21
+ [input, accumulator, designation]
22
+ end
23
+
24
+ def _match(input, trigger)
25
+ case trigger
26
+ when Lab42::Match
27
+ m =trigger.match(input)
28
+ m.success? && m
29
+ when TrueClass
30
+ true
31
+ when FalseClass
32
+ raise StopIteration
33
+ when Symbol
34
+ input.send trigger
35
+ end
36
+ end
37
+
38
+ def freeze
39
+ super
40
+ @triggers.freeze
41
+ end
42
+
43
+ private
44
+
45
+ def _apply(match, accumulator, input:, to:)
46
+ if to
47
+ case to.arity
48
+ when 1
49
+ to.(match)
50
+ when 2
51
+ to.(accumulator, match)
52
+ else
53
+ raise ArgumentError, "#{to} needs to accept one or two parameters , as it is a State's action"
54
+ end
55
+ else
56
+ input
57
+ end
58
+ end
59
+
60
+ def initialize(designation)
61
+ @designation = designation
62
+ @triggers = []
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,5 @@
1
1
  module Lab42
2
2
  class StateMachine
3
- Version = "0.2.1"
4
- end # class StateMachine
5
- end # module Lab42
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
metadata CHANGED
@@ -1,72 +1,157 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_state_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-13 00:00:00.000000000 Z
11
+ date: 2020-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: forwarder19
14
+ name: forwarder2
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.0
19
+ version: '0.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.0
26
+ version: '0.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: pry
28
+ name: lab42_match
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.12
33
+ version: 0.1.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.9'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - ~>
52
+ - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 0.9.12
54
+ version: '3.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-doc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.1.0
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: rspec
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
- - - ~>
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: speculate_about
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
46
102
  - !ruby/object:Gem::Version
47
- version: 2.13.0
103
+ version: '0.15'
48
104
  type: :development
49
105
  prerelease: false
50
106
  version_requirements: !ruby/object:Gem::Requirement
51
107
  requirements:
52
- - - ~>
108
+ - - "~>"
53
109
  - !ruby/object:Gem::Version
54
- version: 2.13.0
55
- description: Filters, Transitions
110
+ version: '0.15'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codeclimate-test-reporter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: travis-lint
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.0'
139
+ description: Define triggers and transitios, what else? Well... Filter
56
140
  email: robert.dober@gmail.com
57
141
  executables: []
58
142
  extensions: []
59
143
  extra_rdoc_files: []
60
144
  files:
61
- - lib/lab42/state_machine/version.rb
62
- - lib/lab42/state_machine/controller.rb
63
- - lib/lab42/state_machine/controller/stream_api.rb
64
- - lib/lab42/state_machine.rb
65
145
  - LICENSE
66
146
  - README.md
147
+ - lib/lab42/state_machine.rb
148
+ - lib/lab42/state_machine/dsl.rb
149
+ - lib/lab42/state_machine/state.rb
150
+ - lib/lab42/state_machine/version.rb
67
151
  homepage: https://github.com/RobertDober/lab42_state_machine
68
152
  licenses:
69
- - MIT
153
+ - Apache
154
+ - '2'
70
155
  metadata: {}
71
156
  post_install_message:
72
157
  rdoc_options: []
@@ -74,18 +159,17 @@ require_paths:
74
159
  - lib
75
160
  required_ruby_version: !ruby/object:Gem::Requirement
76
161
  requirements:
77
- - - '>='
162
+ - - ">="
78
163
  - !ruby/object:Gem::Version
79
- version: 2.0.0
164
+ version: 2.7.0
80
165
  required_rubygems_version: !ruby/object:Gem::Requirement
81
166
  requirements:
82
- - - '>='
167
+ - - ">="
83
168
  - !ruby/object:Gem::Version
84
169
  version: '0'
85
170
  requirements: []
86
- rubyforge_project:
87
- rubygems_version: 2.1.0
171
+ rubygems_version: 3.1.2
88
172
  signing_key:
89
173
  specification_version: 4
90
- summary: A Simple State Machine
174
+ summary: Define triggers and transitios, what else?
91
175
  test_files: []
@@ -1,167 +0,0 @@
1
- require 'lab42/state_machine/controller/stream_api'
2
- module Lab42
3
- class StateMachine
4
- class Controller
5
-
6
- include StreamApi
7
-
8
- attr_reader :stream
9
-
10
- SkipState = Class.new RuntimeError
11
-
12
- def after blk=nil, &block
13
- @afters << mk_block(blk, block)
14
- end
15
-
16
- def before blk=nil, &block
17
- @befores << mk_block(blk, block)
18
- end
19
-
20
- def run input
21
- mk_input_stream input
22
- run_setups
23
- loop do
24
- run_one
25
- end
26
- run_teardowns
27
- end
28
-
29
- def run_afters
30
- run_state_blocks @afters
31
- end
32
-
33
- def run_befores
34
- run_state_blocks @befores
35
- end
36
-
37
- def run_one
38
- @current_input = stream.peek
39
- @stream = stream.drop 1
40
-
41
- run_befores
42
- run_states
43
- run_transitions
44
- run_afters
45
- rescue SkipState
46
- end
47
-
48
- def run_setup_blocks blox
49
- blox.each do | block |
50
- @sm.instance_exec( __peek__, &block )
51
- end
52
- end
53
-
54
- def run_state_blocks blox
55
- blox.each do | block |
56
- @sm.instance_exec( @current_input, @old_state, @state, &block )
57
- end
58
- end
59
-
60
- def run_setups
61
- @setups.each do | block |
62
- @sm.instance_exec( peek_first, &block )
63
- end
64
- rescue SkipState
65
- end
66
-
67
- def run_states
68
- run_state_blocks @handlers[@state]
69
- end
70
-
71
- def run_teardowns
72
- @teardowns.each do | teardown |
73
- @sm.instance_exec( @current_input, @state, &teardown )
74
- end
75
- rescue SkipState
76
- end
77
-
78
- def run_transitions
79
- run_state_blocks @transitions[current_transition]
80
- end
81
-
82
- def set_state! st
83
- @old_state = @state
84
- @state = st
85
- end
86
-
87
- def setup blk=nil, &block
88
- @setups << mk_block(blk, block)
89
- end
90
-
91
- def skip
92
- raise SkipState, "skipping actions in this setup/state/teardown"
93
- end
94
-
95
- def state *args, &block
96
- raise ArgumentError, "args.size = #{args.size} instead of 0..1" if args.size > 1
97
- return @state if block.nil? && args.empty?
98
- return set_state! args.first unless block
99
- @handlers[ args.first ] << block
100
- end
101
-
102
- def teardown blk=nil, &block
103
- @teardowns << mk_block(blk, block)
104
- end
105
-
106
- def transition trs_hshes, &block
107
- trs_hshes.each do | from, to |
108
- @transitions[[from, to]] << block
109
- end
110
- end
111
-
112
- private
113
- def current_transition; [@old_state, @state] end
114
-
115
- def inherit_from! old_controller
116
- raise ArgumentError, "not a controller #{old_controller}" unless self.class === old_controller
117
-
118
- %w{setups teardowns afters befores handlers transitions}.each do | handlers |
119
- instance_variable_set( "@#{handlers}",
120
- old_controller.instance_variable_get( "@#{handlers}" ).clone )
121
- end
122
- end
123
-
124
- def initialize state_machine
125
- @sm = state_machine
126
- @old_state = nil
127
- @state = :init
128
-
129
- @setups = []
130
- @teardowns = []
131
- @afters = []
132
- @befores = []
133
- @handlers = mk_ary_hash
134
- @transitions = mk_ary_hash
135
- end
136
-
137
- def mk_ary_hash; Hash.new{ |h,k| h[k] = [] } end
138
-
139
- def mk_block block, blk
140
- raise ArgumentError, "Need a block or a proc param" unless !!block != !!blk
141
- case b = block || blk
142
- when Proc, Method
143
- b
144
- when Symbol
145
- @sm.method b
146
- else
147
- raise ArgumentError, "Cannot create a proc or method from #{b}"
148
- end
149
- end
150
-
151
- def mk_input_stream input
152
- case input
153
- when Enumerable, Enumerator
154
- @original_stream = @stream = input.lazy
155
- else
156
- raise ArgumentError, "cannot enumerate on object #{input}"
157
- end
158
- end
159
-
160
- def peek_first
161
- @__peek_first__ ||= stream.peek
162
- rescue StopIteration
163
- nil
164
- end
165
- end # class Controller
166
- end # class StateMachine
167
- end # module Lab42
@@ -1,70 +0,0 @@
1
- require 'forwarder'
2
- module Lab42
3
- class StateMachine
4
- class Controller
5
- module StreamApi
6
-
7
- extend Forwarder
8
- forward_all :count, :take,:take_while, to: :stream
9
-
10
- class ::Proc
11
- def negated
12
- ->(*args, &blk){ !self.(*args, &blk) }
13
- end
14
- end
15
-
16
- def drop_until &condition
17
- @stream = stream.drop_while( &condition.negated )
18
- end
19
- def drop_until! &condition
20
- drop_until( &condition )
21
- @sm.skip
22
- end
23
-
24
- def drop_while &condition
25
- @stream = stream.drop_while( &condition )
26
- end
27
- def drop_while! &condition
28
- drop_while( &condition )
29
- @sm.skip
30
- end
31
-
32
- def has_next?
33
- stream.peek
34
- true
35
- rescue StopIteration
36
- false
37
- end
38
- def last?; !has_next? end
39
-
40
- def peek *args
41
- stream.peek
42
- rescue StopIteration
43
- args.first
44
- end
45
-
46
- def reject &filter
47
- @stream = @stream.reject( &filter )
48
- end
49
-
50
- def rewind
51
- @stream = @original_stream
52
- end
53
-
54
- def rewind!
55
- rewind
56
- @sm.skip
57
- end
58
-
59
- def select &filter
60
- @stream = @stream.select( &filter )
61
- end
62
-
63
- def take_until &condition
64
- take_while( &condition.negated )
65
- end
66
-
67
- end # module StreamApi
68
- end # class Controller
69
- end # class StateMachine
70
- end # module Lab42