app-ctx 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +109 -0
- data/lib/app-ctx.rb +228 -0
- data/lib/app-ctx/dlog.rb +164 -0
- data/test/t_app-ctx.rb +199 -0
- metadata +49 -0
data/README
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
Application Context
|
3
|
+
|
4
|
+
Every application needs configuration and app-ctx provides a concise way of
|
5
|
+
doing it.
|
6
|
+
|
7
|
+
For all applications (you are not a mouseclicker, are u?), once in a while
|
8
|
+
you need to supply some configuration values to overrule the built-in
|
9
|
+
defaults. The app-ctx gem does unify and organize built-in constants,
|
10
|
+
config files and commandline option with a clearly defined priority, from
|
11
|
+
low to high:
|
12
|
+
|
13
|
+
- procedural: App::Config#set_default_values
|
14
|
+
- default values YAML file from next to the $0 script
|
15
|
+
- user supplied configuration file, eg.: --config=/tmp/foo.yml
|
16
|
+
- command line options and flags: --foo --bar=foo
|
17
|
+
|
18
|
+
But for your application it is of no interesst from where the values are
|
19
|
+
coming: command line option: "--port=1234", a user configuration file or
|
20
|
+
from the applications built-in default values. Therefor +app-ctx+ combines
|
21
|
+
value settings from various sources into a single configuration hash.
|
22
|
+
|
23
|
+
basically you have two ways to use it:
|
24
|
+
|
25
|
+
require 'app-ctx' # of course,and than...
|
26
|
+
|
27
|
+
1. closures (see examples/run_with_block.rb)
|
28
|
+
|
29
|
+
App::ctx.run do |context| ... end
|
30
|
+
|
31
|
+
or 2. with a mainclass(see examples/run_with_class.rb)
|
32
|
+
|
33
|
+
App::ctx.run YourClassHere
|
34
|
+
|
35
|
+
The context object provides:
|
36
|
+
|
37
|
+
values: the combined key and value settings
|
38
|
+
argv: remaining argument(not the options) of the command line
|
39
|
+
defaults_path: full path to the defaults file
|
40
|
+
|
41
|
+
for the second case(with a mainclass) an application instance of this class
|
42
|
+
is created. The first argument is than taken as method name and executed,
|
43
|
+
again with a context oject provided:
|
44
|
+
|
45
|
+
prompt: ruby example/run_with_class show
|
46
|
+
|
47
|
+
will result in the :show method beeing called:
|
48
|
+
|
49
|
+
context = Config.new...
|
50
|
+
...
|
51
|
+
app = Simple.new...
|
52
|
+
app.show(context)
|
53
|
+
|
54
|
+
|
55
|
+
Conversions
|
56
|
+
|
57
|
+
Commandline options are strings only, but sometimes you need strongly typed
|
58
|
+
primitive values. +app-ctx+ does automatically convert integer and float
|
59
|
+
values, see "examples/conversions.rb" for:
|
60
|
+
|
61
|
+
prompt: ./examples/conversions.rb
|
62
|
+
{:i=>23, :f=>3.14}
|
63
|
+
typeof 'i': Fixnum
|
64
|
+
typeof 'f': Float
|
65
|
+
prompt: ./examples/conversions.rb -i=17 -f=2.12
|
66
|
+
{:i=>17, :f=>2.12}
|
67
|
+
typeof 'i': Fixnum
|
68
|
+
typeof 'f': Float
|
69
|
+
prompt: ./examples/conversions.rb -i=i -f=f
|
70
|
+
{:i=>"i", :f=>"f"}
|
71
|
+
typeof 'i': String
|
72
|
+
typeof 'f': String
|
73
|
+
|
74
|
+
|
75
|
+
Flags/Boolean values
|
76
|
+
|
77
|
+
Flags(options without values) are converted to boolean values, see
|
78
|
+
examples/boolean.rb:
|
79
|
+
|
80
|
+
dluesebrink dl-mbook ruby/app-ctx: r examples/boolean.rb
|
81
|
+
{:bool=>false}
|
82
|
+
typeof 'bool': FalseClass
|
83
|
+
dluesebrink dl-mbook ruby/app-ctx: r examples/boolean.rb --bool
|
84
|
+
{:bool=>true}
|
85
|
+
typeof 'bool': TrueClass
|
86
|
+
|
87
|
+
|
88
|
+
Ruby Conversions
|
89
|
+
|
90
|
+
When Fixnum, Float and boolean conversion are not enough, as a last resort,
|
91
|
+
you can use ruby code directly for evaluation of option values. Replacing
|
92
|
+
'=' with ':' results in assigning the ruby evaluation value to the key, see
|
93
|
+
examples/ruby_conv.rb:
|
94
|
+
|
95
|
+
prompt: examples/ruby_conv.rb
|
96
|
+
{:r=>1..10, :a=>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
|
97
|
+
typeof 'r': Range
|
98
|
+
typeof 'a': Array
|
99
|
+
prompt: ./examples/ruby_conv.rb -r:2..3 -a:'(-1..1).to_a'
|
100
|
+
{:r=>2..3, :a=>[-1, 0, 1]}
|
101
|
+
typeof 'r': Range
|
102
|
+
typeof 'a': Array
|
103
|
+
prompt: ./examples/ruby_conv.rb -r:2..3 -a:\(-1..1\).to_a
|
104
|
+
{:r=>2..3, :a=>[-1, 0, 1]}
|
105
|
+
typeof 'r': Range
|
106
|
+
typeof 'a': Array
|
107
|
+
|
108
|
+
|
109
|
+
# vim: set tw=80 syntax=txt nosmartindent:
|
data/lib/app-ctx.rb
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
require 'rdoc/usage'
|
4
|
+
|
5
|
+
require 'app-ctx/dlog'
|
6
|
+
|
7
|
+
module App
|
8
|
+
|
9
|
+
# the priority of configuation settings is:
|
10
|
+
# 1. command line options
|
11
|
+
# 2. configuration from custom file, eg.: --config=/tmp/foo.yml
|
12
|
+
# 3. built-in default values from yaml file next to $0 script
|
13
|
+
class Config
|
14
|
+
|
15
|
+
include DL::LoggerMixin
|
16
|
+
|
17
|
+
attr_reader :values, :argv, :defaults_path
|
18
|
+
|
19
|
+
# params are :argv and :defaults_path which are defaulting to ARGV and
|
20
|
+
# the applications defaults which are loaded from a yaml file next to
|
21
|
+
# the '$0' application script.
|
22
|
+
def initialize params = {}
|
23
|
+
params = {
|
24
|
+
:argv => ARGV,
|
25
|
+
:defaults_path => App.config_path($0)
|
26
|
+
}.update(params)
|
27
|
+
|
28
|
+
@argv = params[:argv]
|
29
|
+
@defaults_path = params[:defaults_path]
|
30
|
+
|
31
|
+
load_config_file(@defaults_path)
|
32
|
+
@values, @argv = parse_command_line @argv
|
33
|
+
end
|
34
|
+
|
35
|
+
# programatic way to define the default values other then from defaults
|
36
|
+
# file.
|
37
|
+
def set_default_values defaults
|
38
|
+
@values = defaults.merge(@values)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
<<-EOT
|
44
|
+
args : #{argv.inspect}
|
45
|
+
values : #{values.inspect}
|
46
|
+
defaults : '#{defaults_path}'
|
47
|
+
user config : '#{values[:config]}'
|
48
|
+
EOT
|
49
|
+
end
|
50
|
+
|
51
|
+
# overwrite current configuration with values from file
|
52
|
+
#
|
53
|
+
def load_config_file cpath
|
54
|
+
@values ||= {}
|
55
|
+
if File.exists? cpath = File.expand_path(cpath)
|
56
|
+
begin
|
57
|
+
@values.update YAML.load_file(cpath)
|
58
|
+
info "loaded(#{cpath})"
|
59
|
+
debug "config values: #{@values.inspect}"
|
60
|
+
rescue => e
|
61
|
+
warn "failed to load: #{cpath}", e
|
62
|
+
end
|
63
|
+
else
|
64
|
+
debug "no such config file: #{cpath}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# methods below are related to option parsing and are used only at
|
69
|
+
# object creastions time from the c'tor
|
70
|
+
private
|
71
|
+
|
72
|
+
# overide default configuration with command line arguments.
|
73
|
+
# --flag becomes [ :flag => true]
|
74
|
+
# --option=value becomes [ :option => "value"]
|
75
|
+
# --int=1 becomes [ :int => 1]
|
76
|
+
# --float=2.3 becomes [ :float => 2.3]
|
77
|
+
# --txt="foo bar" becomes [ :txt => "foo bar"]
|
78
|
+
#
|
79
|
+
# --list:(1..10) becomes [ :list => (1..10) ]
|
80
|
+
def parse_command_line argv
|
81
|
+
c = {}
|
82
|
+
argv = argv.clone.delete_if do |a|
|
83
|
+
#if m = /^--(\w+)(=(.+))?$/.match(a)
|
84
|
+
if m = /^(-(\w)|--(\w\w+))(=(.+))?$/.match(a)
|
85
|
+
debug "#{m.to_a.inspect}" if m
|
86
|
+
val = parse_arg m[5]
|
87
|
+
key = m[2] || m[3]
|
88
|
+
c[key.to_sym] = val
|
89
|
+
|
90
|
+
# eval as ruby snippet
|
91
|
+
elsif m = /^(-(\w)|--(\w\w+)):(.+)$/.match(a)
|
92
|
+
debug "#{m.to_a.inspect}" if m
|
93
|
+
val = eval m[4]
|
94
|
+
key = m[2] || m[3]
|
95
|
+
c[key.to_sym] = val
|
96
|
+
end
|
97
|
+
end
|
98
|
+
debug "command line options: #{c.inspect}"
|
99
|
+
|
100
|
+
# overload "--config=..." file before command line options
|
101
|
+
if cpath = c[:config]
|
102
|
+
if file_config = load_config_file(cpath)
|
103
|
+
c = file_config.update(c)
|
104
|
+
else
|
105
|
+
warn "configuration file not found: '#{cpath}'"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
#[@values.dup.update(c), av]
|
110
|
+
@values.update(c)
|
111
|
+
[@values, argv]
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_arg val
|
115
|
+
return true unless val # options are booleans
|
116
|
+
Integer(val) rescue Float(val) rescue parse_string(val)
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_string s
|
120
|
+
if s.match %r{^['"].*['"]$}
|
121
|
+
s[1..-2]
|
122
|
+
else
|
123
|
+
s
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Container
|
129
|
+
|
130
|
+
Messages = {
|
131
|
+
:no_entrypoint => <<-'EOM'
|
132
|
+
|
133
|
+
::app-ctx::
|
134
|
+
|
135
|
+
-> sorry, your main class (#{clazz}) seems not to have a default
|
136
|
+
-> 'application_main' entrypoint and no method argument was given, so i don't
|
137
|
+
-> know what to do.
|
138
|
+
|
139
|
+
thank you, good bye and have a nice day
|
140
|
+
|
141
|
+
EOM
|
142
|
+
}
|
143
|
+
|
144
|
+
def inform message_id, binding
|
145
|
+
m = Messages[message_id]
|
146
|
+
puts eval("\"#{m}\"", binding)
|
147
|
+
end
|
148
|
+
|
149
|
+
def initialize context
|
150
|
+
@context = context
|
151
|
+
end
|
152
|
+
|
153
|
+
def execute clazz = nil
|
154
|
+
|
155
|
+
if block_given?
|
156
|
+
warn "attached block takes precedence over class!" if clazz
|
157
|
+
return yield(@context)
|
158
|
+
end
|
159
|
+
|
160
|
+
# create applicaton instance with or without context c'tor (this is
|
161
|
+
# IoC: Constructor Dependency Injection)
|
162
|
+
app = clazz.new(@context) rescue clazz.new
|
163
|
+
|
164
|
+
setter_injection app, @context
|
165
|
+
|
166
|
+
begin
|
167
|
+
# first try the default entry point
|
168
|
+
app.application_main @context
|
169
|
+
|
170
|
+
rescue NoMethodError => e
|
171
|
+
if app.respond_to? :application_main
|
172
|
+
DL.logger.error "failed invokation", e
|
173
|
+
else
|
174
|
+
if 0 == @context.argv.length
|
175
|
+
inform :no_entrypoint, binding()
|
176
|
+
RDoc::usage
|
177
|
+
return
|
178
|
+
else
|
179
|
+
# one or more args => first arg is method with rest
|
180
|
+
# as params
|
181
|
+
begin
|
182
|
+
op = @context.argv.shift
|
183
|
+
app.send(op, @context)
|
184
|
+
rescue => e
|
185
|
+
DL.logger.error "oops: #{e}", e
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
app # the created application instance
|
192
|
+
end
|
193
|
+
|
194
|
+
# IoC setter dependency injection:
|
195
|
+
# try all context keys as setter class
|
196
|
+
def setter_injection obj, context
|
197
|
+
# try setters for keys from context(this is IoC: Setter dependency
|
198
|
+
# injection)
|
199
|
+
context.values.each do |key, value|
|
200
|
+
#p "#{key} #{value}"
|
201
|
+
begin
|
202
|
+
obj.send("#{key}=", value)
|
203
|
+
rescue NoMethodError
|
204
|
+
#ntdh
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
private :setter_injection
|
209
|
+
end
|
210
|
+
|
211
|
+
class << self
|
212
|
+
def run params = {}, &block
|
213
|
+
# create application container from command line context
|
214
|
+
container = Container.new(Config.new(params))
|
215
|
+
if block_given?
|
216
|
+
container.execute {|context| block.call(context) }
|
217
|
+
else
|
218
|
+
container.execute params[:class]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def config_path app_path
|
223
|
+
defaults_path = File.expand_path(app_path)
|
224
|
+
ext = File.extname(defaults_path)
|
225
|
+
defaults_path.sub((ext == "" && /$/ || /#{ext}$/), ".yml")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
data/lib/app-ctx/dlog.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# once (shamelessly) stolen(for testing(and squeezed)) from rails...
|
2
|
+
# now degenerated beyong comprehension
|
3
|
+
# awaiting a major refactoring to get a managble configuration...
|
4
|
+
|
5
|
+
module DL
|
6
|
+
|
7
|
+
AllLoggersByPrefix = {} # list of all loggers
|
8
|
+
|
9
|
+
class Logger
|
10
|
+
|
11
|
+
LevelMetrics = {
|
12
|
+
:fatal => 0.0,
|
13
|
+
:error => 1.0,
|
14
|
+
:warn => 2.0,
|
15
|
+
:info => 3.0,
|
16
|
+
:debug => 4.0,
|
17
|
+
}
|
18
|
+
|
19
|
+
# XXX das mit diesem config kram ist alles noch ziemlicher mist...
|
20
|
+
DefaultLogger = {
|
21
|
+
:prefix => "???",
|
22
|
+
:out => $stderr,
|
23
|
+
:fmt => "%c %23s %s%s\n", # "%c %-13s: %s%s\n",
|
24
|
+
#:include_levels => /^(fatal|error|warn|info)/i, # no debug
|
25
|
+
:include_levels => nil, # all inclusive
|
26
|
+
#:exclude_levels => nil,
|
27
|
+
:exclude_levels => /^debug/i, # no debug on default
|
28
|
+
:quiet => false,
|
29
|
+
:level => 3.0,
|
30
|
+
}
|
31
|
+
Config = {
|
32
|
+
:default => DefaultLogger,
|
33
|
+
}
|
34
|
+
|
35
|
+
attr_accessor :prefix, :out, :fmt, :include_levels, :exclude_levels
|
36
|
+
attr_accessor :quiet, :level
|
37
|
+
|
38
|
+
def initialize prefix, config = DefaultLogger
|
39
|
+
@nesting_level = 0
|
40
|
+
@config = DefaultLogger.dup
|
41
|
+
@config[:prefix] = prefix
|
42
|
+
configure config
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure settings
|
46
|
+
@config.update(settings)
|
47
|
+
@prefix = @config[:prefix]
|
48
|
+
@out = @config[:out]
|
49
|
+
@fmt = @config[:fmt]
|
50
|
+
@include_levels = @config[:include_levels]
|
51
|
+
@exclude_levels = @config[:exclude_levels]
|
52
|
+
@quiet = @config[:quiet]
|
53
|
+
@level = @config[:level]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.[] prefix
|
57
|
+
self.get prefix
|
58
|
+
end
|
59
|
+
|
60
|
+
def log(status, message, exception, &block)
|
61
|
+
unless quiet
|
62
|
+
#return "excluded" if exclude_levels and exclude_levels.match(status)
|
63
|
+
#if include_levels and !include_levels.match(status)
|
64
|
+
# return "not included"
|
65
|
+
#end
|
66
|
+
msg = @fmt % [status[0], prefix, ' ' * @nesting_level, message]
|
67
|
+
@out.print(msg)
|
68
|
+
if exception
|
69
|
+
callstack = exception.backtrace
|
70
|
+
@out.print " !! #{exception}\n\t#{callstack.join("\n\t")}\n"
|
71
|
+
begin
|
72
|
+
reason = exception.reason
|
73
|
+
callstack = reason.backtrace
|
74
|
+
txt = "#{reason}\n\t#{callstack.join("\n\t")}\n"
|
75
|
+
@out.print "(Cause)>> #{txt}"
|
76
|
+
rescue
|
77
|
+
# noop
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
indent(&block) if block_given?
|
82
|
+
end
|
83
|
+
|
84
|
+
def flush
|
85
|
+
@out.flush
|
86
|
+
end
|
87
|
+
|
88
|
+
def indent(&block)
|
89
|
+
@nesting_level += 1
|
90
|
+
if block_given?
|
91
|
+
begin
|
92
|
+
block.call
|
93
|
+
ensure
|
94
|
+
outdent
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def outdent
|
100
|
+
@nesting_level -= 1
|
101
|
+
if block_given?
|
102
|
+
begin
|
103
|
+
block.call
|
104
|
+
ensure
|
105
|
+
indent
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def method_missing(method, *args, &block)
|
112
|
+
message, execption = args
|
113
|
+
if LevelMetrics[method.to_sym] <= level
|
114
|
+
log(method.to_s.upcase, message, execption, &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.logger(prefix = $0, out = $stdout)
|
120
|
+
prefix = self.find_prefix(prefix)
|
121
|
+
unless log = AllLoggersByPrefix[prefix]
|
122
|
+
c = Logger::Config[prefix] || {}
|
123
|
+
#c[:fmt] = "%c %23s %s%s\n"
|
124
|
+
log = (AllLoggersByPrefix[prefix] = Logger.new(prefix, c))
|
125
|
+
end
|
126
|
+
log
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.find_prefix prefix
|
130
|
+
raise "nil prefix not yet implemented!" unless prefix
|
131
|
+
return prefix if prefix.kind_of? String
|
132
|
+
return prefix.name if prefix.kind_of? Class
|
133
|
+
return prefix.name if prefix.kind_of? Module
|
134
|
+
return prefix.class.name
|
135
|
+
end
|
136
|
+
|
137
|
+
module LoggerMixin
|
138
|
+
def logger
|
139
|
+
@dl_class_logger ||= DL.logger(self) # class logger
|
140
|
+
end
|
141
|
+
|
142
|
+
def debug *args
|
143
|
+
logger.debug *args
|
144
|
+
end
|
145
|
+
def info *args
|
146
|
+
logger.info *args
|
147
|
+
end
|
148
|
+
def warn *args
|
149
|
+
logger.warn *args
|
150
|
+
end
|
151
|
+
def error *args
|
152
|
+
logger.error *args
|
153
|
+
end
|
154
|
+
def fatal *args
|
155
|
+
logger.fatal *args
|
156
|
+
end
|
157
|
+
def log_level
|
158
|
+
logger.level
|
159
|
+
end
|
160
|
+
def set_log_level level
|
161
|
+
logger.level= level
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/test/t_app-ctx.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'pp'
|
5
|
+
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
require 'app-ctx'
|
8
|
+
|
9
|
+
class AppModuleTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
class With_application_main
|
12
|
+
attr_reader :context
|
13
|
+
attr_accessor :baroque
|
14
|
+
# application_main is the default catch-all entry point for app-ctx
|
15
|
+
def application_main context
|
16
|
+
puts "#{self}: #{context.inspect}"
|
17
|
+
@context = context
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def test_default_entry_point
|
21
|
+
puts "\n --> #{self.name}"
|
22
|
+
argv = ["-f", "--foo=bar", "--baroque=gaga", "arg1", "arg2"]
|
23
|
+
app = App::run :class => With_application_main, :argv => argv
|
24
|
+
assert_not_nil app
|
25
|
+
assert_equal With_application_main, app.class
|
26
|
+
assert_not_nil app.context
|
27
|
+
assert_equal({:f=>true, :foo=>"bar", :baroque =>"gaga"}, app.context.values)
|
28
|
+
assert_equal ["arg1", "arg2"], app.context.argv
|
29
|
+
assert_equal "gaga", app.baroque
|
30
|
+
end
|
31
|
+
|
32
|
+
class ArgumentEntryPoint
|
33
|
+
attr_reader :context
|
34
|
+
def entrypoint context
|
35
|
+
puts "#{self}: #{context.inspect}"
|
36
|
+
@context = context
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def test_run_without_point
|
40
|
+
puts "\n --> #{self.name}"
|
41
|
+
argv = ["-f", "--foo=bar", "entrypoint", "first_method_arg"]
|
42
|
+
app = App::run :class => ArgumentEntryPoint, :argv => argv
|
43
|
+
assert_not_nil app
|
44
|
+
assert_equal ArgumentEntryPoint, app.class
|
45
|
+
assert_not_nil app.context
|
46
|
+
assert_equal({:f=>true, :foo=>"bar"}, app.context.values)
|
47
|
+
assert_equal ["first_method_arg"], app.context.argv
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class AppConfigTest < Test::Unit::TestCase
|
53
|
+
|
54
|
+
include App
|
55
|
+
|
56
|
+
def test_config_block
|
57
|
+
puts "\n --> #{self.name}"
|
58
|
+
App::Config.new do |context|
|
59
|
+
puts "--------------->context: #{context.inspect}"
|
60
|
+
assert_not_nil context
|
61
|
+
assert_equal({}, context.values)
|
62
|
+
end
|
63
|
+
|
64
|
+
App::Config.new :argv=>["-f", "--foo=bar", "arg1", "arg2"] do |context|
|
65
|
+
puts "--------------->context: #{context.inspect}"
|
66
|
+
assert_not_nil context
|
67
|
+
assert_equal({:f=>true, :foo=>"bar"}, context.values)
|
68
|
+
assert_equal ["arg1", "arg2"], context.argv
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_load_config_file
|
73
|
+
puts "\n --> #{self.name}"
|
74
|
+
c = App::Config.new
|
75
|
+
# config file does not exist:w
|
76
|
+
c.load_config_file File.join(File.dirname($0), "no-such-file")
|
77
|
+
assert_equal({}, c.values)
|
78
|
+
|
79
|
+
# load configuration from file
|
80
|
+
c.load_config_file File.join(File.dirname($0), "config-1.yml")
|
81
|
+
assert_equal({
|
82
|
+
:f=>3.21, :i=>17, :empty_hash=>{}, :assoc=>{:key=>"foo"}
|
83
|
+
}, c.values)
|
84
|
+
|
85
|
+
# config overloading
|
86
|
+
c.load_config_file File.join(File.dirname($0), "config-2.yml")
|
87
|
+
assert_equal({
|
88
|
+
:f=>3.21, :i=>17, :empty_hash=>{},
|
89
|
+
:assoc=>{:key=>"bar"}, :f2=>-3.21
|
90
|
+
}, c.values)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_configure_defaults
|
94
|
+
puts "\n --> #{self.name}"
|
95
|
+
c = App::Config.new
|
96
|
+
assert_equal ARGV, c.argv
|
97
|
+
assert_equal({}, c.values)
|
98
|
+
|
99
|
+
# find the default values config file next to the $0 script
|
100
|
+
assert_equal File.expand_path($0).sub(/.rb$/, ".yml"), c.defaults_path
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_config_path
|
104
|
+
puts "\n --> #{self.name}"
|
105
|
+
|
106
|
+
cpath = App.config_path "foo.bar"
|
107
|
+
assert_not_nil cpath
|
108
|
+
assert_equal File.expand_path(File.join(".", "foo.yml")), cpath
|
109
|
+
|
110
|
+
cpath = App.config_path "/tmp/foo.rb.rb"
|
111
|
+
assert_not_nil cpath
|
112
|
+
assert_equal File.expand_path(File.join("/", "tmp", "foo.rb.yml")), cpath
|
113
|
+
|
114
|
+
# XXX this might fail on windows??
|
115
|
+
cpath = App.config_path "~/foo.bar"
|
116
|
+
assert_not_nil cpath
|
117
|
+
assert_equal File.expand_path(File.join("~", "foo.yml")), cpath
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_parse_ruby_options
|
121
|
+
puts "\n --> #{self.name}"
|
122
|
+
|
123
|
+
c = Config.new :argv => ["--list:(1..10).to_a"]
|
124
|
+
cfg, av = c.values, c.argv
|
125
|
+
assert_equal({:list => (1..10).to_a}, cfg)
|
126
|
+
|
127
|
+
c = Config.new :argv => ["--list:(1..10)"]
|
128
|
+
cfg, av = c.values, c.argv
|
129
|
+
assert_equal({:list => (1..10)}, cfg)
|
130
|
+
assert cfg[:list].kind_of?(Range)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_parse_command_line_options
|
134
|
+
puts "\n --> #{self.name}"
|
135
|
+
|
136
|
+
c = Config.new :argv => []
|
137
|
+
cfg, av = c.values, c.argv
|
138
|
+
assert_equal({}, cfg)
|
139
|
+
assert_equal [], av
|
140
|
+
|
141
|
+
c = Config.new :argv => ["-a"]
|
142
|
+
cfg, av = c.values, c.argv
|
143
|
+
assert cfg[:a]
|
144
|
+
assert ! cfg[:b]
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_parse_command_line_int_float_flags
|
148
|
+
puts "\n --> #{self.name}"
|
149
|
+
|
150
|
+
c = Config.new :argv => ["-a", "--int=5", "--float=2.34"]
|
151
|
+
cfg, av = c.values, c.argv
|
152
|
+
assert_equal({:a=>true,:int=>5, :float=>2.34}, cfg)
|
153
|
+
assert_equal [], av
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_various_string_values
|
157
|
+
puts "\n --> #{self.name}"
|
158
|
+
|
159
|
+
c = Config.new :argv => ["xxx"]
|
160
|
+
cfg, av = c.values, c.argv
|
161
|
+
assert_equal ["xxx"], av
|
162
|
+
assert_equal({}, cfg)
|
163
|
+
|
164
|
+
c = Config.new :argv => ["foo", "bar"]
|
165
|
+
cfg, av = c.values, c.argv
|
166
|
+
assert_equal({}, cfg)
|
167
|
+
assert_equal ["foo", "bar"], av
|
168
|
+
|
169
|
+
c = Config.new :argv => ["--foo", "bar"]
|
170
|
+
cfg, av = c.values, c.argv
|
171
|
+
assert_equal({:foo=>true}, cfg)
|
172
|
+
assert_equal ["bar"], av
|
173
|
+
|
174
|
+
c = Config.new :argv => ["foo", "--bar"]
|
175
|
+
cfg, av = c.values, c.argv
|
176
|
+
assert_equal({:bar=>true}, cfg)
|
177
|
+
assert_equal ["foo"], av
|
178
|
+
|
179
|
+
c = Config.new :argv => ["--key=val", "foo"]
|
180
|
+
cfg, av = c.values, c.argv
|
181
|
+
assert_equal({:key=>'val'}, cfg)
|
182
|
+
assert_equal ["foo"], av
|
183
|
+
|
184
|
+
c = Config.new :argv => ["--key=val", "--key=ooo"]
|
185
|
+
cfg, av = c.values, c.argv
|
186
|
+
assert_equal({:key=>'ooo'}, cfg)
|
187
|
+
assert_equal [], av
|
188
|
+
|
189
|
+
c = Config.new :argv => ["-a='foo bar'", '-b="bar foo"']
|
190
|
+
cfg, av = c.values, c.argv
|
191
|
+
assert_equal [], av
|
192
|
+
assert_equal({:a=>"foo bar", :b=>"bar foo"}, cfg)
|
193
|
+
|
194
|
+
c = Config.new :argv => ["--aa='foo bar'", '--bb="bar foo"']
|
195
|
+
cfg, av = c.values, c.argv
|
196
|
+
assert_equal [], av
|
197
|
+
assert_equal({:aa=>"foo bar", :bb=>"bar foo"}, cfg)
|
198
|
+
end
|
199
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: app-ctx
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.2
|
7
|
+
date: 2006-09-18 00:00:00 +02:00
|
8
|
+
summary: command line application startup and config context
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: dirk.luesebrink@idmedia.com
|
12
|
+
homepage: http://www.idmedia.com/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Dirk Luesebrink
|
30
|
+
files:
|
31
|
+
- lib/app-ctx.rb
|
32
|
+
- lib/app-ctx/dlog.rb
|
33
|
+
- test/t_app-ctx.rb
|
34
|
+
- README
|
35
|
+
test_files:
|
36
|
+
- test/t_app-ctx.rb
|
37
|
+
rdoc_options:
|
38
|
+
- --main
|
39
|
+
- README
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
dependencies: []
|
49
|
+
|