finity 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.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Martin Donath
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation files
5
+ (the "Software"), to deal in the Software without restriction,
6
+ including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software,
8
+ and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ 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
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Finity
2
+
3
+ **Finity** tries to be an extremly lightweight state machine implementation with an easily readable syntax
4
+ which is essential if you have tens or hundreds of transitions. It is inspired by [transitions][],
5
+ a great state machine implementation tightly integrated with ActiveRecord by Jakub Kuźma and Timo Rößner.
6
+
7
+ [https://github.com/troessner/transitions][transitions]
8
+
9
+ The goal of **Finity** is to provide a state machine implementation which is as slim and fast as possible
10
+ while maintaining a beautiful and readable syntax. If you need ActiveModel/ActiveRecord integration,
11
+ [transitions][] is your weapon of choice. However, if you only need a plain state machine implementation
12
+ which is optimized for readability and efficiency, give **Finity** a spin.
13
+
14
+ ## Installation
15
+
16
+ If you use Rails, include this into your Gemfile and run `bundle install` via command line:
17
+
18
+ ```
19
+ gem 'finity'
20
+ ```
21
+
22
+ If you're not using Rails, you can install **Finity** with `gem` via command line:
23
+
24
+ ```
25
+ gem install finity
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ **Finity** can transform any class into a state machine. The only thing you have to do is to include it
31
+ and define some transitions. For example, consider a state machine modelling the different states of reading
32
+ the contents of a file:
33
+
34
+ ```
35
+ class Readfile
36
+ include Finity
37
+
38
+ finity :init => :opened do
39
+
40
+ state :opened,
41
+ :enter => proc { @file = File.open '...' }
42
+
43
+ state :reading,
44
+ :enter => proc { process @file.readline }
45
+
46
+ state :closed,
47
+ :enter => proc { @file.close '...' }
48
+
49
+ event :read do
50
+ transitions :from => [:opened, :reading], :to => :reading,
51
+ :if => proc { not @file.eof? }
52
+ :do => proc { log 'Reading next line of file' }
53
+
54
+ transitions :from => [:opened, :reading], :to => :reading,
55
+ :do => proc { log 'Reached end of file' }
56
+ end
57
+
58
+ event :close do
59
+ transitions :from => [:opened, :reading], :to => :closed,
60
+ :do => proc { log 'Closing file handle' }
61
+ end
62
+ end
63
+ end
64
+ ```
65
+
66
+ ## States
67
+
68
+ A state is defined by its name and can define transition functions upon entering and leaving the state.
69
+ These functions can be either referenced as Symbols, Strings, Procs or Lambda:
70
+
71
+ ```
72
+ state :some_state,
73
+ :enter => proc { do_something and some_other_thing },
74
+ :leave => :execute_leave_action!
75
+ ```
76
+
77
+ ## Events and Transitions
78
+
79
+ Events are like states defined by their names and can trigger several transitions from several states
80
+ to other states. The transitions are evaluated in the order they are defined. If a valid transition is
81
+ found, the execution is stopped and the transition performed:
82
+
83
+ ```
84
+ event :some_event do
85
+ transitions :from => [:some_state, :another_state], :to => :another_state,
86
+ :if => proc { is_some_condition_true? },
87
+ :do => :execute_something_upon_transition
88
+
89
+ transitions :from => [:some_state], :to => :another_state,
90
+ :do => :execute_something_else
91
+ end
92
+ ```
93
+
94
+ Transitions can be guarded by decision functions (`:if`) and execute another function upon successful
95
+ matching (`:do`). Transitions are triggered by the method `event!` which is defined for the including
96
+ object. Many other state machine implementations define one method for each event and for each state,
97
+ however, **Finity** tries to be as minimally invasive as possible:
98
+
99
+ ```
100
+ object = SomeClassIncludingFinity.new
101
+ if object.state? :some_state
102
+ object.event! :some_event
103
+ end
104
+ ```
105
+
106
+ [transitions]: https://github.com/troessner/transitions
data/finity.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/finity/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'finity'
6
+ s.version = Finity::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Martin Donath']
9
+ s.email = 'md@struct.cc'
10
+ s.homepage = 'http://github.com/squidfunk/finity'
11
+ s.summary = 'Slim and more readable state machine for Ruby'
12
+ s.description = 'Extremly lightweight implementation with an easily readable syntax, ' +
13
+ 'which is essential if you have tens or hundreds of transitions'
14
+
15
+ s.required_rubygems_version = '>= 1.3.6'
16
+ s.add_development_dependency 'bundler', '~> 1'
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
20
+ s.require_path = 'lib'
21
+ end
data/lib/finity.rb ADDED
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2012 Martin Donath
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # 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
21
+ # SOFTWARE.
22
+
23
+ require 'finity/event'
24
+ require 'finity/machine'
25
+ require 'finity/state'
26
+ require 'finity/transition'
27
+ require 'finity/version'
28
+
29
+ module Finity
30
+ class InvalidCallback < StandardError; end
31
+ class MissingCallback < StandardError; end
32
+ class InvalidState < StandardError; end
33
+
34
+ # Class methods to be injected into the including class upon inclusion.
35
+ module ClassMethods
36
+ attr_accessor :machine
37
+
38
+ # When inheriting this module, pass the registered machines to the
39
+ # inheriting class after executing potential parent inheritance logic.
40
+ def inherited klass
41
+ super and klass.machine = machine
42
+ end
43
+
44
+ # Instantiate a new state machine for the including class by accepting a
45
+ # block with state and event (and subsequent transition) definitions.
46
+ def finity options = {}, &block
47
+ @machine = Machine.new self, options, &block
48
+ end
49
+
50
+ # Return the names of all registered states.
51
+ def states
52
+ @machine.states.map { |name, _| name }
53
+ end
54
+
55
+ # Return the names of all registered events.
56
+ def events
57
+ @machine.events.map { |name, _| name }
58
+ end
59
+ end
60
+
61
+ # Inject methods into the including class upon inclusion.
62
+ def self.included base
63
+ base.extend ClassMethods
64
+ end
65
+ end
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2012 Martin Donath
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # 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
21
+ # SOFTWARE.
22
+
23
+ module Finity
24
+ class Event
25
+ attr_accessor :name
26
+
27
+ # Initialize a new event and execute the block which holds the
28
+ # transition definitions.
29
+ def initialize name, options = {}, &block
30
+ @name, @transitions = name, {}
31
+ instance_eval &block if block_given?
32
+ end
33
+
34
+ # Add a transition to the event.
35
+ def transitions options = {}
36
+ transition = Transition.new options
37
+ [options[:from]].flatten.each do |from|
38
+ @transitions[from] ||= []
39
+ @transitions[from] << transition
40
+ end
41
+ end
42
+
43
+ # Handle the current state and execute the first allowed transition.
44
+ def handle object, state
45
+ @transitions[state.name].find do |transition|
46
+ name = transition.handle object
47
+ return name unless name.nil?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2012 Martin Donath
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # 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
21
+ # SOFTWARE.
22
+
23
+ module Finity
24
+ class Machine
25
+ attr_accessor :states, :events, :current
26
+
27
+ # Initialize a new state machine within the provided class and define
28
+ # methods for querying the current state and initiating transitions.
29
+ def initialize klass, options = {}, &block
30
+ @klass, @states, @events, @init = klass, {}, {}, options.delete(:init)
31
+ @klass.send :define_method, :event! do |*args|
32
+ klass.machine.update self, *args
33
+ end
34
+ @klass.send :define_method, :state? do |*args|
35
+ klass.machine.current.name.eql? *args
36
+ end
37
+ instance_eval &block if block_given?
38
+ end
39
+
40
+ # Return the name of the initial state.
41
+ def init
42
+ @init ||= @states.keys.first unless @states.first.nil?
43
+ end
44
+
45
+ # Register a state.
46
+ def state name, options = {}
47
+ @states[name] = State.new name, options
48
+ end
49
+
50
+ # Register an event and evaluate the block for transitions.
51
+ def event name, options = {}, &block
52
+ @events[name] = Event.new name, options, &block
53
+ end
54
+
55
+ # An event occured, so update the state machine by evaluating the
56
+ # transition functions and notify the left and entered state.
57
+ def update object, event
58
+ @current ||= @states[init]
59
+ if (state = @events[event].handle object, @current)
60
+ if @states[state].nil?
61
+ raise InvalidState, "Invalid state '#{state}'"
62
+ end
63
+ @current.leave object
64
+ @current = @states[state]
65
+ @current.enter object
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2012 Martin Donath
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # 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
21
+ # SOFTWARE.
22
+
23
+ module Finity
24
+ class State
25
+ attr_accessor :name
26
+
27
+ # Initialize a new state for the state machine with callbacks.
28
+ def initialize name, options = {}
29
+ @name, @enter, @leave = name, *options.values_at(:enter, :leave)
30
+ instance_eval &block if block_given?
31
+ end
32
+
33
+ # Executed when the current state is entered.
34
+ def enter object
35
+ execute object, @enter unless @enter.nil?
36
+ end
37
+
38
+ # Executed when the current state is left.
39
+ def leave object
40
+ execute object, @leave unless @leave.nil?
41
+ end
42
+
43
+ private
44
+
45
+ # Internal method to execute a provided action on a given object. The
46
+ # action can be a symbol, string, proc or lambda function.
47
+ def execute object, action
48
+ case action
49
+ when Symbol, String
50
+ object.send action
51
+ when Proc
52
+ action.lambda? and action.call object or object.instance_eval &action
53
+ else
54
+ raise InvalidCallback, 'Only symbols, strings, procs and lambdas may ' +
55
+ 'be passed as callbacks'
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2012 Martin Donath
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # 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
21
+ # SOFTWARE.
22
+
23
+ module Finity
24
+ class Transition
25
+
26
+ # A transition must define at least one original state (:from) and
27
+ # a state it transitions to (:to).
28
+ def initialize options
29
+ @from, @to, @if, @do = options.values_at(:from, :to, :if, :do)
30
+ if @from.nil? or @to.nil?
31
+ raise MissingCallback, 'A transition demands states at least one original ' +
32
+ 'state (:from) and a state it transitions to (:to)'
33
+ end
34
+ end
35
+
36
+ # Check, whether the current transition is allowed and execute it.
37
+ def handle object
38
+ if @if.nil? or execute object, @if
39
+ execute object, @do unless @do.nil?
40
+ @to
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Internal method to execute a provided action on a given object. The
47
+ # action can be a symbol, string, proc or lambda function.
48
+ def execute object, action
49
+ case action
50
+ when Symbol, String
51
+ object.send action
52
+ when Proc
53
+ action.lambda? and action.call object or object.instance_eval &action
54
+ else
55
+ raise InvalidCallback, 'Only symbols, strings, procs and lambdas may ' +
56
+ 'be passed as callbacks'
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module Finity
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: finity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Martin Donath
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1'
30
+ description: Extremly lightweight implementation with an easily readable syntax, which
31
+ is essential if you have tens or hundreds of transitions
32
+ email: md@struct.cc
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - Gemfile
38
+ - LICENSE
39
+ - README.md
40
+ - finity.gemspec
41
+ - lib/finity.rb
42
+ - lib/finity/event.rb
43
+ - lib/finity/machine.rb
44
+ - lib/finity/state.rb
45
+ - lib/finity/transition.rb
46
+ - lib/finity/version.rb
47
+ homepage: http://github.com/squidfunk/finity
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.6
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.24
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Slim and more readable state machine for Ruby
71
+ test_files: []