jinx 2.1.1 → 2.1.2
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.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
|