souffle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,30 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "yajl-ruby", "~> 1.1.0"
7
+ gem "eventmachine", "~> 0.12.10"
8
+ gem "amqp", "~> 0.9.7"
9
+ gem "state_machine", "~> 1.1.2"
10
+
11
+ gem "right_aws", "~> 3.0.4"
12
+
13
+ gem "mixlib-cli"
14
+ gem "mixlib-config", ">= 1.1.0"
15
+ gem "mixlib-log", ">= 1.3.0"
16
+
17
+ # Add dependencies to develop your gem here.
18
+ # Include everything needed to run rake, tests, features, etc.
19
+ group :development do
20
+ gem "rspec", "~> 2.10.0"
21
+ gem "fakefs", "~> 0.4.0"
22
+ gem "yard", "~> 0.8"
23
+ gem "redcarpet", "~> 2.1.1"
24
+ gem "rdoc", "~> 3.12"
25
+ gem "cucumber", ">= 0"
26
+ gem "bundler", "~> 1.1.0"
27
+ gem "jeweler", "~> 1.8.3"
28
+ gem "ruby-graphviz", :require => "graphviz"
29
+ gem (RUBY_VERSION =~ /^1\.9/ ? "simplecov" : "rcov"), ">= 0"
30
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Josh Toft
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ 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 BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # souffle
2
+
3
+ An orchestrator for setting up isolated chef-managed systems.
4
+
5
+ ## Contributing to souffle
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ ## Copyright
16
+
17
+ Copyright (c) 2012 Josh Toft. See LICENSE.txt for
18
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
15
+ require 'jeweler'
16
+ require 'souffle'
17
+ Jeweler::Tasks.new do |gem|
18
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
19
+ gem.name = "souffle"
20
+ gem.homepage = "http://github.com/seryl/souffle"
21
+ gem.license = "MIT"
22
+ gem.summary = %Q{An orchestrator to create entire chef environments}
23
+ gem.description = %Q{An orchestrator to create entire chef environments}
24
+ gem.email = "joshtoft@gmail.com"
25
+ gem.authors = ["Josh Toft"]
26
+ gem.version = Souffle::VERSION
27
+ # dependencies defined in Gemfile
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rspec/core'
32
+ require 'rspec/core/rake_task'
33
+ RSpec::Core::RakeTask.new(:spec) do |spec|
34
+ spec.pattern = FileList['spec/**/*_spec.rb']
35
+ end
36
+
37
+ if RUBY_VERSION =~ /^1\.9/
38
+ desc "Code coverage detail"
39
+ task :simplecov do
40
+ ENV['COVERAGE'] = "true"
41
+ Rake::Task['spec'].execute
42
+ end
43
+ else
44
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
45
+ spec.pattern = 'spec/**/*_spec.rb'
46
+ spec.rcov = true
47
+ end
48
+ end
49
+
50
+ require 'cucumber/rake/task'
51
+ Cucumber::Rake::Task.new(:features)
52
+
53
+ task :default => :spec
54
+
55
+ require 'yard'
56
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'souffle'
5
+ require 'souffle/application/souffle-server'
6
+
7
+ Souffle::Application::Server.new.run
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'souffle'
5
+ require 'souffle/application/souffle-worker'
6
+
7
+ Souffle::Application::Worker.new.run
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,13 @@
1
+ require 'bundler'
2
+ begin
3
+ Bundler.setup(:default, :development)
4
+ rescue Bundler::BundlerError => e
5
+ $stderr.puts e.message
6
+ $stderr.puts "Run `bundle install` to install missing gems"
7
+ exit e.status_code
8
+ end
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
11
+ require 'souffle'
12
+
13
+ require 'rspec/expectations'
@@ -0,0 +1,46 @@
1
+ require 'souffle/application'
2
+ require 'souffle/server'
3
+
4
+ # The souffle server command line parser.
5
+ class Souffle::Application::Server < Souffle::Application
6
+
7
+ option :config_file,
8
+ :short => "-c CONFIG",
9
+ :long => "--config CONFIG",
10
+ :default => "/etc/souffle/worker.rb",
11
+ :description => "The configuration file to use"
12
+
13
+ option :log_level,
14
+ :short => "-l LEVEL",
15
+ :long => "--log_level LEVEL",
16
+ :description => "Set the log level (debug, info, warn, error, fatal)",
17
+ :proc => lambda { |l| l.to_sym }
18
+
19
+ option :log_location,
20
+ :short => "-L LOGLOCATION",
21
+ :long => "--logfile LOGLOCATION",
22
+ :description => "Set the log file location, defaults to STDOUT",
23
+ :proc => nil
24
+
25
+ option :help,
26
+ :short => "-h",
27
+ :long => "--help",
28
+ :description => "Show this message",
29
+ :on => :tail,
30
+ :boolean => true,
31
+ :show_options => true,
32
+ :exit => 0
33
+
34
+ option :version,
35
+ :short => "-v",
36
+ :long => "--version",
37
+ :description => "Show souffle version",
38
+ :boolean => true,
39
+ :proc => lambda { |v| puts "Souffle: #{::Souffle::VERSION}"},
40
+ :exit => 0
41
+
42
+ def initialize
43
+ super
44
+
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ require 'souffle/application'
2
+ require 'souffle/worker'
3
+
4
+ # The souffle worker command line parser.
5
+ class Souffle::Application::Worker < Souffle::Application
6
+
7
+ option :config_file,
8
+ :short => "-c CONFIG",
9
+ :long => "--config CONFIG",
10
+ :default => "/etc/souffle/worker.rb",
11
+ :description => "The configuration file to use"
12
+
13
+ option :log_level,
14
+ :short => "-l LEVEL",
15
+ :long => "--log_level LEVEL",
16
+ :description => "Set the log level (debug, info, warn, error, fatal)",
17
+ :proc => lambda { |l| l.to_sym }
18
+
19
+ option :log_location,
20
+ :short => "-L LOGLOCATION",
21
+ :long => "--logfile LOGLOCATION",
22
+ :description => "Set the log file location, defaults to STDOUT",
23
+ :proc => nil
24
+
25
+ option :help,
26
+ :short => "-h",
27
+ :long => "--help",
28
+ :description => "Show this message",
29
+ :on => :tail,
30
+ :boolean => true,
31
+ :show_options => true,
32
+ :exit => 0
33
+
34
+ option :version,
35
+ :short => "-v",
36
+ :long => "--version",
37
+ :description => "Show souffle version",
38
+ :boolean => true,
39
+ :proc => lambda { |v| puts "Souffle: #{::Souffle::VERSION}"},
40
+ :exit => 0
41
+
42
+ def initialize
43
+ super
44
+
45
+ end
46
+ end
@@ -0,0 +1,133 @@
1
+ require 'mixlib/cli'
2
+
3
+ # The souffle application class for both server and worker.
4
+ class Souffle::Application
5
+ include Mixlib::CLI
6
+
7
+ # Added a Wakeup exception.
8
+ class Wakeup < Exception; end
9
+
10
+ # Initialize the application, setting up default handlers.
11
+ def initialize
12
+ super
13
+
14
+ trap("TERM") do
15
+ Souffle::Application.fatal!("SIGTERM received, stopping", 1)
16
+ end
17
+
18
+ trap("INT") do
19
+ Souffle::Application.fatal!("SIGINT received, stopping", 2)
20
+ end
21
+
22
+ trap("QUIT") do
23
+ Souffle::Log.info("SIGQUIT received, call stack:\n ", caller.join("\n "))
24
+ end
25
+
26
+ trap("HUP") do
27
+ Souffle::Log.info("SIGHUP received, reconfiguring")
28
+ reconfigure
29
+ end
30
+ end
31
+
32
+ # Reconfigure the application and logging.
33
+ def reconfigure
34
+ configure_souffle
35
+ configure_logging
36
+ end
37
+
38
+ # Configure the application throwing a warning when there is no config file.
39
+ def configure_souffle
40
+ parse_options
41
+
42
+ begin
43
+ ::File.open(config[:config_file]) { |f| apply_config(f.path) }
44
+ rescue Errno::ENOENT => error
45
+ noconfig = "Did not find config file: #{config[:config_file]}"
46
+ Souffle::Log.warn("*****************************************")
47
+ Souffle::Log.warn("#{noconfig}, using command line options.")
48
+ Souffle::Log.warn("*****************************************")
49
+ end
50
+ end
51
+
52
+ # Configures the logging in a relatively sane fashion.
53
+ # Only prints to STDOUT given a valid tty.
54
+ # Does not write to STDOUT when daemonizing.
55
+ def configure_logging
56
+ Souffle::Log.init(Souffle::Config[:log_location])
57
+ if ( Souffle::Config[:log_location] != STDOUT ) && STDOUT.tty? &&
58
+ ( !Souffle::Config[:daemonize] )
59
+ stdout_loger = Logger.new(STDOUT)
60
+ STDOUT.sync = true
61
+ stdout_logger = Souffle::Log.logger.formatter
62
+ Souffle::Log.loggers << stdout_logger
63
+ end
64
+ Souffle::Log.level = Souffle::Config[:log_level]
65
+ end
66
+
67
+ # Run the application itself. Configure, setup, and then run.
68
+ def run
69
+ reconfigure
70
+ setup_application
71
+ run_application
72
+ end
73
+
74
+ # Placeholder for setup_application, intended to be overridden.
75
+ #
76
+ # @raise Souffle::Exceptions::Application Must be overridden.
77
+ def setup_application
78
+ error_msg = "#{self.to_s}: you must override setup_application"
79
+ raise Souffle::Exceptions::Application, error_msg
80
+ end
81
+
82
+ # Placeholder for run_application, intended to be overridden.
83
+ #
84
+ # @raise Souffle::Exceptions::Application Must be overridden.
85
+ def run_application
86
+ error_msg = "#{self.to_s}: you must override run_application"
87
+ raise Souffle::Exceptions::Application, error_msg
88
+ end
89
+
90
+ private
91
+
92
+ # Apply the configuration given a file path.
93
+ #
94
+ # @param [ String ] config_file_path The path to the configuration file.
95
+ def apply_config(config_file_path)
96
+ Souffle::Config.from_file(config_file_path)
97
+ Souffle::Config.merge!(config)
98
+ end
99
+
100
+ class << self
101
+ # Present a debug stracktrace upon an error.
102
+ # Gives a readable backtrace with a timestamp.
103
+ #
104
+ # @param [ Exception ] e The raised exception.
105
+ def debug_stacktrace(e)
106
+ message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
107
+ stacktrace_out = "Generated at #{Time.now.to_s}\n"
108
+ stacktrace_out += message
109
+
110
+ Souffle::Log.debug(message)
111
+ end
112
+
113
+ # Log a fatal error message to both STDERR and the Logger,
114
+ # exit the application with a fatal message.
115
+ #
116
+ # @param [ msg ] String The message to log.
117
+ # @param [ err ] Integer The exit level.
118
+ def fatal!(msg, err = -1)
119
+ Souffle::Log.fatal(msg)
120
+ Process.exit err
121
+ end
122
+
123
+ # Log a fatal error message to both STDERR and the Logger,
124
+ # exit the application with a debug message.
125
+ #
126
+ # @param [ msg ] String The message to log.
127
+ # @param [ err ] Integer The exit level.
128
+ def exit!(msg, err = -1)
129
+ Souffle::Log.debug(msg)
130
+ Process.exit err
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,75 @@
1
+ require 'souffle/log'
2
+ require 'mixlib/config'
3
+ require 'yajl'
4
+
5
+ module Souffle
6
+ # The configuration object for the souffle server.
7
+ class Config
8
+
9
+ extend Mixlib::Config
10
+
11
+ # Return the configuration itself upon inspection.
12
+ def self.inspect
13
+ configuration.inspect
14
+ end
15
+
16
+ # Loads a given file and passes it to the appropriate parser.
17
+ #
18
+ # @raise [ IOError ] Any IO Exceptions that occur.
19
+ #
20
+ # @param [ String ] filename The filename to read.
21
+ def self.from_file(filename, parser="ruby")
22
+ send("from_file_#{parser}".to_sym, filename)
23
+ end
24
+
25
+ # Loads a given ruby file and runs instance_eval against it
26
+ # in the context of the current object.
27
+ #
28
+ # @raise [ IOError ] Any IO Exceptions that occur.
29
+ #
30
+ # @param [ String ] filename The file to read.
31
+ def self.from_file_ruby(filename)
32
+ self.instance_eval(IO.read(filename), filename, 1)
33
+ end
34
+
35
+ # Loads a given json file and merges the current context
36
+ # configuration with the updated hash.
37
+ #
38
+ # @raise [ IOError ] Any IO Exceptions that occur.
39
+ # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
40
+ #
41
+ # @param [ String ] filename The file to read.
42
+ def self.from_file_json(filename)
43
+ self.from_input_json(IO.read(filename))
44
+ end
45
+
46
+ # Loads a given json input and merges the current context
47
+ # configuration with the updated hash.
48
+ #
49
+ # @raise [ IOError ] Any IO Exceptions that occur.
50
+ # @raise [ Yajl::ParseError ] Raises Yajl Parsing error on improper json.
51
+ #
52
+ # @param [ String ] input The json configuration input.
53
+ def self.from_stream_json(input)
54
+ parser = Yajl::Parser.new
55
+ configuration.merge!(parser.parse(input))
56
+ end
57
+
58
+ # When you are using ActiveSupport, they monkey-patch 'daemonize' into
59
+ # Kernel. So while this is basically identical to what method_missing
60
+ # would do, we pull it up here and get a real method written so that
61
+ # things get dispatched properly.
62
+ config_attr_writer :daemonize do |v|
63
+ configure do |c|
64
+ c[:daemonize] = v
65
+ end
66
+ end
67
+
68
+ log_level :info
69
+ log_location STDOUT
70
+
71
+ aws_access_key = ""
72
+ aws_access_secret = ""
73
+
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ module Souffle
2
+ # Souffle specific exceptions; intended for debugging purposes.
3
+ class Exceptions
4
+
5
+ # Application level error.
6
+ class Application < RuntimeError; end
7
+ # Provider level error.
8
+ class Provider < RuntimeError; end
9
+
10
+ # Runlist Name cannot be nil or empty and must be a word [A-Za-z0-9_:].
11
+ class InvalidRunlistName < RuntimeError; end
12
+ # Runlist Type cannot be nil or empty and must be one of (role|recipe).
13
+ class InvalidRunlistType < RuntimeError; end
14
+
15
+ # Node children must respond to dependencies and run_list.
16
+ class InvalidChild < RuntimeError; end
17
+
18
+ # The provider must exist in souffle/providers.
19
+ class InvalidProvider < RuntimeError; end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require 'logger'
2
+ require 'mixlib/log'
3
+
4
+ module Souffle
5
+ # Souffle's internal logging facility.
6
+ # Standardized to provide a consistent log format.
7
+ class Log
8
+ extend Mixlib::Log
9
+
10
+ # Force initialization of the primary log device (@logger)
11
+ init
12
+
13
+ # Monkeypatch Formatter to allow local show_time updates.
14
+ class Formatter
15
+ # Allow enabling and disabling of time with a singleton.
16
+ def self.show_time=(*args)
17
+ Mixlib::Log::Formatter.show_time = *args
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ require 'souffle/node/runlist_item'
2
+
3
+ module Souffle
4
+ # A specialized Array that handles runlist items appropriately.
5
+ class Node::RunList < Array
6
+
7
+ # Pushes another runlist item onto the runlist array.
8
+ #
9
+ # @param [ String ] item The runlist item as a string.
10
+ def <<(item)
11
+ item = Souffle::Node::RunListItem.new(item)
12
+ super(item)
13
+ end
14
+
15
+ # Pushes another item onto the runlist array.
16
+ #
17
+ # @param [ String ] item The runlist item as a string.
18
+ def push(item)
19
+ item = Souffle::Node::RunListItem.new(item)
20
+ super(item)
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ require 'souffle/node/runlist_parser'
2
+
3
+ module Souffle
4
+ # A single runlist item, most be parsed and either a recipe or role.
5
+ class Node::RunListItem
6
+
7
+ # Creates a new runlist item from a string.
8
+ #
9
+ # @param [ String ] item The runlist string to turn into an object.
10
+ # @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions when
11
+ # the runlist item or type isn't a proper chef role or recipe.
12
+ def initialize(item=nil)
13
+ @original_item = item
14
+ @item = Souffle::Node::RunListParser.parse(item)
15
+ end
16
+
17
+ # Returns the name of the runlist item.
18
+ #
19
+ # @return [ String ] The name of the runlist item.
20
+ def name
21
+ @item["name"]
22
+ end
23
+
24
+ # Returns the type of the runlist item.
25
+ #
26
+ # @return [ String ] The type of the runlist item.
27
+ def type
28
+ @item["type"]
29
+ end
30
+
31
+ # Returns the RunListItem as it's original string.
32
+ def to_s
33
+ @original_item
34
+ end
35
+
36
+ # Overriding the default equality comparator to use string representation.
37
+ #
38
+ # @param [ Souffle::Node::RunListItem ] runlist_item
39
+ #
40
+ # @return [ true,false ] Whether or not the objects are equal.
41
+ def ==(runlist_item)
42
+ self.to_s == runlist_item.to_s
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,73 @@
1
+ module Souffle
2
+ # The runlist parser singleton.
3
+ class Node::RunListParser
4
+
5
+ # The runlist match parser
6
+ PARSER = %r{
7
+ (?<type> (recipe|role)) {0} # The runlist item type.
8
+ (?<name> (.*)) {0} # The runlist item name.
9
+
10
+ \g<type>\[\g<name>\]
11
+ }x
12
+
13
+ class << self
14
+ # Checks to see whether the runlist item is a valid recipe or role.
15
+ #
16
+ # @param [ String ] item The runlist item.
17
+ #
18
+ # @return [ Hash ] The runlist item as a hash.
19
+ def parse(item)
20
+ runlist_hash = hashify_match(PARSER.match(item))
21
+ gaurentee_valid_keys(runlist_hash)
22
+ gaurentee_name_is_word(runlist_hash)
23
+ runlist_hash
24
+ end
25
+
26
+ # Takes the matches and converts them into a hashed version.
27
+ #
28
+ # @param [ MatchData,nil ] match The MatchData to hashify.
29
+ #
30
+ # @return [ Hash,nil ] The hashified version of the runlist item.
31
+ def hashify_match(match)
32
+ return nil if match.nil?
33
+ Hash[*match.names.zip(match.captures).flatten]
34
+ end
35
+
36
+ # Tests whether the runlist_hash name and type are valid.
37
+ #
38
+ # @param [ Hash ] runlist_hash The runlist hash to test.
39
+ #
40
+ # @raise [ InvalidRunlistName, InvalidRunlistType ] Raises exceptions
41
+ # when the runlist match failed, the type wasn't a recipe or role,
42
+ # or when the name itself isn't a valid word.
43
+ def gaurentee_valid_keys(runlist_hash)
44
+ if runlist_hash.nil?
45
+ raise Souffle::Exceptions::InvalidRunlistType,
46
+ "Type must be one of (role|recipe)"
47
+ end
48
+ if runlist_hash["name"].nil? or runlist_hash["name"].empty?
49
+ raise Souffle::Exceptions::InvalidRunlistName,
50
+ "Name cannot be nil or empty."
51
+ end
52
+ if runlist_hash["type"].nil? or runlist_hash["type"].empty?
53
+ raise Souffle::Exceptions::InvalidRunlistType,
54
+ "Type cannot be nil or empty and must be one of (role|recipe)"
55
+ end
56
+ end
57
+
58
+ # Checks whether the runlist_hash is a valid word.
59
+ #
60
+ # @param [ Hash ] runlist_hash The runlist hash to test.
61
+ #
62
+ # @raise [ InvalidRunlistName ] Runlist Name is invalid.
63
+ def gaurentee_name_is_word(runlist_hash)
64
+ m = /[A-Za-z0-9_:]+/
65
+ unless m.match(runlist_hash["name"])[0] == runlist_hash["name"]
66
+ raise Souffle::Exceptions::InvalidRunlistName,
67
+ "Name must be [A-Za-z0-9_:]."
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end