yacl 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +5 -0
- data/LICENSE +16 -0
- data/Manifest.txt +48 -0
- data/README.rdoc +55 -0
- data/Rakefile +308 -0
- data/example/myapp-simple/bin/myapp +16 -0
- data/example/myapp-simple/config/database.yml +8 -0
- data/example/myapp-simple/config/host.yml +2 -0
- data/example/myapp-simple/config/pipeline.yml +1 -0
- data/example/myapp-simple/lib/myapp.rb +53 -0
- data/example/myapp/bin/myapp +17 -0
- data/example/myapp/bin/myapp-job +10 -0
- data/example/myapp/config/database.yml +8 -0
- data/example/myapp/config/httpserver.yml +3 -0
- data/example/myapp/config/pipeline.yml +1 -0
- data/example/myapp/lib/myapp.rb +6 -0
- data/example/myapp/lib/myapp/cli.rb +92 -0
- data/example/myapp/lib/myapp/defaults.rb +28 -0
- data/example/myapp/lib/myapp/job.rb +56 -0
- data/lib/yacl.rb +12 -0
- data/lib/yacl/define.rb +9 -0
- data/lib/yacl/define/cli.rb +7 -0
- data/lib/yacl/define/cli/options.rb +97 -0
- data/lib/yacl/define/cli/parser.rb +112 -0
- data/lib/yacl/define/cli/runner.rb +82 -0
- data/lib/yacl/define/defaults.rb +58 -0
- data/lib/yacl/define/plan.rb +197 -0
- data/lib/yacl/loader.rb +80 -0
- data/lib/yacl/loader/env.rb +103 -0
- data/lib/yacl/loader/yaml_dir.rb +137 -0
- data/lib/yacl/loader/yaml_file.rb +102 -0
- data/lib/yacl/properties.rb +144 -0
- data/lib/yacl/simple.rb +52 -0
- data/spec/data/yaml_dir/database.yml +8 -0
- data/spec/data/yaml_dir/httpserver.yml +3 -0
- data/spec/define/cli/options_spec.rb +47 -0
- data/spec/define/cli/parser_spec.rb +64 -0
- data/spec/define/cli/runner_spec.rb +57 -0
- data/spec/define/defaults_spec.rb +24 -0
- data/spec/define/plan_spec.rb +77 -0
- data/spec/loader/env_spec.rb +32 -0
- data/spec/loader/yaml_dir_spec.rb +43 -0
- data/spec/loader/yaml_file_spec.rb +80 -0
- data/spec/loader_spec.rb +16 -0
- data/spec/properties_spec.rb +60 -0
- data/spec/simple_spec.rb +85 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/version_spec.rb +8 -0
- metadata +207 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'yacl/loader/yaml_file'
|
2
|
+
class Yacl::Loader
|
3
|
+
# YamlDir uses a directory of yaml files to load a Properties instance.
|
4
|
+
# It does so as if the entire directory were one big yaml file.
|
5
|
+
#
|
6
|
+
# Examples:
|
7
|
+
#
|
8
|
+
# Given the following directory structure -
|
9
|
+
#
|
10
|
+
# ./config
|
11
|
+
# ├── database.yml
|
12
|
+
# ├── httpserver.yml
|
13
|
+
# └── pipeline.yml
|
14
|
+
#
|
15
|
+
# And Creating a YamlDir instance -
|
16
|
+
#
|
17
|
+
# yd = YamlDir.new( :path => "./config" )
|
18
|
+
#
|
19
|
+
# Then you will have propertes like these.
|
20
|
+
#
|
21
|
+
# props = yd.properties
|
22
|
+
#
|
23
|
+
# props.dataabase.adapter # => postgre
|
24
|
+
# props.httpserver.port # => 80
|
25
|
+
#
|
26
|
+
# You may also create a YamlDir and have it use the propety from a Properites
|
27
|
+
# instance to indicate the directory to load from.
|
28
|
+
#
|
29
|
+
# other_props.config.dir # => ./config
|
30
|
+
#
|
31
|
+
# yd = YamlDir.new( :properties => other_props, :parameter => 'config.dir' )
|
32
|
+
#
|
33
|
+
# props = yd.properties
|
34
|
+
# props.dataabase.adapter # => postgre
|
35
|
+
# props.httpserver.port # => 80
|
36
|
+
#
|
37
|
+
# This latter approach use what is used when incorparting a commandline option
|
38
|
+
# of a configuration directory within a Plan and the loading of the
|
39
|
+
# configuration directory after the pipeline is parsed.
|
40
|
+
#
|
41
|
+
# class MyPlan < ::Yacl::Define::Plan
|
42
|
+
# try MyApp::Cli::Parser
|
43
|
+
# try Yacl::Loader::YamlDir, :parameter => 'config.dir'
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Since this use of YamlDir is part of a Plan, the Plan will pass in the
|
47
|
+
# Properties accumulated so far to YamlDir
|
48
|
+
#
|
49
|
+
class YamlDir < ::Yacl::Loader
|
50
|
+
class Error < ::Yacl::Loader::Error; end
|
51
|
+
|
52
|
+
# Public: Create a new instance of YamlDir
|
53
|
+
#
|
54
|
+
# opts - A Hash of options
|
55
|
+
# :path - The filesystem path to the directory to load
|
56
|
+
# :properties - A Properties instance to use in conjunction with
|
57
|
+
# :parameter
|
58
|
+
# :pararmeter - The parameter to look up within the Properties that
|
59
|
+
# passed in with :properites in order to populate
|
60
|
+
# :path
|
61
|
+
# :scope - Scope the loded Properties by this value and only
|
62
|
+
# return that subset from #properties
|
63
|
+
#
|
64
|
+
def initialize( opt = {} )
|
65
|
+
super
|
66
|
+
@path = YamlDir.extract_path( options )
|
67
|
+
@parameter = YamlDir.mapify_key( options[:parameter] )
|
68
|
+
|
69
|
+
if (not @path) and (reference_properties and @parameter) then
|
70
|
+
value = reference_properties.get( *@parameter )
|
71
|
+
@path = Pathname.new( value ) if value
|
72
|
+
end
|
73
|
+
|
74
|
+
@scope = options[:scope]
|
75
|
+
YamlDir.validate_directory( @path )
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Return the Properites that are described by the this YamlDir
|
79
|
+
# instance
|
80
|
+
#
|
81
|
+
# Returns a Properties instance
|
82
|
+
def properties
|
83
|
+
YamlDir.validate_and_load_properties( @path, @scope )
|
84
|
+
end
|
85
|
+
|
86
|
+
class << self
|
87
|
+
|
88
|
+
# Internal: Validate the directory and then load the properties.
|
89
|
+
#
|
90
|
+
# dirname - The directory from which to load properties
|
91
|
+
# scope - Apply the given scope to the loaded properties
|
92
|
+
#
|
93
|
+
# Returns a Properties instance.
|
94
|
+
def validate_and_load_properties( dirname , scope = nil )
|
95
|
+
validate_directory( dirname )
|
96
|
+
YamlDir.load_properties( dirname, scope )
|
97
|
+
end
|
98
|
+
|
99
|
+
# Internal: Validate the given directory.
|
100
|
+
#
|
101
|
+
# path - the Pathname to check.
|
102
|
+
#
|
103
|
+
# 1) make sure it is not nil
|
104
|
+
# 2) make sure that it exists
|
105
|
+
# 3) make sure that it is a directory
|
106
|
+
#
|
107
|
+
# Raise an error if any of the above fail
|
108
|
+
#
|
109
|
+
# Returns nothing.
|
110
|
+
def validate_directory( path )
|
111
|
+
raise Error, "No directory specified" unless path
|
112
|
+
raise Error, "#{path} does not exist" unless path.exist?
|
113
|
+
raise Error, "#{path} is not a directory" unless path.directory?
|
114
|
+
end
|
115
|
+
|
116
|
+
# Interal: Load the proprerties scoped by the given scope
|
117
|
+
#
|
118
|
+
# dirname - The directory from which to load properties
|
119
|
+
# scope - Apply the given scope to the loaded properties
|
120
|
+
#
|
121
|
+
# Returns a Properties instance.
|
122
|
+
def load_properties( dirname, scope )
|
123
|
+
p = Yacl::Properties.new
|
124
|
+
Dir.glob( File.join( dirname, '*.yml') ).each do |config_fname|
|
125
|
+
file_properties = Yacl::Loader::YamlFile.new( :path => config_fname ).properties
|
126
|
+
namespace = File.basename( config_fname, ".yml" )
|
127
|
+
file_properties.each do |k,v|
|
128
|
+
args = [ namespace, k ].flatten
|
129
|
+
args << v
|
130
|
+
p.set( *args )
|
131
|
+
end
|
132
|
+
end
|
133
|
+
return p
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
class Yacl::Loader
|
3
|
+
# YamlFile loads a Properites instance from a single yaml file.
|
4
|
+
#
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# yd = YamlFile.new( :filename => "./config/database.yml" )
|
8
|
+
# props = yd.properties
|
9
|
+
# props.adapter #=> 'postgres'
|
10
|
+
#
|
11
|
+
# You may also create a YamlFile and have it use the propety from a Properites
|
12
|
+
# instance to indicate the directory to load from.
|
13
|
+
#
|
14
|
+
# other_props.config.file # => ./config/database.yml
|
15
|
+
#
|
16
|
+
# yd = YamFile.new( :properties => other_props, :parameter => 'config.file' )
|
17
|
+
#
|
18
|
+
# props = yd.properties
|
19
|
+
# props.adapter # => postgre
|
20
|
+
#
|
21
|
+
class YamlFile < ::Yacl::Loader
|
22
|
+
class Error < ::Yacl::Loader::Error; end
|
23
|
+
|
24
|
+
def initialize( opts = {} )
|
25
|
+
super
|
26
|
+
@path = YamlFile.extract_path( options )
|
27
|
+
@scope = options.fetch( :scope, nil )
|
28
|
+
@parameter = YamlFile.mapify_key( options[:parameter] )
|
29
|
+
|
30
|
+
if (not @path) and (reference_properties and @parameter) then
|
31
|
+
@path = Pathname.new( reference_properties.get( *@parameter ) )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
YamlFile.validate_file( @path )
|
36
|
+
end
|
37
|
+
|
38
|
+
def properties
|
39
|
+
YamlFile.validate_and_load_properties( @path, @scope )
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
# Internal: load a Properties from the given filename.
|
44
|
+
#
|
45
|
+
# filename - The name of the yaml file to load
|
46
|
+
# scope - scope the loaded Properties by the given scope
|
47
|
+
#
|
48
|
+
# Returns a Properties instance.
|
49
|
+
def validate_and_load_properties( filename, scope = nil )
|
50
|
+
validate_file( filename )
|
51
|
+
YamlFile.load_properties( filename, scope )
|
52
|
+
end
|
53
|
+
|
54
|
+
# Internal: Validate that the give Pathname is a valid, readable file
|
55
|
+
#
|
56
|
+
# path - the Patname we are going to check
|
57
|
+
#
|
58
|
+
# Validates:
|
59
|
+
#
|
60
|
+
# 1) that path has a value
|
61
|
+
# 1) that the path exists
|
62
|
+
# 2) that the path is readable
|
63
|
+
#
|
64
|
+
# Raises an error if either of the above failes
|
65
|
+
def validate_file( path )
|
66
|
+
raise Error, "No path specified" unless path
|
67
|
+
raise Error, "#{path} does not exist" unless path.exist?
|
68
|
+
raise Error, "#{path} is not readable" unless path.readable?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: load a Properties from the given filename.
|
72
|
+
#
|
73
|
+
# filename - The name of the yaml file to load
|
74
|
+
# scope - scope the loaded Properties by the given scope
|
75
|
+
#
|
76
|
+
# Returns a Properties instance.
|
77
|
+
def load_properties( filename, scope )
|
78
|
+
loaded = ::YAML.load_file( filename )
|
79
|
+
raise Error, "#{filename} does not contain a top level hash" unless loaded.kind_of?( Hash )
|
80
|
+
|
81
|
+
p = Yacl::Properties.new( loaded )
|
82
|
+
return scoped( p, scope, filename )
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal: scope the given Properties that are loaded from the given
|
86
|
+
# filename.
|
87
|
+
#
|
88
|
+
# p - The Properties to scope by scope
|
89
|
+
# scope - The scope to apply to p
|
90
|
+
# filename - The filename from which Properties was loaded
|
91
|
+
#
|
92
|
+
# Raises Error if the scope does not exist
|
93
|
+
#
|
94
|
+
# Returns a new Properties instance.
|
95
|
+
def scoped( p, scope, filename )
|
96
|
+
return p unless scope
|
97
|
+
raise Error, "#{filename} does not contain a top level scope of '#{scope}'. Options are #{p.scopes.join(", ")}" unless p.has_scope?( scope )
|
98
|
+
return p.scoped_by( scope )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'set'
|
3
|
+
require 'map'
|
4
|
+
|
5
|
+
module Yacl
|
6
|
+
# Properties is a wrapper around keys and values that are part of a
|
7
|
+
# Configuration. This is just the keys and the values, it does not tie it to
|
8
|
+
# where the data comes from.
|
9
|
+
#
|
10
|
+
# The properties interface is a limited subset of the Hash interface.
|
11
|
+
#
|
12
|
+
# Properties may also be accessed via a dotted method call location
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# p = Properties.new( 'my.a' => 'foo', 'my.b' => 'bar' )
|
17
|
+
# p.my.a #=> 'foo'
|
18
|
+
# p.my.b #=> 'bar'
|
19
|
+
# p.my #=> Properties
|
20
|
+
#
|
21
|
+
#
|
22
|
+
class Properties
|
23
|
+
extend Forwardable
|
24
|
+
|
25
|
+
# Internal: returns the list of methods to delegate to the internal Map
|
26
|
+
# instance
|
27
|
+
#
|
28
|
+
# Returns an Array of method names
|
29
|
+
def self.delegating_to_map
|
30
|
+
[ :set, :get, :fetch, :store, :delete, :clear, :[], :[]=, :has?, :has_key?, :each, :length, :keys, :method_missing ]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Internal: Returns the list of methods that may be delegated to a
|
34
|
+
# Properties instance.
|
35
|
+
#
|
36
|
+
# Returns an Array of method names
|
37
|
+
def self.delegatable_methods
|
38
|
+
delegating_to_map + [ :scoped_by, :scopes, :has_scope? ]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Internal: Delegate methods to the internal Map instance
|
42
|
+
def_delegators :@map, *Properties.delegating_to_map
|
43
|
+
|
44
|
+
# Internal: used only for merging
|
45
|
+
#
|
46
|
+
# Returns the internal Map instances
|
47
|
+
attr_reader :map
|
48
|
+
|
49
|
+
# Create a new Properites
|
50
|
+
#
|
51
|
+
# initial - A Hash or Map that is used as the initial values of the
|
52
|
+
# Properites
|
53
|
+
#
|
54
|
+
def initialize( initial = {} )
|
55
|
+
@map = Map.new
|
56
|
+
expanded_keys( initial ) do |k,v|
|
57
|
+
k << v
|
58
|
+
@map.set( *k )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Mereg another Properties instance on top of this one, overwriting
|
63
|
+
# any commaon key/values.
|
64
|
+
#
|
65
|
+
# This is a destructive operation on this Properties instance.
|
66
|
+
#
|
67
|
+
# other - The Properties instance to lay on top of this one.
|
68
|
+
#
|
69
|
+
# Returns nothing
|
70
|
+
def merge!( other )
|
71
|
+
@map.merge!( other.map )
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Return a new Properties that is a subset of the properties with the first
|
75
|
+
# prefix removed.
|
76
|
+
#
|
77
|
+
# scope - the scope under which to use
|
78
|
+
#
|
79
|
+
# Returns a new Properties object
|
80
|
+
def scoped_by( *scope )
|
81
|
+
scope = scope.length == 1 ? scope.first : scope
|
82
|
+
sub_map = @map.get( expand_key( scope ) ) || {}
|
83
|
+
Properties.new( sub_map )
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: List the knownn scops, i.e. top level keys, of the current
|
87
|
+
# Properitess.
|
88
|
+
#
|
89
|
+
# Returns an Array
|
90
|
+
def scopes
|
91
|
+
scope_names.to_a.sort
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: Return true or false if the Properites instance has the given
|
95
|
+
# scope.
|
96
|
+
#
|
97
|
+
# Returns true or false.
|
98
|
+
def has_scope?( scope )
|
99
|
+
scope_names.include?( scope )
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Internal: The list of all scope names
|
105
|
+
#
|
106
|
+
# Returns an Array of Strings
|
107
|
+
def scope_names
|
108
|
+
keys.map { |k| k.to_s }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Internal: Expand the hash into an Array including the multi-value key and
|
112
|
+
# the value.
|
113
|
+
#
|
114
|
+
# hash - the Hash to convert to an array of arrays.
|
115
|
+
#
|
116
|
+
# Yields the Array of the currenty key and the value
|
117
|
+
#
|
118
|
+
# Returns nothing.
|
119
|
+
def expanded_keys( hash )
|
120
|
+
hash.each do |k,v|
|
121
|
+
ek = expand_key( k )
|
122
|
+
yield ek, v unless ek.empty?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Convert the given key into an Array key suitable for use with
|
127
|
+
# Map.set
|
128
|
+
#
|
129
|
+
# key - the Key
|
130
|
+
#
|
131
|
+
# Retursn an Array
|
132
|
+
def expand_key( key )
|
133
|
+
ek = case key
|
134
|
+
when Array
|
135
|
+
key
|
136
|
+
when /\./
|
137
|
+
key.split(".")
|
138
|
+
else
|
139
|
+
[ key ]
|
140
|
+
end
|
141
|
+
ek.select { |k| k.to_s.strip.length > 0 }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/yacl/simple.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Yacl
|
2
|
+
class Simple < ::Yacl::Define::Cli::Runner
|
3
|
+
class << self
|
4
|
+
# Public: Create a child class of Yacl::Define::Defaults
|
5
|
+
#
|
6
|
+
# block - an optional block will be evaluated in the context of the
|
7
|
+
# created class.
|
8
|
+
#
|
9
|
+
# The child class is created once, and the block is evaluated once.
|
10
|
+
# Further class to this method will result in returning the already
|
11
|
+
# defined class.
|
12
|
+
def defaults( &block )
|
13
|
+
nested_class( 'Defaults', Yacl::Define::Defaults, &block )
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Creates a child class of Yacl::Define::Cli::Parser
|
17
|
+
#
|
18
|
+
# block - an optional block will be evaluated in the context of the
|
19
|
+
# created class.
|
20
|
+
#
|
21
|
+
# The child class is created once, and the block is evaluated once.
|
22
|
+
# Further class to this method will result in returning the already
|
23
|
+
# defined class.
|
24
|
+
def parser( &block )
|
25
|
+
nested_class( 'Parser', Yacl::Define::Cli::Parser, &block )
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Creates a child class of Yacl::Define::Plan
|
29
|
+
#
|
30
|
+
# block - an optional block will be evaluated in the context of the
|
31
|
+
# created class.
|
32
|
+
#
|
33
|
+
# The child class is created once, and the block is evaluated once.
|
34
|
+
# Further class to this method will result in returning the already
|
35
|
+
# defined class.
|
36
|
+
def plan( &block )
|
37
|
+
nested_class( 'Plan', Yacl::Define::Plan, &block )
|
38
|
+
end
|
39
|
+
|
40
|
+
# Internal: Define a class that is a nested class of the module
|
41
|
+
def nested_class( name, parent, &block )
|
42
|
+
unless const_defined?( name ) then
|
43
|
+
klass = Class.new( parent )
|
44
|
+
klass.class_eval( &block ) if block_given?
|
45
|
+
const_set( name , klass )
|
46
|
+
end
|
47
|
+
return const_get( name )
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|