SFP Parser and Planner for Ruby =============================== - Author: Herry (herry13@gmail.com) - Last update: 06 May 2013 - Version: 0.1.3 - License: [BSD License](https://github.com/herry13/sfp-ruby/blob/master/LICENSE) A Ruby gem that provides a Ruby interface to parse SFP language. It also provides a Ruby interface for a solver to generate a workflow for a planning task written in SFP language. For execution, it provides two execution framework: - Bash - there is no representation of SFP schema or object - a SFP procedure is a Bash script file - SFP namespaces are represented by subdirectories of *modules* directory - Ruby ( *under-development* ) - a SFP schema is represented by a Ruby class - a SFP object is an instance of Ruby class - a SFP procedure is a Ruby method - SFP namespaces are represented by a tree of Hash data structure This is a spin-out project from [Nuri](https://github.com/herry13/nuri). To install ---------- $ gem install sfp Requirements ------------ - Rubygems - antlr3 - json Supporting Platforms -------------------- - Linux (x86, arm) - MacOS X To use as a command line to solve a planning task ------------------------------------------------- - parse a SFP file, and then print the output in JSON $ sfp -p - solve a planning task, and then print the plan in JSON $ sfp The planning task must be written in [SFP language](https://github.com/herry13/nuri/wiki/SFP-language). To use as Ruby library ---------------------- - include file **main.sfp** in your codes: require 'sfp' - to parse an SFP file: create a Sfp::Parser object, and then pass the content of the file: # Determine the home directory of your SFP file. home_dir = File.expand_path(File.dirname("my_file.sfp")) # Create Sfp::Parser object; pass the home and root directory so the parser # can find the included files. parser = Sfp::Parser.new({:home_dir => home_dir, :root_dir => '/'}) # Parse the file. parser.parse(File.read("my_file.sfp")) # Get the result in Hash data structure result = parser.root - to solve a planning task: create a Sfp::Planner object, and then pass the file's path: # Create Sfp::Planner object. planner = Sfp::Planner.new # Solve a planning task written in "my_file.sfp", then print # the result in JSON. puts planner.solve({:file => "my_file.sfp", :json => true}) To solve planning task and execute it using Bash scripts -------------------------------------------------------- - Parse a SFP file, print the plan in JSON, and then execute the plan by invoking Bash scripts the current directory or as specified in environment variable SFP_HOME. *Note*: the namespaces are represented by directories e.g. executing procedure *$.a.b.foo* will be invoking a Bash script *./a/b/foo* (or *$SFP_HOME/a/b/foo*). $ sfp --exec-bash Example ------- - Create file **types.sfp** that holds required schemas: schema Service { running is false procedure start { conditions { this.running is false } effects { this.running is true } } procedure stop { conditions { this.running is true } effects { this.running is false } } } schema Client { refer isref Service procedure redirect(s isref Service) { conditions { } effects { this.refer is s } } } In this file, we have two schemas that model our domain. First, schema **Service** with an attribute **running*, procedure **start** that changes **running**'s value from **false** to *true*, and procedure **stop** that changes **running**'s value from **true** to **false**. We also have schema **Client** with an attribute **refer**, which is a reference to an instance of **Service**. There is a procedure **redirect** that changes the value of **refer** with any instance if **Service**. - Create file **task.sfp** that holds the task: include "types.sfp" initial state { a isa Service { running is true } b isa Service // with "running" is false pc isa Client { refer is a } } goal constraint { pc.refer is b a.running is false } global constraint { pc.refer.running is true } In this file, we specify a task where in the initial state of our domain, we have two services **a** and **b**, and a client **pc**. **a** is running, **b** is stopped, and **pc** is referring to **a**. We want to generate a workflow that achieves goal: **pc** is referring to **b** and **a** is stopped, and preserves global constraint: **pc** is always referring to a running service. - To generate the workflow, we invoke **sfp** command with argument the path of the task file: $ sfp task.sfp Which will generate a workflow in JSON { "type": "sequential", "workflow": [ { "name": "$.b.start", "parameters": { }, "condition": { "$.b.running": false }, "effect": { "$.b.running": true } }, { "name": "$.pc.redirect", "parameters": { "$.s": "$.b" }, "condition": { }, "effect": { "$.pc.refer": "$.b" } }, { "name": "$.a.stop", "parameters": { }, "condition": { "$.a.running": true }, "effect": { "$.a.running": false } } ], "version": "1", "total": 3 } This workflow is sequential that has 3 procedures. If you executes the workflow in given order, it will achieves the goal state as well as perserves the global constraints during the changes. - To generate and execute the plan using Bash framework, we invoke **sfp** command with an option *--exec-bash* and with an argument the path of the task file: $ sfp --exec-bash task,sfp It will generate and execute the plan by invoking the Bash scripts in the current directory (or as specified in environment variable SFP_HOME) in the following sequence: 1. *modules/b/start* 2. *modules/pc/redirect "$.b"* 3. *modules/a/stop* About Nuri ---------- [Nuri](https://github.com/herry13/nuri) is a configuration tool that binds these procedures with particular shell commands and Ruby objects in order to implement the required configuration changes based on the goal and global constraints given by the user.