jinx 2.1.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.md +4 -0
- data/lib/jinx.rb +0 -1
- data/lib/jinx/cli/application.rb +3 -3
- data/lib/jinx/cli/command.rb +1 -1
- data/lib/jinx/helpers/array.rb +3 -3
- data/lib/jinx/helpers/associative.rb +41 -0
- data/lib/jinx/helpers/collections.rb +0 -1
- data/lib/jinx/helpers/enumerable.rb +5 -2
- data/lib/jinx/helpers/hash.rb +1 -1
- data/lib/jinx/helpers/hashable.rb +1 -7
- data/lib/jinx/helpers/inflector.rb +1 -1
- data/lib/jinx/helpers/log.rb +63 -11
- data/lib/jinx/helpers/math.rb +20 -11
- data/lib/jinx/helpers/options.rb +4 -4
- data/lib/jinx/helpers/pretty_print.rb +0 -1
- data/lib/jinx/helpers/transitive_closure.rb +1 -1
- data/lib/jinx/helpers/uniquifier.rb +50 -17
- data/lib/jinx/helpers/visitor.rb +3 -3
- data/lib/jinx/import/java.rb +1 -1
- data/lib/jinx/importer.rb +3 -2
- data/lib/jinx/metadata/attribute_enumerator.rb +1 -1
- data/lib/jinx/metadata/dependency.rb +3 -3
- data/lib/jinx/metadata/introspector.rb +46 -42
- data/lib/jinx/metadata/inverse.rb +17 -1
- data/lib/jinx/metadata/java_property.rb +10 -10
- data/lib/jinx/metadata/propertied.rb +19 -16
- data/lib/jinx/metadata/property.rb +11 -11
- data/lib/jinx/resource.rb +86 -14
- data/lib/jinx/resource/match_visitor.rb +7 -5
- data/lib/jinx/resource/merge_visitor.rb +3 -10
- data/lib/jinx/resource/mergeable.rb +16 -16
- data/lib/jinx/resource/reference_enumerator.rb +0 -1
- data/lib/jinx/resource/reference_path_visitor.rb +1 -1
- data/lib/jinx/resource/reference_visitor.rb +5 -6
- data/lib/jinx/resource/unique.rb +1 -1
- data/lib/jinx/version.rb +1 -1
- data/test/lib/jinx/helpers/associative_test.rb +26 -0
- data/test/lib/jinx/helpers/collections_test.rb +14 -2
- data/test/lib/jinx/helpers/uniquifier_test.rb +11 -0
- metadata +9 -8
- data/Gemfile.lock +0 -27
- data/lib/jinx/helpers/error.rb +0 -15
- data/lib/jinx/helpers/key_transformer_hash.rb +0 -43
data/History.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
This history lists major release themes. See the GitHub commits (https://github.com/jinx/core)
|
2
2
|
for change details.
|
3
3
|
|
4
|
+
2.1.2 / 2012-06-12
|
5
|
+
------------------
|
6
|
+
* Fine-tune introspection.
|
7
|
+
|
4
8
|
2.1.1 / 2012-04-13
|
5
9
|
------------------
|
6
10
|
* Initial public release spun off from caruby/core.
|
data/lib/jinx.rb
CHANGED
data/lib/jinx/cli/application.rb
CHANGED
@@ -19,16 +19,16 @@ module Jinx
|
|
19
19
|
# * improve the output messages
|
20
20
|
# * print an exception to stderr as well as the log
|
21
21
|
def start(*args, &block)
|
22
|
-
|
22
|
+
rc = 1
|
23
23
|
begin
|
24
|
-
|
24
|
+
rc = run(*args, &block)
|
25
25
|
rescue
|
26
26
|
log(FATAL, "#{@appname} detected an exception: #{$!}\n#{$@.qp}")
|
27
27
|
msg = "#{@appname} was unsuccessful: #{$!}."
|
28
28
|
msg += "\nSee the log #{Log.instance.file} for more information." if Log.instance.file
|
29
29
|
$stderr.puts msg
|
30
30
|
ensure
|
31
|
-
log(INFO, "#{@appname} completed with status #{
|
31
|
+
log(INFO, "#{@appname} completed with status #{rc}.")
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
data/lib/jinx/cli/command.rb
CHANGED
@@ -105,7 +105,7 @@ module Jinx
|
|
105
105
|
|
106
106
|
# @param [{Symbol => Object}] opts the option => value hash
|
107
107
|
def call_executor(opts)
|
108
|
-
if @executor.nil? then
|
108
|
+
if @executor.nil? then raise CommandError.new("Command #{self} does not have an execution block") end
|
109
109
|
@executor.call(opts)
|
110
110
|
end
|
111
111
|
|
data/lib/jinx/helpers/array.rb
CHANGED
@@ -4,7 +4,7 @@ class Array
|
|
4
4
|
# The EMPTY_ARRAY constant is an immutable empty array, used primarily as a default argument.
|
5
5
|
class << EMPTY_ARRAY ||= Array.new
|
6
6
|
def <<(value)
|
7
|
-
|
7
|
+
raise NotImplementedError.new("Modification of the constant empty array is not supported")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -64,7 +64,7 @@ class Array
|
|
64
64
|
def to_assoc_hash
|
65
65
|
hash = {}
|
66
66
|
each do |item|
|
67
|
-
|
67
|
+
raise ArgumentError.new("Array member must be an array: #{item.pp_s(:single_line)}") unless Array === item
|
68
68
|
key = item.first
|
69
69
|
if item.size < 2 then
|
70
70
|
value = nil
|
@@ -100,7 +100,7 @@ class Array
|
|
100
100
|
rescue NoMethodError
|
101
101
|
raise e
|
102
102
|
rescue
|
103
|
-
|
103
|
+
raise ArgumentError.new("Can't convert #{other.class.name} to array")
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'jinx/helpers/options'
|
2
|
+
module Jinx
|
3
|
+
# An Associative object implements a {#[]} method.
|
4
|
+
class Associative
|
5
|
+
# @yield [key] the associated oject
|
6
|
+
# @yieldparam key the key to find
|
7
|
+
def initialize(&accessor)
|
8
|
+
@accessor = accessor
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param key the key to find
|
12
|
+
# @return the associated object
|
13
|
+
def [](key)
|
14
|
+
@accessor.call(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @yield [key] the associated oject
|
18
|
+
# @yieldparam key the key to find
|
19
|
+
# @return [Associative] a new Associative with a +[]=+ writer method
|
20
|
+
def writer(&writer)
|
21
|
+
Writable.new(self, &writer)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
class Writable < Associative
|
28
|
+
def initialize(base, &writer)
|
29
|
+
@base = base
|
30
|
+
@writer = writer
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
@base[key]
|
35
|
+
end
|
36
|
+
|
37
|
+
def []=(key, value)
|
38
|
+
@writer.call(key, value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -31,7 +31,7 @@ module Enumerable
|
|
31
31
|
# @raise [ArgumentError] if the generator block is not given
|
32
32
|
# @see #hashify
|
33
33
|
def to_compact_hash
|
34
|
-
|
34
|
+
raise ArgumentError.new("Compact hash builder is missing the value generator block") unless block_given?
|
35
35
|
to_compact_hash_with_index { |item, index| yield item }
|
36
36
|
end
|
37
37
|
|
@@ -159,6 +159,9 @@ module Enumerable
|
|
159
159
|
# Returns an Enumerable which iterates over items in this Enumerable and the other Enumerable in sequence.
|
160
160
|
# Unlike the Array plus (+) operator, {#union} reflects changes to the underlying enumerators.
|
161
161
|
#
|
162
|
+
# @quirk Cucumber Cucumber defines it's own Enumerable union monkey-patch. Work around this in the short
|
163
|
+
# term by trying to call the super first.
|
164
|
+
#
|
162
165
|
# @example
|
163
166
|
# a = [1, 2]
|
164
167
|
# b = [4, 5]
|
@@ -170,7 +173,7 @@ module Enumerable
|
|
170
173
|
# @param [Enumerable] other the Enumerable to compose with this Enumerable
|
171
174
|
# @return [Enumerable] an enumerator over self followed by other
|
172
175
|
def union(other)
|
173
|
-
Jinx::MultiEnumerator.new(self, other)
|
176
|
+
super rescue Jinx::MultiEnumerator.new(self, other)
|
174
177
|
end
|
175
178
|
|
176
179
|
alias :+ :union
|
data/lib/jinx/helpers/hash.rb
CHANGED
@@ -6,7 +6,7 @@ class Hash
|
|
6
6
|
# The EMPTY_HASH constant is an immutable empty hash, used primarily as a default argument.
|
7
7
|
class << EMPTY_HASH ||= Hash.new
|
8
8
|
def []=(key, value)
|
9
|
-
|
9
|
+
raise NotImplementedError.new("Modification of the constant empty hash is not supported")
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -392,7 +392,7 @@ module Jinx
|
|
392
392
|
attr_reader :components
|
393
393
|
|
394
394
|
def initialize(*hashes)
|
395
|
-
if hashes.include?(nil) then
|
395
|
+
if hashes.include?(nil) then raise ArgumentError.new("MultiHash is missing a component hash.") end
|
396
396
|
@components = hashes
|
397
397
|
end
|
398
398
|
|
@@ -465,12 +465,6 @@ module Jinx
|
|
465
465
|
@xfm = transformer
|
466
466
|
end
|
467
467
|
|
468
|
-
# @param key the untransformed hash key
|
469
|
-
# @return the value for the transformed key
|
470
|
-
def [](key)
|
471
|
-
@base[@xfm.call(@base[key])]
|
472
|
-
end
|
473
|
-
|
474
468
|
# @yield [key, value] operate on the transformed key and value
|
475
469
|
# @yieldparam key the transformed hash key
|
476
470
|
# @yieldparam value the hash value
|
@@ -7,7 +7,7 @@ class String
|
|
7
7
|
# "rose".quantify(3) #=> "roses"
|
8
8
|
# "rose".quantify(1 #=> "rose"
|
9
9
|
def quantify(quantity)
|
10
|
-
|
10
|
+
raise ArgumentError.new("Missing quantity argument") if quantity.nil?
|
11
11
|
"#{quantity} #{quantity == 1 ? self : pluralize}"
|
12
12
|
end
|
13
13
|
|
data/lib/jinx/helpers/log.rb
CHANGED
@@ -3,15 +3,19 @@ require 'singleton'
|
|
3
3
|
require 'ftools'
|
4
4
|
require 'jinx/helpers/collections'
|
5
5
|
require 'jinx/helpers/options'
|
6
|
+
require 'jinx/helpers/inflector'
|
6
7
|
|
8
|
+
# @param [String, IO, nil] dev the optional log file or device
|
7
9
|
# @return [Jinx::MultilineLogger] the global logger
|
8
|
-
def logger
|
9
|
-
Jinx.logger
|
10
|
+
def logger(dev=nil, opts=nil)
|
11
|
+
Jinx.logger(dev, opts)
|
10
12
|
end
|
11
13
|
|
12
14
|
module Jinx
|
13
|
-
# @
|
14
|
-
|
15
|
+
# @param [String, IO, nil] dev the optional log file or device
|
16
|
+
# @return [Jinx::MultilineLogger] the global logger
|
17
|
+
def self.logger(dev=nil, opts=nil)
|
18
|
+
Log.instance.open(dev, opts) if dev or opts
|
15
19
|
Log.instance.logger
|
16
20
|
end
|
17
21
|
|
@@ -45,17 +49,27 @@ module Jinx
|
|
45
49
|
class Log
|
46
50
|
include Singleton
|
47
51
|
|
48
|
-
# Opens the log.
|
52
|
+
# Opens the log. The default log location is determined from the application name.
|
53
|
+
# The application name is the value of the +:app+ option, or +Jinx+ by default.
|
54
|
+
# For an application +MyApp+, the log location is determined as follows:
|
55
|
+
# * +/var/log/my_app.log+ for Linux
|
56
|
+
# * +%LOCALAPPDATA%\MyApp\log\MyApp.log+ for Windows
|
57
|
+
# * +./log/MyApp.log+ otherwise
|
58
|
+
# The default file must be creatable or writable. If the device argument is not
|
59
|
+
# provided and there is no suitable default log file, then logging is disabled.
|
49
60
|
#
|
50
|
-
# @param [String, IO, nil] dev the log file or device
|
61
|
+
# @param [String, IO, nil] dev the log file or device
|
51
62
|
# @param [Hash, nil] opts the logger options
|
63
|
+
# @option opts [String] :app the application name
|
52
64
|
# @option opts [Integer] :shift_age the number of log files retained in the rotation
|
53
65
|
# @option opts [Integer] :shift_size the maximum size of each log file
|
54
66
|
# @option opts [Boolean] :debug whether to include debug messages in the log file
|
55
67
|
# @return [MultilineLogger] the global logger
|
56
68
|
def open(dev=nil, opts=nil)
|
57
69
|
raise RuntimeError.new("Log already open") if open?
|
58
|
-
|
70
|
+
dev, opts = nil, dev if Hash === dev
|
71
|
+
dev ||= default_log_file(Options.get(:app, opts))
|
72
|
+
FileUtils.mkdir_p(File.dirname(dev)) if String === dev
|
59
73
|
# default is 4-file rotation @ 16MB each
|
60
74
|
shift_age = Options.get(:shift_age, opts, 4)
|
61
75
|
shift_size = Options.get(:shift_size, opts, 16 * 1048576)
|
@@ -97,10 +111,48 @@ module Jinx
|
|
97
111
|
private
|
98
112
|
|
99
113
|
# Stream-lined log format.
|
100
|
-
FORMAT = %{%s [%s] %5s %s\n}
|
114
|
+
FORMAT = %{%s [%s] %5s %s\n}
|
115
|
+
|
116
|
+
# The default log file.
|
117
|
+
LINUX_LOG_DIR = '/var/log'
|
101
118
|
|
102
|
-
|
103
|
-
|
119
|
+
# Returns the log file, as described in {#open}.
|
120
|
+
#
|
121
|
+
# If the standard Linux log location exists, then try that.
|
122
|
+
# Otherwise, try the conventional Windows app data location.
|
123
|
+
# If all else fails, use the working directory.
|
124
|
+
#
|
125
|
+
# The file must be creatable or writable.
|
126
|
+
#
|
127
|
+
# @param [String, nil] app the application name (default +jinx+)
|
128
|
+
# @return [String] the file name
|
129
|
+
def default_log_file(app=nil)
|
130
|
+
app ||= 'Jinx'
|
131
|
+
default_linux_log_file(app) || default_windows_log_file(app) || "log/#{app}.log"
|
132
|
+
end
|
133
|
+
|
134
|
+
# @param [String] app the application name
|
135
|
+
# @return [String, nil] the default file name
|
136
|
+
def default_linux_log_file(app)
|
137
|
+
return unless File.exists?(LINUX_LOG_DIR)
|
138
|
+
base = app.underscore.gsub(' ', '_')
|
139
|
+
file = File.expand_path("#{base}.log", LINUX_LOG_DIR)
|
140
|
+
log = file if File.exists?(file) ? File.writable?(file) : File.writable?(LINUX_LOG_DIR)
|
141
|
+
log || '/dev/null'
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [String] app the application name
|
145
|
+
# @return [String, nil] the default file name
|
146
|
+
def default_windows_log_file(app)
|
147
|
+
# the conventional Windows app data location
|
148
|
+
app_dir = ENV['LOCALAPPDATA'] || return
|
149
|
+
dir = app_dir + "/#{app}/log"
|
150
|
+
file = File.expand_path("#{app}.log", dir)
|
151
|
+
if File.exists?(file) ? File.writable?(file) : (File.directory?(dir) ? File.writable?(dir) : File.writable?(app_dir)) then
|
152
|
+
file
|
153
|
+
else
|
154
|
+
'NUL'
|
155
|
+
end
|
104
156
|
end
|
105
157
|
end
|
106
|
-
end
|
158
|
+
end
|
data/lib/jinx/helpers/math.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Jinx
|
2
|
+
module Math
|
3
|
+
# @param [<Numeric>] the numbers to compare
|
4
|
+
# @return [Numeric] the smallest number
|
5
|
+
def self.min(*args)
|
6
|
+
args.inject { |m, n| m < n ? m : n }
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param [<Numeric>] the numbers to compare
|
10
|
+
# @return [Numeric] the largest number
|
11
|
+
def self.max(*args)
|
12
|
+
args.inject { |m, n| m < n ? n : m }
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param value the value to check
|
16
|
+
# @return [Boolean] whether the value is a Ruby or Java number
|
17
|
+
def self.numeric?(value)
|
18
|
+
Numeric === value or Java::JavaLang::Number === value
|
19
|
+
end
|
6
20
|
end
|
7
|
-
|
8
|
-
# Returns the minimum of this Numeric and the other Numerics.
|
9
|
-
def max(*others)
|
10
|
-
others.inject(self) { |max, other| other > max ? other : max }
|
11
|
-
end
|
12
|
-
end
|
21
|
+
end
|
data/lib/jinx/helpers/options.rb
CHANGED
@@ -17,7 +17,7 @@ class Options
|
|
17
17
|
# If default is nil and a block is given to this method, then the default is determined
|
18
18
|
# by calling the block with no arguments. The block can also be used to raise a missing
|
19
19
|
# option exception, e.g.:
|
20
|
-
# Options.get(:userid, options) {
|
20
|
+
# Options.get(:userid, options) { raise RuntimeError.new("Missing required option: userid") }
|
21
21
|
#
|
22
22
|
# @example
|
23
23
|
# Options.get(:create, {:create => true}) #=> true
|
@@ -41,7 +41,7 @@ class Options
|
|
41
41
|
when Symbol then
|
42
42
|
option == options or default(default, &block)
|
43
43
|
else
|
44
|
-
|
44
|
+
raise ArgumentError.new("Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -64,7 +64,7 @@ class Options
|
|
64
64
|
case opt
|
65
65
|
when Symbol then hash[opt] = true
|
66
66
|
when Hash then hash.merge!(opt)
|
67
|
-
else
|
67
|
+
else raise ArgumentError.new("Expected a symbol or hash option, found #{opt.qp}")
|
68
68
|
end
|
69
69
|
end
|
70
70
|
hash
|
@@ -74,7 +74,7 @@ class Options
|
|
74
74
|
# @raise [ValidationError] if the given options are not in the given allowable choices
|
75
75
|
def self.validate(options, choices)
|
76
76
|
to_hash(options).each_key do |opt|
|
77
|
-
|
77
|
+
raise ValidationError.new("Option is not supported: #{opt}") unless choices.include?(opt)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -29,7 +29,7 @@ class Object
|
|
29
29
|
# a.transitive_closure { |node| node.children }.to_a.join(", ") #=> a, b, c, d
|
30
30
|
# a.transitive_closure(:children).to_a.join(", ") #=> a, b, c, d
|
31
31
|
def transitive_closure(method=nil)
|
32
|
-
|
32
|
+
raise ArgumentError.new("Missing both a method argument and a block") if method.nil? and not block_given?
|
33
33
|
# If there is a method argument, then the transitive closure is based on that method.
|
34
34
|
# Otherwise, visit the closure in reverse depth-first order.
|
35
35
|
if method then
|
@@ -24,27 +24,60 @@ module Jinx
|
|
24
24
|
def initialize
|
25
25
|
@cache = Jinx::LazyHash.new { Hash.new }
|
26
26
|
end
|
27
|
-
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
|
32
|
-
|
27
|
+
|
28
|
+
# Returns a relatively unique String for the given base String object or
|
29
|
+
# (object, String value) pair. In the former case, each call returns a distinct value.
|
30
|
+
# In the latter case, successive calls of the same String value for the same object
|
31
|
+
# class return the same unique value.
|
32
|
+
#
|
33
|
+
# This method is useful to transform a String object key to a unique value for testing
|
34
|
+
# purposes.
|
35
|
+
#
|
36
|
+
# The unique value is comprised of a prefix and suffix. The prefix is the base value
|
37
|
+
# with spaces replaced by an underscore. The suffix is a {Jinx::Uniquifier.qualifier}
|
38
|
+
# converted to digits and lower-case letters, excluding the digits 0, 1 and characters
|
39
|
+
# l, o to avoid confusion.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# Jinx::Uniquifier.instance.uniquify('Groucho') #=> Groucho_wiafye6e
|
43
|
+
# Jinx::Uniquifier.instance.uniquify('Groucho') #=> Groucho_uqafye6e
|
44
|
+
# Jinx::Uniquifier.instance.uniquify('Groucho Marx') #=> Groucho_ay23ye6e
|
45
|
+
# Jinx::Uniquifier.instance.uniquify(person, 'Groucho') #=> Groucho_wy874e6e
|
46
|
+
# Jinx::Uniquifier.instance.uniquify(person, 'Groucho') #=> Groucho_wy87ye6e
|
47
|
+
#
|
48
|
+
# @param obj the object containing the value to uniquify
|
49
|
+
# @param value [String, nil] the value to make unique, or nil if the containing object is a String
|
50
|
+
# @return [String, nil] the new unique value, or nil if the containing object is a String
|
51
|
+
# and the given value is nil
|
52
|
+
def uniquify(obj, value=nil)
|
53
|
+
if String === obj then
|
54
|
+
to_unique(obj)
|
55
|
+
elsif value then
|
56
|
+
@cache[obj.class][value] ||= to_unique(value)
|
57
|
+
end
|
33
58
|
end
|
34
59
|
|
35
60
|
def clear
|
36
61
|
@cache.clear
|
37
62
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
CHARS = 'abcdefghijkmnpqrstuvwxyz23456789'
|
67
|
+
|
68
|
+
# @param value [String, nil] the value to make unique
|
69
|
+
# @return [String] the new unique value, or nil if the given value is nil
|
70
|
+
# @raise [ArgumentError] if value is neither a String nor nil
|
71
|
+
def to_unique(value)
|
72
|
+
return if value.nil?
|
73
|
+
raise ArgumentError.new("#{value.qp} is not a String") unless String === value
|
74
|
+
s = ''
|
75
|
+
n = Jinx::Uniquifier.qualifier
|
76
|
+
while n > 0 do
|
77
|
+
n, m = n.divmod(32)
|
78
|
+
s << CHARS[m]
|
79
|
+
end
|
80
|
+
[value.gsub(' ', '_'), s].join('_')
|
81
|
+
end
|
49
82
|
end
|
50
83
|
end
|