├── .gitignore ├── Gemfile ├── LICENSE ├── LICENSE.txt ├── README.md ├── Rakefile ├── aasm_graph.gemspec ├── bin └── aasm_graph ├── example └── job.rb ├── job.jpg ├── lib └── aasm │ ├── graph.rb │ ├── graph │ ├── cli.rb │ └── version.rb │ └── tasks │ └── aasm_graph.rake ├── test ├── aasm │ └── graph │ │ └── cli_test.rb ├── fixtures │ └── job.rb └── test_helper.rb └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | Gemfile.lock 31 | .ruby-version 32 | .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in aasm_graph.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 aasm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ivan Tse 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AASM::Graph 2 | 3 | Add-on gem for creating graphs from AASM state machine definitions 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'aasm_graph' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install aasm_graph 20 | 21 | ## Usage 22 | 23 | You can use it via the command line interface: 24 | ``` 25 | ./bin/aasm_graph -Iexample -rjob Job 26 | ``` 27 | Or you can use it with Rake. In your `Rakefile`, load the `aasm_graph` task file: 28 | ``` 29 | load "aasm/tasks/aasm_graph.rake" 30 | ``` 31 | And then you can run the rake task: 32 | ``` 33 | rake aasm_graph INCLUDE=./example REQUIRE=job CLASS=Job 34 | ``` 35 | 36 | ## Contributing 37 | 38 | 1. Fork it ( https://github.com/aasm/aasm_graph/fork ) 39 | 2. Create your feature branch (`git checkout -b my-new-feature`) 40 | 3. Commit your changes (`git commit -am 'Add some feature'`) 41 | 4. Push to the branch (`git push origin my-new-feature`) 42 | 5. Create a new Pull Request 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | load "./lib/aasm/tasks/aasm_graph.rake" 3 | 4 | -------------------------------------------------------------------------------- /aasm_graph.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'aasm/graph/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "aasm_graph" 8 | spec.version = AASM::Graph::VERSION 9 | spec.authors = ["Ivan Tse", "Esteban Pastorino"] 10 | spec.email = ["ivan.tse1@gmail.com", "ejpastorino@gmail.com"] 11 | spec.summary = %q{Add-on gem for creating graphs from AASM state machine definitions} 12 | spec.description = %q{Add-on gem for creating graphs from AASM state machine definitions} 13 | spec.homepage = "https://github.com/aasm/aasm_graph" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "aasm", "~> 4.1.0" 22 | 23 | spec.add_development_dependency "bundler", "~> 1.7" 24 | spec.add_development_dependency "rake", "~> 10.0" 25 | end 26 | -------------------------------------------------------------------------------- /bin/aasm_graph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "optparse" 4 | require_relative "../lib/aasm/graph/cli" 5 | 6 | options = { 7 | class_names: [] 8 | } 9 | 10 | optparse = OptionParser.new do |opts| 11 | opts.banner = "Usage: aasm_graph [options] ClassName1 ClassName2 ..." 12 | opts.on( '-I', '--include PATH', 'Include paths in the load path' ) do |path| 13 | paths = path.split(':') 14 | paths.each do |path| 15 | $:.unshift(path) 16 | end 17 | end 18 | 19 | opts.on('-R', '--require FILE', 'Require files') do |path| 20 | require path 21 | end 22 | end 23 | optparse.parse! 24 | ARGV.each do |name| 25 | options[:class_names] << name 26 | end 27 | 28 | AASM::Graph::CLI.new(options).run 29 | -------------------------------------------------------------------------------- /example/job.rb: -------------------------------------------------------------------------------- 1 | require 'aasm' 2 | class Job 3 | include AASM 4 | 5 | aasm do 6 | state :sleeping, :initial => true 7 | state :running 8 | state :cleaning 9 | 10 | event :run do 11 | transitions :from => :sleeping, :to => :running 12 | end 13 | 14 | event :clean do 15 | transitions :from => :running, :to => :cleaning 16 | end 17 | 18 | event :sleep do 19 | transitions :from => [:running, :cleaning], :to => :sleeping 20 | end 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /job.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aasm/aasm_graph/89d776da36f291b325bdec0a02cbf79ff8746e92/job.jpg -------------------------------------------------------------------------------- /lib/aasm/graph.rb: -------------------------------------------------------------------------------- 1 | require "aasm/graph/version" 2 | require "aasm/graph/cli" 3 | -------------------------------------------------------------------------------- /lib/aasm/graph/cli.rb: -------------------------------------------------------------------------------- 1 | module AASM 2 | module Graph 3 | class CLI 4 | 5 | def initialize(options) 6 | @class_names = Array(options[:class_names]) 7 | @output_path = options[:output_path] 8 | end 9 | 10 | def run 11 | @class_names.each do |name| 12 | # This needs to handle namespace class names 13 | klass = Object.const_get(name) 14 | edges = "" 15 | if initial = klass.aasm.initial_state 16 | edges << "initial [shape=point];\n" 17 | edges << "initial -> #{initial};\n" 18 | end 19 | klass.aasm.events.each do |event| 20 | event.transitions.each do |transition| 21 | edges << "#{transition.from} -> #{transition.to} [ label = \"#{event.name}\" ];\n" 22 | end 23 | end 24 | 25 | `echo "#{dot_notation(edges)}" | dot -Tjpg -o #{file_path(name)}` unless edges.empty? 26 | end 27 | end 28 | 29 | private 30 | 31 | def file_path(name) 32 | File.join(@output_path, "#{name.downcase}.jpg") 33 | end 34 | 35 | def dot_notation(edges) 36 | <<-DOT 37 | digraph cronjob { 38 | rankdir=LR; /* This should be configurable */ 39 | node [shape = circle]; 40 | #{edges} 41 | } 42 | DOT 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/aasm/graph/version.rb: -------------------------------------------------------------------------------- 1 | module AASM 2 | module Graph 3 | VERSION = "0.0.1" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/aasm/tasks/aasm_graph.rake: -------------------------------------------------------------------------------- 1 | require_relative "../graph/cli" 2 | 3 | desc "Generate a graph of your state machine" 4 | task :aasm_graph do 5 | if defined?(Rails) 6 | Rake::Task["environment"].invoke 7 | else 8 | paths = ENV["INCLUDE"].to_s 9 | paths.split(':').each do |path| 10 | $:.unshift(path) 11 | end 12 | 13 | requires = ENV["REQUIRE"].to_s 14 | requires.split(",").each do |file| 15 | require file 16 | end 17 | end 18 | 19 | options = {} 20 | 21 | options[:class_names] = ENV["CLASS"].split(",") 22 | 23 | AASM::Graph::CLI.new(options).run 24 | end 25 | -------------------------------------------------------------------------------- /test/aasm/graph/cli_test.rb: -------------------------------------------------------------------------------- 1 | require_relative "../../test_helper" 2 | 3 | class CLITest < Minitest::Test 4 | 5 | def test_graph_file_is_created 6 | require 'job' 7 | Dir.mktmpdir do |dir| 8 | file = File.join(dir, 'job.jpg') 9 | AASM::Graph::CLI.new( 10 | class_names: ['Job'], 11 | output_path: dir 12 | ).run 13 | assert File.exists?(file), "File at #{file} doesn't exist" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/fixtures/job.rb: -------------------------------------------------------------------------------- 1 | require 'aasm' 2 | class Job 3 | include AASM 4 | 5 | aasm do 6 | state :sleeping, :initial => true 7 | state :running 8 | state :cleaning 9 | 10 | event :run do 11 | transitions :from => :sleeping, :to => :running 12 | end 13 | 14 | event :clean do 15 | transitions :from => :running, :to => :cleaning 16 | end 17 | 18 | event :sleep do 19 | transitions :from => [:running, :cleaning], :to => :sleeping 20 | end 21 | end 22 | 23 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.join(File.dirname(__FILE__), '../lib')) 2 | $:.unshift(File.join(File.dirname(__FILE__), 'fixtures')) 3 | require "minitest/autorun" 4 | require "aasm/graph" -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | - add tests 2 | - do not use ruby-graphviz gem, just call via command line (add error messages if not installed) 3 | - output .dot notation to put into graphviz d3 4 | - add more output like conditions or callbacks 5 | - ascii art? 6 | - double circle for final nodes? 7 | - more output formats 8 | - TD vs LR 9 | - better/helpful error messages 10 | - how to namespace objects (copy from active_support) 11 | - support multiple versions aasm 12 | - travis 13 | - fix require_relative --------------------------------------------------------------------------------