caruby-core 1.4.1
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.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
|