yacl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|