caruby-core 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +51 -0
- data/doc/website/css/site.css +1 -5
- data/doc/website/images/avatar.png +0 -0
- data/doc/website/images/favicon.ico +0 -0
- data/doc/website/images/logo.png +0 -0
- data/doc/website/index.html +82 -0
- data/doc/website/install.html +87 -0
- data/doc/website/quick_start.html +87 -0
- data/doc/website/tissue.html +85 -0
- data/doc/website/uom.html +10 -0
- data/lib/caruby.rb +3 -0
- data/lib/caruby/active_support/README.txt +2 -0
- data/lib/caruby/active_support/core_ext/string.rb +7 -0
- data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/caruby/active_support/inflections.rb +55 -0
- data/lib/caruby/active_support/inflector.rb +398 -0
- data/lib/caruby/cli/application.rb +36 -0
- data/lib/caruby/cli/command.rb +169 -0
- data/lib/caruby/csv/csv_mapper.rb +157 -0
- data/lib/caruby/csv/csvio.rb +185 -0
- data/lib/caruby/database.rb +252 -0
- data/lib/caruby/database/fetched_matcher.rb +66 -0
- data/lib/caruby/database/persistable.rb +432 -0
- data/lib/caruby/database/persistence_service.rb +162 -0
- data/lib/caruby/database/reader.rb +599 -0
- data/lib/caruby/database/saved_merger.rb +131 -0
- data/lib/caruby/database/search_template_builder.rb +59 -0
- data/lib/caruby/database/sql_executor.rb +75 -0
- data/lib/caruby/database/store_template_builder.rb +200 -0
- data/lib/caruby/database/writer.rb +469 -0
- data/lib/caruby/domain/annotatable.rb +25 -0
- data/lib/caruby/domain/annotation.rb +23 -0
- data/lib/caruby/domain/attribute_metadata.rb +447 -0
- data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
- data/lib/caruby/domain/merge.rb +91 -0
- data/lib/caruby/domain/properties.rb +95 -0
- data/lib/caruby/domain/reference_visitor.rb +289 -0
- data/lib/caruby/domain/resource_attributes.rb +528 -0
- data/lib/caruby/domain/resource_dependency.rb +205 -0
- data/lib/caruby/domain/resource_introspection.rb +159 -0
- data/lib/caruby/domain/resource_metadata.rb +117 -0
- data/lib/caruby/domain/resource_module.rb +285 -0
- data/lib/caruby/domain/uniquify.rb +38 -0
- data/lib/caruby/import/annotatable_class.rb +28 -0
- data/lib/caruby/import/annotation_class.rb +27 -0
- data/lib/caruby/import/annotation_module.rb +67 -0
- data/lib/caruby/import/java.rb +338 -0
- data/lib/caruby/migration/migratable.rb +167 -0
- data/lib/caruby/migration/migrator.rb +533 -0
- data/lib/caruby/migration/resource.rb +8 -0
- data/lib/caruby/migration/resource_module.rb +11 -0
- data/lib/caruby/migration/uniquify.rb +20 -0
- data/lib/caruby/resource.rb +969 -0
- data/lib/caruby/util/attribute_path.rb +46 -0
- data/lib/caruby/util/cache.rb +53 -0
- data/lib/caruby/util/class.rb +99 -0
- data/lib/caruby/util/collection.rb +1053 -0
- data/lib/caruby/util/controlled_value.rb +35 -0
- data/lib/caruby/util/coordinate.rb +75 -0
- data/lib/caruby/util/domain_extent.rb +49 -0
- data/lib/caruby/util/file_separator.rb +65 -0
- data/lib/caruby/util/inflector.rb +20 -0
- data/lib/caruby/util/log.rb +95 -0
- data/lib/caruby/util/math.rb +12 -0
- data/lib/caruby/util/merge.rb +59 -0
- data/lib/caruby/util/module.rb +34 -0
- data/lib/caruby/util/options.rb +92 -0
- data/lib/caruby/util/partial_order.rb +36 -0
- data/lib/caruby/util/person.rb +119 -0
- data/lib/caruby/util/pretty_print.rb +184 -0
- data/lib/caruby/util/properties.rb +112 -0
- data/lib/caruby/util/stopwatch.rb +66 -0
- data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
- data/lib/caruby/util/transitive_closure.rb +45 -0
- data/lib/caruby/util/tree.rb +48 -0
- data/lib/caruby/util/trie.rb +37 -0
- data/lib/caruby/util/uniquifier.rb +30 -0
- data/lib/caruby/util/validation.rb +48 -0
- data/lib/caruby/util/version.rb +56 -0
- data/lib/caruby/util/visitor.rb +351 -0
- data/lib/caruby/util/weak_hash.rb +36 -0
- data/lib/caruby/version.rb +3 -0
- metadata +186 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'caruby/util/collection'
|
2
|
+
require 'caruby/util/validation'
|
3
|
+
require 'caruby/util/merge'
|
4
|
+
|
5
|
+
# Options is a utility class to support method options.
|
6
|
+
class Options
|
7
|
+
# Returns the value of option in options as follows:
|
8
|
+
# * If options is a hash which contains the option key, then this method returns
|
9
|
+
# the option value. A non-collection options[option] value is wrapped as a singleton
|
10
|
+
# collection to conform to a collection default type, as shown in the example below.
|
11
|
+
# * If options equals the option symbol, then this method returns +true+.
|
12
|
+
# * If options is an Array of symbols which includes the given option, then this method
|
13
|
+
# returns +true+.
|
14
|
+
# * Otherwise, this method returns the default.
|
15
|
+
#
|
16
|
+
# If default is nil and a block is given to this method, then the default is determined
|
17
|
+
# by calling the block with no arguments. The block can also be used to raise a missing
|
18
|
+
# option exception, e.g.:
|
19
|
+
# Options.get(:userid, options) { raise RuntimeError.new("Missing required option: userid") }
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# Options.get(:create, {:create => true}) #=> true
|
23
|
+
# Options.get(:create, :create) #=> true
|
24
|
+
# Options.get(:create, [:create, :compress]) #=> true
|
25
|
+
# Options.get(:create, nil) #=> nil
|
26
|
+
# Options.get(:create, nil, :false) #=> false
|
27
|
+
# Options.get(:create, nil, :true) #=> true
|
28
|
+
# Options.get(:values, nil, []) #=> []
|
29
|
+
# Options.get(:values, {:values => :a}, []) #=> [:a]
|
30
|
+
def self.get(option, options, default=nil, &block)
|
31
|
+
return default(default, &block) if options.nil?
|
32
|
+
case options
|
33
|
+
when Hash then
|
34
|
+
value = options[option]
|
35
|
+
value.nil? ? default(default, &block) : value
|
36
|
+
when Enumerable then
|
37
|
+
options.include?(option) ? true : default(default, &block)
|
38
|
+
when Symbol then
|
39
|
+
option == options ? true : default(default, &block)
|
40
|
+
else
|
41
|
+
raise ArgumentError.new("Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Merges the others options with options and returns the new merged option hash.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Options.merge(nil, :create) #=> {:create => :true}
|
49
|
+
# Options.merge(:create, :optional => :a, :required => :b) #=> {:create => :true, :optional => :a, :required => :b}
|
50
|
+
# Options.merge({:required => [:b]}, :required => [:c]) #=> {:required => [:b, :c]}
|
51
|
+
def self.merge(options, others)
|
52
|
+
options = options.dup if Hash === options
|
53
|
+
self.merge!(options, others)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Merges the others options into the given options and returns the created or modified option hash.
|
57
|
+
# This method differs from {Options.merge} by modifying an existing options hash.
|
58
|
+
def self.merge!(options, others)
|
59
|
+
to_hash(options).merge!(to_hash(others)) { |key, oldval, newval| oldval.respond_to?(:merge) ? oldval.merge(newval) : newval }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the options as a hash. If options is already a hash, then this method returns hash.
|
63
|
+
# * If options is a Symbol _s_, then this method returns +{+_s_+=>true}+.
|
64
|
+
# * An Array of Symbols is enumerated as individual Symbol options.
|
65
|
+
# * If options is nil, then this method returns a new empty hash.
|
66
|
+
def self.to_hash(options)
|
67
|
+
return Hash.new if options.nil?
|
68
|
+
case options
|
69
|
+
when Hash then
|
70
|
+
options
|
71
|
+
when Array then
|
72
|
+
options.to_hash { |item| Symbol === item or raise ArgumentError.new("Option is not supported; expected Symbol, found: #{options.class}") }
|
73
|
+
when Symbol then
|
74
|
+
{options => true}
|
75
|
+
else
|
76
|
+
raise ArgumentError.new("Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Raises a ValidationError if the given options are not in the given allowable choices.
|
81
|
+
def self.validate(options, choices)
|
82
|
+
to_hash(options).each_key do |opt|
|
83
|
+
raise ValidationError.new("Option is not supported: #{opt}") unless choices.include?(opt)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def self.default(value)
|
90
|
+
value.nil? && block_given? ? yield : value
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# A PartialOrder is a Comparable which restricted scope. Classes wich include PartialOrder
|
2
|
+
# are required to implement the <=> operator with the following semantics:
|
3
|
+
# * _a_ <=> _b_ returns -1, 0, or 1 if a and b are comparable, nil otherwise
|
4
|
+
# A PartialOrder thus relaxes comparison symmetry, e.g.
|
5
|
+
# a < b
|
6
|
+
# does not imply
|
7
|
+
# b >= a.
|
8
|
+
# Example:
|
9
|
+
# module Queued
|
10
|
+
# attr_reader :queue
|
11
|
+
# def <=>(other)
|
12
|
+
# raise TypeError.new("Comparison argument is not another Queued item") unless Queued == other
|
13
|
+
# queue.index(self) <=> queue.index(other) if queue.equal?(other.queue)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# q1 = [a, b] # a, b are Queued
|
17
|
+
# q2 = [c] # c is a Queued
|
18
|
+
# a < b #=> true
|
19
|
+
# b < c #=> nil
|
20
|
+
module PartialOrder
|
21
|
+
include Comparable
|
22
|
+
|
23
|
+
Comparable.instance_methods(false).each do |m|
|
24
|
+
define_method(m.to_sym) do |other|
|
25
|
+
self <=> other ? super : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if other is an instance of this object's class and other == self,
|
29
|
+
# false otherwise.
|
30
|
+
def eql?(other)
|
31
|
+
self.class === other and super
|
32
|
+
end
|
33
|
+
|
34
|
+
alias :== :eql?
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'caruby/util/validation'
|
2
|
+
|
3
|
+
# Mix-in for standard Person attributes.
|
4
|
+
module CaRuby
|
5
|
+
module Person
|
6
|
+
class Name
|
7
|
+
include Validation
|
8
|
+
|
9
|
+
attr_accessor :salutation, :qualifier, :credentials
|
10
|
+
attr_reader :first, :last, :middle
|
11
|
+
|
12
|
+
# Creates a new Name with the required last and optional first and middle components.
|
13
|
+
def initialize(last, first=nil, middle=nil)
|
14
|
+
# replace empty with nil
|
15
|
+
@first = first unless first == ''
|
16
|
+
@last = last unless last == ''
|
17
|
+
@middle = middle unless middle == ''
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns this Name as an array consisting of the first, middle and last fields.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Person.parse("Abe Lincoln").to_a #=> ["Abe", nil, "Lincoln"]
|
24
|
+
def to_a
|
25
|
+
[@first, @middle, @last]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns this Name in the format [Salutation] First [Middle] Last[, Credentials].
|
29
|
+
def to_s
|
30
|
+
name_s = [salutation, first, middle, last, qualifier].reject { |part| part.nil? }.join(' ')
|
31
|
+
name_s << ', ' << credentials if credentials
|
32
|
+
name_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns whether this Person's first, middle and last name components equal the other Person's.
|
36
|
+
def ==(other)
|
37
|
+
self.class == other.class and first == other.first and middle == other.middle and last == other.last
|
38
|
+
end
|
39
|
+
|
40
|
+
alias :inspect :to_s
|
41
|
+
|
42
|
+
# Parses the name_s String into a Name. The name can be in one of the following formats:
|
43
|
+
# * last, first middle
|
44
|
+
# * [salutation] [first [middle]] last [qualifier] [, credentials]
|
45
|
+
# where _salutation_ ends in a period.
|
46
|
+
#
|
47
|
+
# Examples:
|
48
|
+
# * Longfellow, Henry Wadsworth
|
49
|
+
# * Longfellow, Henry Gallifant Wadsworth
|
50
|
+
# * Henry Longfellow
|
51
|
+
# * Longfellow
|
52
|
+
# * Mr. Henry Wadsworth Longfellow III, MD, Ph.D.
|
53
|
+
def self.parse(name_s)
|
54
|
+
return if name_s.blank?
|
55
|
+
# the name component variables
|
56
|
+
first = middle = last = salutation = qualifier = credentials = nil
|
57
|
+
# split into comma-delimited tokens
|
58
|
+
tokens = name_s.split(',')
|
59
|
+
# the word(s) before the first comma
|
60
|
+
before_comma = tokens[0].split(' ')
|
61
|
+
# if this is a last, first middle format, then parse it that way.
|
62
|
+
# otherwise the format is [salutation] [first [middle]] last [qualifier] [credentials]
|
63
|
+
if before_comma.size == 1 then
|
64
|
+
last = before_comma[0]
|
65
|
+
if tokens.size > 1 then
|
66
|
+
after_comma = tokens[1].split(' ')
|
67
|
+
first = after_comma.shift
|
68
|
+
middle = after_comma.join(' ')
|
69
|
+
end
|
70
|
+
else
|
71
|
+
# extract the salutation from the front, if any
|
72
|
+
salutation = before_comma.shift if salutation?(before_comma[0])
|
73
|
+
# extract the qualifier from the end, if any
|
74
|
+
qualifier = before_comma.pop if qualifier?(before_comma[-1])
|
75
|
+
# extract the last name from the end
|
76
|
+
last = before_comma.pop
|
77
|
+
# extract the first name from the front
|
78
|
+
first = before_comma.shift
|
79
|
+
# the middle name is whatever is left before the comma
|
80
|
+
middle = before_comma.join(' ')
|
81
|
+
# the credentials are the comma-delimited words after the first comma
|
82
|
+
credentials = tokens[1..-1].join(',').strip
|
83
|
+
end
|
84
|
+
# if there is only one name field, then it is the last name
|
85
|
+
if last.nil? then
|
86
|
+
last = first
|
87
|
+
first = nil
|
88
|
+
end
|
89
|
+
# make the name
|
90
|
+
name = self.new(last, first, middle)
|
91
|
+
name.salutation = salutation
|
92
|
+
name.qualifier = qualifier
|
93
|
+
name.credentials = credentials
|
94
|
+
name
|
95
|
+
end
|
96
|
+
|
97
|
+
# Raises ValidationError if there is neither a first nor a last name
|
98
|
+
# or if there is a middle name but no first name.
|
99
|
+
def validate
|
100
|
+
if last.nil? and first.nil? then
|
101
|
+
raise ValidationError.new("Name is missing both the first and last fields")
|
102
|
+
end
|
103
|
+
if !middle.nil? and first.nil? then
|
104
|
+
raise ValidationError.new("Name with middle field #{middle} is missing the first field")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns whether s ends in a period.
|
109
|
+
def self.salutation?(s)
|
110
|
+
s =~ /\.$/
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns whether s is Jr., Sr., II, III, etc.
|
114
|
+
def self.qualifier?(s)
|
115
|
+
s and (s =~ /[J|S]r[.]?/ or s =~ /\AI+\Z/)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'date'
|
3
|
+
require 'pp'
|
4
|
+
require 'stringio'
|
5
|
+
require 'caruby/util/options'
|
6
|
+
require 'caruby/util/collection'
|
7
|
+
require 'caruby/util/inflector'
|
8
|
+
|
9
|
+
class PrettyPrint
|
10
|
+
# Fixes the standard prettyprint gem SingleLine to add an output accessor and an optional output argument to {#initialize}.
|
11
|
+
class SingleLine
|
12
|
+
attr_reader :output
|
13
|
+
|
14
|
+
alias :base__initialize :initialize
|
15
|
+
private :base__initialize
|
16
|
+
|
17
|
+
# Allow output to be optional, defaulting to ''
|
18
|
+
def initialize(output='', maxwidth=nil, newline=nil)
|
19
|
+
base__initialize(output, maxwidth, newline)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# A PrintWrapper prints arguments by calling a printer proc.
|
25
|
+
class PrintWrapper < Proc
|
26
|
+
# Creates a new PrintWrapper on the given arguments.
|
27
|
+
def initialize(*args)
|
28
|
+
super()
|
29
|
+
@args = args
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sets the arguments to wrap with this wrapper's print block and returns self.
|
33
|
+
def wrap(*args)
|
34
|
+
@args = args
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calls this PrintWrapper's print procedure on its arguments.
|
39
|
+
def to_s
|
40
|
+
@args.empty? ? 'nil' : call(*@args)
|
41
|
+
end
|
42
|
+
|
43
|
+
alias :inspect :to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
class Object
|
47
|
+
# Prints this object's class demodulized name and object id.
|
48
|
+
def print_class_and_id
|
49
|
+
"#{self.class.qp}@#{object_id}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# qp, an abbreviation for quick-print, calls {#print_class_and_id} in this base implementation.
|
53
|
+
alias :qp :print_class_and_id
|
54
|
+
|
55
|
+
# Formats this object as a String with PrettyPrint.
|
56
|
+
# If the :single_line option is set, then the output is printed to a single line.
|
57
|
+
def pp_s(options=nil)
|
58
|
+
s = StringIO.new
|
59
|
+
if Options.get(:single_line, options) then
|
60
|
+
PP.singleline_pp(self, s)
|
61
|
+
else
|
62
|
+
PP.pp(self, s)
|
63
|
+
end
|
64
|
+
s.rewind
|
65
|
+
s.read.chomp
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Numeric
|
70
|
+
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
71
|
+
alias :qp :to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
class String
|
75
|
+
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
76
|
+
alias :qp :to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
class TrueClass
|
80
|
+
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
81
|
+
alias :qp :to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
class FalseClass
|
85
|
+
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
86
|
+
alias :qp :to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
class NilClass
|
90
|
+
# qp, an abbreviation for quick-print, is an alias for {#inspect} in this NilClass.
|
91
|
+
alias :qp :inspect
|
92
|
+
end
|
93
|
+
|
94
|
+
class Symbol
|
95
|
+
# qp, an abbreviation for quick-print, is an alias for {#inspect} in this Symbol class.
|
96
|
+
alias :qp :inspect
|
97
|
+
end
|
98
|
+
|
99
|
+
class Module
|
100
|
+
# qp, an abbreviation for quick-print, prints this module's name unqualified by a parent module prefix.
|
101
|
+
def qp
|
102
|
+
name[/\w+$/]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
module Enumerable
|
107
|
+
# qp, short for quick-print, prints a collection Enumerable with a filter that calls qp on each item.
|
108
|
+
# Non-collection Enumerables delegate to the superclass method.
|
109
|
+
def qp
|
110
|
+
wrap { |item| item.qp }.pp_s
|
111
|
+
end
|
112
|
+
|
113
|
+
# If the transformer block is given to this method, then the transformer block to each
|
114
|
+
# enumerated item before pretty-printing the result.
|
115
|
+
def pp_s(options=nil, &transformer)
|
116
|
+
# delegate to Object if no block
|
117
|
+
return super(options) unless block_given?
|
118
|
+
# make a print wrapper
|
119
|
+
wrapper = PrintWrapper.new { |item| yield item }
|
120
|
+
# print using the wrapper on each item
|
121
|
+
wrap { |item| wrapper.wrap(item) }.pp_s(options)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Pretty-prints the content within brackets, as is done by the Array pretty printer.
|
125
|
+
def pretty_print(q)
|
126
|
+
q.group(1, '[', ']') {
|
127
|
+
q.seplist(self) {|v|
|
128
|
+
q.pp v
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
# Pretty-prints the cycle within brackets, as is done by the Array pretty printer.
|
134
|
+
def pretty_print_cycle(q)
|
135
|
+
q.text(empty? ? '[]' : '[...]')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module Hashable
|
140
|
+
# qp, short for quick-print, prints this Hashable with a filter that calls qp on each key and value.
|
141
|
+
def qp
|
142
|
+
qph = {}
|
143
|
+
each { |k, v| qph[k.qp] = v.qp }
|
144
|
+
qph.pp_s
|
145
|
+
end
|
146
|
+
|
147
|
+
def pretty_print(q)
|
148
|
+
Hash === self ? q.pp_hash(self) : q.pp_hash(to_hash)
|
149
|
+
end
|
150
|
+
|
151
|
+
def pretty_print_cycle(q)
|
152
|
+
q.text(empty? ? '{}' : '{...}')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class String
|
157
|
+
# Pretty-prints this String using the Object pretty_print rather than Enumerable pretty_print.
|
158
|
+
def pretty_print(q)
|
159
|
+
q.text self
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class DateTime
|
164
|
+
def pretty_print(q)
|
165
|
+
q.text(strftime)
|
166
|
+
end
|
167
|
+
|
168
|
+
# qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
|
169
|
+
alias :qp :to_s
|
170
|
+
end
|
171
|
+
|
172
|
+
class Set
|
173
|
+
# Formats this set using {Enumerable#pretty_print}.
|
174
|
+
def pretty_print(q)
|
175
|
+
# mark this object as visited; this fragment is inferred from pp.rb and is necessary to detect a cycle
|
176
|
+
Thread.current[:__inspect_key__] << __id__
|
177
|
+
to_a.pretty_print(q)
|
178
|
+
end
|
179
|
+
|
180
|
+
# The pp.rb default pretty printing method for general objects that are detected as part of a cycle.
|
181
|
+
def pretty_print_cycle(q)
|
182
|
+
to_a.pretty_print_cycle(q)
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'set'
|
3
|
+
require 'caruby/util/log'
|
4
|
+
require 'caruby/util/pretty_print'
|
5
|
+
require 'caruby/util/collection'
|
6
|
+
require 'caruby/util/merge'
|
7
|
+
|
8
|
+
module CaRuby
|
9
|
+
# Exception raised if a configuration property is missing or invalid.
|
10
|
+
class ConfigurationError < RuntimeError; end
|
11
|
+
|
12
|
+
# A Properties instance encapsulates a properties file accessor. The properties are stored
|
13
|
+
# in YAML format.
|
14
|
+
class Properties < Hash
|
15
|
+
# Creates a new Properties object. If the file argument is given, then the properties are loaded from
|
16
|
+
# that file.
|
17
|
+
#
|
18
|
+
# Supported options include the following:
|
19
|
+
# * :merge - the properties which are merged rather than replaced when loaded from the property files
|
20
|
+
# * :required - the properties which must be set when the property files are loaded
|
21
|
+
# * :array - the properties whose comma-separated String input value is converted to an array value
|
22
|
+
def initialize(file=nil, options=nil)
|
23
|
+
super()
|
24
|
+
@merge_properties = Options.get(:merge, options, []).to_set
|
25
|
+
@required_properties = Options.get(:required, options, []).to_set
|
26
|
+
@array_properties = Options.get(:array, options, []).to_set
|
27
|
+
load_properties(file) if file
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a new Hash which associates this Properties' keys converted to symbols to the respective values.
|
31
|
+
def symbolize
|
32
|
+
Hash.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns whether the property key or its alternate is defined.
|
36
|
+
def has_property?(key)
|
37
|
+
has_key?(key) or has_key?(alternate_key(key))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the property value for the key. If there is no String key entry but there is a
|
41
|
+
# alternate key entry, then this method returns the alternate key value.
|
42
|
+
def [](key)
|
43
|
+
super(key) or super(alternate_key(key))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the property value for the key. If there is no key entry but there is an
|
47
|
+
# alternate key entry, then alternate key entry is set.
|
48
|
+
def []=(key, value)
|
49
|
+
return super if has_key?(key)
|
50
|
+
alt = alternate_key(key)
|
51
|
+
has_key?(alt) ? super(alt, value) : super
|
52
|
+
end
|
53
|
+
|
54
|
+
# Deletes the entry for the given property key or its alternate.
|
55
|
+
def delete(key)
|
56
|
+
key = alternate_key(key) unless has_key?(key)
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
# Loads the specified properties file, replacing any existing properties.
|
61
|
+
#
|
62
|
+
# If a key is included in this Properties merge_properties array, then the
|
63
|
+
# old value for that key will be merged with the new value for that key
|
64
|
+
# rather than replaced.
|
65
|
+
#
|
66
|
+
# This method reloads a property file that has already been loaded.
|
67
|
+
#
|
68
|
+
# Raises ConfigurationError if file doesn't exist or couldn't be parsed.
|
69
|
+
def load_properties(file)
|
70
|
+
raise ConfigurationError.new("Properties file not found: #{File.expand_path(file)}") unless File.exists?(file)
|
71
|
+
logger.debug { "Loading properties file #{file}..." }
|
72
|
+
properties = {}
|
73
|
+
begin
|
74
|
+
YAML::load_file(file).each { |key, value| properties[key.to_sym] = value }
|
75
|
+
rescue
|
76
|
+
raise ConfigurationError.new("Could not read properties file #{file}: " + $!)
|
77
|
+
end
|
78
|
+
# Uncomment the following line to print detail properties.
|
79
|
+
#logger.debug { "#{file} properties:\n#{properties.pp_s}" }
|
80
|
+
# parse comma-delimited string values of array properties into arrays
|
81
|
+
@array_properties.each do |key|
|
82
|
+
value = properties[key]
|
83
|
+
if String === value then
|
84
|
+
properties[key] = value.split(/,\s*/)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
# if the key is a merge property key, then perform a deep merge.
|
88
|
+
# otherwise, do a shallow merge of the property value into this property hash.
|
89
|
+
deep, shallow = properties.partition { |key, value| @merge_properties.include?(key) }
|
90
|
+
merge!(deep, :deep)
|
91
|
+
merge!(shallow)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Returns key as a Symbol if key is a String, key as a String if key is a Symbol,
|
97
|
+
# or nil if key is neither a String nor a Symbol.
|
98
|
+
def alternate_key(key)
|
99
|
+
case key
|
100
|
+
when String then key.to_sym
|
101
|
+
when Symbol then key.to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Validates that the required properties exist.
|
106
|
+
def validate_properties
|
107
|
+
@required_properties.each do |key|
|
108
|
+
raise CaRuby::ConfigurationError.new("A required #{@application} property was not found: #{key}") unless has_property?(key)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|