machina 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d67cfd0648b85a02e58ce0f287c24a5f2a2424ff50ac949b1272f44885cb3f77
4
+ data.tar.gz: 341433286e327508944bd8fac79640b0be466288629fe02f4c296ba3f3ea01cd
5
+ SHA512:
6
+ metadata.gz: 8e34227ae96d02e0682e7c3a489d318baffcb95b91d6068b740010e4640f1f3bad6da62e03683ce259bfd8db02032aae4a23392d54c951140ee6fd185b2e24d3
7
+ data.tar.gz: edefe97cd25753f5d7458502cc625817b3204c0957e5351eb3a2ef9889dea8e72f22b0ac315ef521ccc8ba94b52aff0c7f28b2c3693a7ea0d428cc1c4ec3c371
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ machina (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ machina!
15
+
16
+ BUNDLED WITH
17
+ 1.17.2
data/Makefile ADDED
@@ -0,0 +1,13 @@
1
+ .PHONY: *
2
+
3
+ default: test
4
+
5
+ build:
6
+ rm *.gem
7
+ gem build machina.gemspec
8
+
9
+ publish: build
10
+ gem push *.gem
11
+
12
+ test:
13
+ ruby -Ilib:spec spec/*_spec.rb
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Machina
2
+
3
+ _a god introduced by means of a crane in ancient Greek and Roman drama to decide the final outcome_
4
+
5
+ ![](https://theatricaleffectsandstaging.files.wordpress.com/2016/05/untitled1.png?w=390&h=326)
6
+
7
+ ```ruby
8
+ fsm = Machina.new(:unconfirmed)
9
+
10
+ fsm.when[:welcome_email] = -> { send_welcome_email }
11
+ fsm.when[:enable_account] = -> (user) { user.enabled! }
12
+
13
+ fsm[:confirm] = {
14
+ :unconfirmed => [:welcome_email, :enable_account, :confirmed],
15
+ }
16
+
17
+ fsm.on[:confirm] = -> (user) { puts "Starting confirmation process for #{user.id}." }
18
+
19
+ fsm.state # => :unconfirmed
20
+ fsm.trigger!(:confirm, user)
21
+ fsm.state # => :confirmed
22
+ ```
data/lib/machina.rb ADDED
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Machina
4
+ InvalidEventError = Class.new(StandardError)
5
+ InvalidStateChangeError = Class.new(StandardError)
6
+
7
+ EVENT = -> (h, k) { h[k] = ->(*args) {} }
8
+
9
+ attr_reader :state, :events
10
+
11
+ # Initializes Machina
12
+ #
13
+ # @param initial_state [Symbol] the initial state of the state machine
14
+ # @return [Machina] the initialized Machina
15
+ #
16
+ def initialize(initial_state)
17
+ @state = initial_state
18
+ @events = Hash.new { |h, k| h[k] = [] }
19
+ end
20
+
21
+ # Triggers an event.
22
+ #
23
+ # @param event [Symbol] the event to trigger
24
+ # @param args [Array<Object>] the objects that the callback will receive
25
+ # @return [void]
26
+ #
27
+ def trigger(event, *args)
28
+ return unless trigger?(event)
29
+
30
+ Array(events[event][state]).each do |candidate_state|
31
+ begin
32
+ self.when[candidate_state].call(*args)
33
+ rescue StandardError
34
+ break
35
+ end
36
+
37
+ @state = candidate_state
38
+ end
39
+
40
+ on[event].call(*args)
41
+ end
42
+
43
+ # Triggers an event and raises on error.
44
+ #
45
+ # @param event [Symbol] the event to trigger
46
+ # @param args [Array<Object>] the objects that the callback will receive
47
+ # @return [void]
48
+ #
49
+ def trigger!(event, *args)
50
+ raise InvalidStateChangeError, "Event #{event} missing #{state} step" unless trigger?(event)
51
+
52
+ trigger(event, *args)
53
+ end
54
+
55
+ # Checks if an event can be triggered
56
+ #
57
+ # @param event [Symbol] the event to trigger
58
+ # @return [Boolean] the chance posibility
59
+ #
60
+ def trigger?(event)
61
+ raise InvalidEventError, "Event #{event} not found." unless events.key?(event)
62
+
63
+ events[event].key?(state)
64
+ end
65
+
66
+ # Defines the list of possible callbacks when an event is triggered
67
+ #
68
+ # @return [Hash]
69
+ #
70
+ def on
71
+ @on ||= Hash.new(&EVENT)
72
+ end
73
+
74
+ # Defines the list of possible callbacks when a state is reached
75
+ #
76
+ # @return [Hash]
77
+ #
78
+ def when
79
+ @when ||= Hash.new(&EVENT)
80
+ end
81
+
82
+ # Defines the list of possible events and their transitions
83
+ #
84
+ # @param key [Symbol] the event name
85
+ # @param states [Hash] the event transitions
86
+ # @return [void]
87
+ #
88
+ def []=(key, states)
89
+ events[key] = states
90
+ end
91
+ end
data/machina.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "machina"
5
+ s.version = "0.0.1"
6
+ s.summary = "Simple state machine"
7
+ s.description = "Really really simple state machine"
8
+ s.authors = ["elcuervo"]
9
+ s.licenses = %w[MIT HUGWARE]
10
+ s.email = ["elcuervo@elcuervo.net"]
11
+ s.homepage = "http://github.com/elcuervo/machina"
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files test`.split("\n")
14
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+
4
+ Post = Struct.new(:title, :notified, :published)
5
+
6
+ describe Machina do
7
+ attr_reader :fsm
8
+
9
+ before { @fsm = Machina.new(:start) }
10
+
11
+ it "basic" do
12
+ fsm[:begin] = { :start => :step1 }
13
+
14
+ assert_equal :start, fsm.state
15
+
16
+ fsm.trigger(:begin)
17
+
18
+ assert_equal :step1, fsm.state
19
+ end
20
+
21
+ it "complex" do
22
+ post = Post.new("Something HN worthy", false, false)
23
+ completed = false
24
+
25
+ fsm.when[:send_notification] = -> (post) { post.notified = true }
26
+ fsm.when[:published] = -> (post) { post.published = true }
27
+
28
+ fsm[:complete] = {
29
+ :start => [:review, :send_notification, :published],
30
+ :review => [:send_notification, :published],
31
+ :reset => :start
32
+ }
33
+
34
+ fsm.on[:complete] = -> (_post) { completed = true }
35
+
36
+ assert_equal :start, fsm.state
37
+ assert_equal false, completed
38
+ assert_equal false, post.notified
39
+ assert_equal false, post.published
40
+
41
+ fsm.trigger(:complete, post)
42
+
43
+ assert_equal :published, fsm.state
44
+ assert_equal true, completed
45
+ assert_equal true, post.notified
46
+ assert_equal true, post.published
47
+ end
48
+
49
+ it "stops on error" do
50
+ fsm.when[:stop] = -> { raise }
51
+
52
+ fsm[:act] = {
53
+ :start => [:a, :b, :stop, :final]
54
+ }
55
+
56
+ fsm.trigger(:act)
57
+
58
+ assert_equal :b, fsm.state
59
+ end
60
+
61
+ it "trigger!" do
62
+ fsm[:one] = {
63
+ :not_start => :reset
64
+ }
65
+
66
+ assert_raises(Machina::InvalidStateChangeError) { fsm.trigger!(:one) }
67
+ assert_raises(Machina::InvalidEventError) { fsm.trigger!(:two) }
68
+
69
+ assert_equal :start, fsm.state
70
+ end
71
+ end
@@ -0,0 +1,2 @@
1
+ require "minitest/autorun"
2
+ require "machina"
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: machina
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - elcuervo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Really really simple state machine
14
+ email:
15
+ - elcuervo@elcuervo.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - Gemfile
22
+ - Gemfile.lock
23
+ - Makefile
24
+ - README.md
25
+ - lib/machina.rb
26
+ - machina.gemspec
27
+ - spec/machina_spec.rb
28
+ - spec/spec_helper.rb
29
+ homepage: http://github.com/elcuervo/machina
30
+ licenses:
31
+ - MIT
32
+ - HUGWARE
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.0.3
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Simple state machine
53
+ test_files: []