finist 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.
- checksums.yaml +7 -0
- data/.gems +2 -0
- data/LICENSE +19 -0
- data/README.md +112 -0
- data/finist.gemspec +15 -0
- data/lib/finist.rb +53 -0
- data/makefile +4 -0
- data/test/all.rb +60 -0
- data/test/helper.rb +1 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ef4a139c70308a595e49e7ba7053a64631c33e0
|
4
|
+
data.tar.gz: 6a640ada556919739c4bc01de30d4aa3e9fba735
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5720d02f6093f0eeb9bd20161f7b7b617f70c20ded4a85d8305cb2f61e5e38c50e2b3c18ea33c225fb35841cc6d0f28b88bbe75dc5e409a4297698965bbbd11
|
7
|
+
data.tar.gz: 2cc188dd3d8a6d7fd58bdab1c9727765ca7ddbb346132e687364617ece39a41d5127df78382838a4e333736cbe32241892c7fd4f4ece7f7cf75085a2cd2f6cae
|
data/.gems
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 Michel Martens
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
Finist
|
2
|
+
======
|
3
|
+
|
4
|
+
Redis based Finite State Machine.
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Finist is a finite state machine that is defined and persisted in
|
10
|
+
[Redis][redis].
|
11
|
+
|
12
|
+
Community
|
13
|
+
---------
|
14
|
+
|
15
|
+
Meet us on IRC: [#lesscode](irc://chat.freenode.net/#lesscode) on
|
16
|
+
[freenode.net](http://freenode.net/).
|
17
|
+
|
18
|
+
Related projects
|
19
|
+
----------------
|
20
|
+
|
21
|
+
There's an [alternative implementation for Lua][finist.lua].
|
22
|
+
|
23
|
+
Getting started
|
24
|
+
---------------
|
25
|
+
|
26
|
+
Install [Redis][redis]. On most platforms it's as easy as grabbing
|
27
|
+
the sources, running make and then putting the `redis-server` binary
|
28
|
+
in the PATH.
|
29
|
+
|
30
|
+
Once you have it installed, you can execute `redis-server` and it
|
31
|
+
will run on `localhost:6379` by default. Check the `redis.conf`
|
32
|
+
file that comes with the sources if you want to change some settings.
|
33
|
+
|
34
|
+
Usage
|
35
|
+
-----
|
36
|
+
|
37
|
+
Finist requires a [Redic][redic] compatible client. To make things
|
38
|
+
easier, `redic` is listed as a runtime dependency so the examples
|
39
|
+
in this document will work.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require "finist"
|
43
|
+
|
44
|
+
# Initialize with a Redis client, the name of the machine and the
|
45
|
+
# initial state. In this example, the machine is called "order" and
|
46
|
+
# the initial status is "pending". The Redis client is connected to
|
47
|
+
# the default host (127.0.0.1:6379).
|
48
|
+
machine = Finist.new(Redic.new, "order", "pending")
|
49
|
+
|
50
|
+
# Available transitions are defined with the `on` method
|
51
|
+
# `machine.on(<event>, <initial_state>, <final_state>)`
|
52
|
+
machine.on("approve", "pending", "approved")
|
53
|
+
machine.on("cancel", "pending", "cancelled")
|
54
|
+
machine.on("cancel", "approved", "cancelled")
|
55
|
+
machine.on("reset", "cancelled", "pending")
|
56
|
+
```
|
57
|
+
|
58
|
+
Now that the possible transitions are defined, we can check the
|
59
|
+
current state:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
machine.state
|
63
|
+
# => "pending"
|
64
|
+
```
|
65
|
+
|
66
|
+
And we can trigger an event:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
machine.trigger("approve")
|
70
|
+
# => [true, "approved"]
|
71
|
+
```
|
72
|
+
|
73
|
+
The `trigger` method returns an array of two values: the first
|
74
|
+
represents whether a transition occurred, and the second represents
|
75
|
+
the current state.
|
76
|
+
|
77
|
+
Here's what happens if an event doesn't cause a transition:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
machine.trigger("reset")
|
81
|
+
# => [false, "approved"]
|
82
|
+
```
|
83
|
+
|
84
|
+
Here's a convenient way to use this flag:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
changed, state = machine.trigger("reset")
|
88
|
+
|
89
|
+
if changed
|
90
|
+
printf("State changed to %s\n", state)
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
If you need to remove all the transitions for a given event, you
|
95
|
+
can use `rm`:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
machine.rm("reset")
|
99
|
+
```
|
100
|
+
|
101
|
+
Note that every change is persisted in Redis.
|
102
|
+
|
103
|
+
Installation
|
104
|
+
------------
|
105
|
+
|
106
|
+
```
|
107
|
+
$ gem install finist
|
108
|
+
```
|
109
|
+
|
110
|
+
[redis]: http://redis.io
|
111
|
+
[redic]: https://github.com/amakawa/redic
|
112
|
+
[finist.lua]: https://github.com/soveran/finist.lua
|
data/finist.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "finist"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = %{Redis based Finite State Machine}
|
5
|
+
s.description = %Q{Finist is a finite state machine that is defined and persisted in Redis.}
|
6
|
+
s.authors = ["Michel Martens"]
|
7
|
+
s.email = ["michel@soveran.com"]
|
8
|
+
s.homepage = "https://github.com/soveran/finist"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency "redic"
|
14
|
+
s.add_development_dependency "cutest"
|
15
|
+
end
|
data/lib/finist.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "redic"
|
4
|
+
|
5
|
+
class Finist
|
6
|
+
SCRIPT = <<-EOS
|
7
|
+
local curr = redis.call("GET", KEYS[1])
|
8
|
+
local next = redis.call("HGET", KEYS[2], curr)
|
9
|
+
|
10
|
+
if next then
|
11
|
+
redis.call("SET", KEYS[1], next)
|
12
|
+
return { next, true }
|
13
|
+
else
|
14
|
+
return { curr, false }
|
15
|
+
end
|
16
|
+
EOS
|
17
|
+
|
18
|
+
def initialize(redis, name, init)
|
19
|
+
@name = sprintf("finist:%s", name)
|
20
|
+
@redis = redis
|
21
|
+
@redis.call("SET", @name, init, "NX")
|
22
|
+
end
|
23
|
+
|
24
|
+
def event_key(ev)
|
25
|
+
sprintf("%s:%s", @name, ev)
|
26
|
+
end
|
27
|
+
|
28
|
+
def on(ev, curr_state, next_state)
|
29
|
+
@redis.call("HSET", event_key(ev), curr_state, next_state)
|
30
|
+
end
|
31
|
+
|
32
|
+
def rm(ev)
|
33
|
+
@redis.call("DEL", event_key(ev))
|
34
|
+
end
|
35
|
+
|
36
|
+
def state
|
37
|
+
@redis.call("GET", @name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_event(ev)
|
41
|
+
@redis.call("EVAL", SCRIPT, "2", @name, event_key(ev))
|
42
|
+
end
|
43
|
+
|
44
|
+
def trigger(ev)
|
45
|
+
result = send_event(ev)
|
46
|
+
|
47
|
+
if result[1]
|
48
|
+
return true, result[0]
|
49
|
+
else
|
50
|
+
return false, result[0]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/makefile
ADDED
data/test/all.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
test do
|
4
|
+
c = Redic.new
|
5
|
+
|
6
|
+
fsm = Finist.new(c, "myfsm", "pending")
|
7
|
+
|
8
|
+
fsm.on("approve", "pending", "approved")
|
9
|
+
fsm.on("cancel", "pending", "cancelled")
|
10
|
+
fsm.on("cancel", "approved", "cancelled")
|
11
|
+
fsm.on("reset", "cancelled", "pending")
|
12
|
+
|
13
|
+
# Verify initial state
|
14
|
+
assert_equal("pending", fsm.state)
|
15
|
+
|
16
|
+
# Send an event
|
17
|
+
fsm.trigger("approve")
|
18
|
+
|
19
|
+
# Verify transition to "approved"
|
20
|
+
assert_equal("approved", fsm.state)
|
21
|
+
|
22
|
+
# Send an event
|
23
|
+
fsm.trigger("cancel")
|
24
|
+
|
25
|
+
# Verify transition to "cancelled"
|
26
|
+
assert_equal("cancelled", fsm.state)
|
27
|
+
|
28
|
+
# Send an event
|
29
|
+
fsm.trigger("approve")
|
30
|
+
|
31
|
+
# Verify state remains as "cancelled"
|
32
|
+
assert_equal("cancelled", fsm.state)
|
33
|
+
|
34
|
+
# Create a different fsm with client
|
35
|
+
fsm2 = Finist.new(c, "myfsm", "pending")
|
36
|
+
|
37
|
+
# Verify state remains as "cancelled"
|
38
|
+
assert_equal("cancelled", fsm2.state)
|
39
|
+
|
40
|
+
# A successful event returns true
|
41
|
+
changed, state = fsm.trigger("reset")
|
42
|
+
|
43
|
+
assert_equal(true, changed)
|
44
|
+
assert_equal("pending", state)
|
45
|
+
|
46
|
+
# An unsuccessful event returns false
|
47
|
+
changed, state = fsm.trigger("reset")
|
48
|
+
|
49
|
+
assert_equal(false, changed)
|
50
|
+
assert_equal("pending", state)
|
51
|
+
|
52
|
+
# Delete an event
|
53
|
+
fsm.rm("approve")
|
54
|
+
|
55
|
+
# Non existent events return false
|
56
|
+
changed, state = fsm.trigger("approve")
|
57
|
+
|
58
|
+
assert_equal(false, changed)
|
59
|
+
assert_equal("pending", state)
|
60
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "../lib/finist"
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: finist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michel Martens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redic
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cutest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Finist is a finite state machine that is defined and persisted in Redis.
|
42
|
+
email:
|
43
|
+
- michel@soveran.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gems
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- finist.gemspec
|
52
|
+
- lib/finist.rb
|
53
|
+
- makefile
|
54
|
+
- test/all.rb
|
55
|
+
- test/helper.rb
|
56
|
+
homepage: https://github.com/soveran/finist
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.0.14
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Redis based Finite State Machine
|
80
|
+
test_files: []
|