gorillib 0.4.1pre → 0.4.2pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +13 -10
  2. data/.rspec +1 -1
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +47 -0
  5. data/Gemfile +22 -19
  6. data/Guardfile +23 -9
  7. data/README.md +12 -12
  8. data/Rakefile +29 -40
  9. data/VERSION +1 -1
  10. data/examples/benchmark/factories_benchmark.rb +87 -0
  11. data/examples/builder/ironfan.rb +1 -19
  12. data/examples/hash/slicing_methods.rb +101 -0
  13. data/gorillib.gemspec +36 -35
  14. data/lib/gorillib/array/deep_compact.rb +4 -3
  15. data/lib/gorillib/array/simple_statistics.rb +76 -0
  16. data/lib/gorillib/base.rb +0 -1
  17. data/lib/gorillib/builder.rb +15 -30
  18. data/lib/gorillib/collection.rb +159 -57
  19. data/lib/gorillib/collection/model_collection.rb +136 -43
  20. data/lib/gorillib/datetime/parse.rb +4 -2
  21. data/lib/gorillib/{array → deprecated/array}/average.rb +0 -0
  22. data/lib/gorillib/{array → deprecated/array}/random.rb +2 -1
  23. data/lib/gorillib/{array → deprecated/array}/sorted_median.rb +0 -0
  24. data/lib/gorillib/{array → deprecated/array}/sorted_percentile.rb +0 -0
  25. data/lib/gorillib/deprecated/array/sorted_sample.rb +13 -0
  26. data/lib/gorillib/{metaprogramming → deprecated/metaprogramming}/aliasing.rb +0 -0
  27. data/lib/gorillib/enumerable/sum.rb +3 -3
  28. data/lib/gorillib/exception/raisers.rb +92 -22
  29. data/lib/gorillib/factories.rb +550 -0
  30. data/lib/gorillib/hash/mash.rb +15 -58
  31. data/lib/gorillib/hashlike/deep_compact.rb +2 -2
  32. data/lib/gorillib/hashlike/slice.rb +55 -40
  33. data/lib/gorillib/model.rb +5 -3
  34. data/lib/gorillib/model/base.rb +33 -119
  35. data/lib/gorillib/model/defaults.rb +58 -14
  36. data/lib/gorillib/model/errors.rb +10 -0
  37. data/lib/gorillib/model/factories.rb +1 -367
  38. data/lib/gorillib/model/field.rb +40 -18
  39. data/lib/gorillib/model/fixup.rb +16 -0
  40. data/lib/gorillib/model/positional_fields.rb +35 -0
  41. data/lib/gorillib/model/schema_magic.rb +162 -0
  42. data/lib/gorillib/model/serialization.rb +1 -2
  43. data/lib/gorillib/model/serialization/csv.rb +59 -0
  44. data/lib/gorillib/pathname.rb +19 -8
  45. data/lib/gorillib/some.rb +2 -0
  46. data/lib/gorillib/string/constantize.rb +17 -10
  47. data/lib/gorillib/string/inflector.rb +11 -7
  48. data/lib/gorillib/type/boolean.rb +40 -0
  49. data/lib/gorillib/type/extended.rb +76 -40
  50. data/lib/gorillib/type/url.rb +6 -4
  51. data/lib/gorillib/utils/console.rb +1 -18
  52. data/lib/gorillib/utils/edge_cases.rb +18 -0
  53. data/spec/examples/builder/ironfan_spec.rb +5 -10
  54. data/spec/gorillib/array/compact_blank_spec.rb +36 -21
  55. data/spec/gorillib/array/simple_statistics_spec.rb +143 -0
  56. data/spec/gorillib/builder_spec.rb +16 -20
  57. data/spec/gorillib/collection_spec.rb +131 -35
  58. data/spec/gorillib/exception/raisers_spec.rb +39 -0
  59. data/spec/gorillib/hash/deep_compact_spec.rb +3 -3
  60. data/spec/gorillib/model/{record/defaults_spec.rb → defaults_spec.rb} +5 -1
  61. data/spec/gorillib/model/factories_spec.rb +335 -0
  62. data/spec/gorillib/model/{record/overlay_spec.rb → overlay_spec.rb} +0 -0
  63. data/spec/gorillib/model/serialization_spec.rb +2 -2
  64. data/spec/gorillib/model_spec.rb +19 -18
  65. data/spec/gorillib/pathname_spec.rb +7 -7
  66. data/spec/gorillib/string/truncate_spec.rb +3 -13
  67. data/spec/gorillib/type/extended_spec.rb +50 -2
  68. data/spec/gorillib/utils/capture_output_spec.rb +1 -1
  69. data/spec/spec_helper.rb +10 -7
  70. data/spec/support/factory_test_helpers.rb +76 -0
  71. data/spec/support/gorillib_test_helpers.rb +36 -24
  72. data/spec/support/model_test_helpers.rb +39 -2
  73. metadata +86 -51
  74. data/lib/alt/kernel/call_stack.rb +0 -56
  75. data/lib/gorillib/array/sorted_sample.rb +0 -12
  76. data/lib/gorillib/builder/field.rb +0 -5
  77. data/lib/gorillib/collection/has_collection.rb +0 -31
  78. data/lib/gorillib/collection/list_collection.rb +0 -58
  79. data/lib/gorillib/exception/confidence.rb +0 -17
  80. data/lib/gorillib/io/system_helpers.rb +0 -30
  81. data/lib/gorillib/model/record_schema.rb +0 -9
  82. data/lib/gorillib/utils/stub_module.rb +0 -33
  83. data/spec/array/average_spec.rb +0 -24
  84. data/spec/array/sorted_median_spec.rb +0 -18
  85. data/spec/array/sorted_percentile_spec.rb +0 -24
  86. data/spec/array/sorted_sample_spec.rb +0 -28
  87. data/spec/gorillib/metaprogramming/aliasing_spec.rb +0 -180
  88. data/spec/gorillib/model/record/factories_spec.rb +0 -335
  89. data/spec/support/kcode_test_helper.rb +0 -16
@@ -15,7 +15,7 @@ module Gorillib
15
15
  attr_reader :model
16
16
 
17
17
  # [Hash] all options passed to the field not recognized by one of its own current fields
18
- attr_reader :extra_attributes
18
+ attr_reader :_extra_attributes
19
19
 
20
20
  # Note: `Gorillib::Model::Field` is assembled in two pieces, so that it
21
21
  # can behave as a model itself. Defining `name` here, along with some
@@ -28,7 +28,6 @@ module Gorillib
28
28
  class_attribute :visibilities, :instance_writer => false
29
29
  self.visibilities = { :reader => :public, :writer => :public, :receiver => :public, :tester => false }
30
30
 
31
-
32
31
  # @param [#to_sym] name Field name
33
32
  # @param [#receive] type Factory for field values. To accept any object as-is, specify `Object` as the type.
34
33
  # @param [Gorillib::Model] model Field's owner
@@ -38,11 +37,14 @@ module Gorillib
38
37
  # @option options [true, false, :public, :protected, :private] :writer Visibility for the writer (`#foo=`) method; `false` means don't create one.
39
38
  # @option options [true, false, :public, :protected, :private] :receiver Visibility for the receiver (`#receive_foo`) method; `false` means don't create one.
40
39
  #
41
- def initialize(name, type, model, options={})
40
+ def initialize(model, name, type, options={})
42
41
  Validate.identifier!(name)
42
+ type_opts = options.extract!(:blankish, :empty_product, :items, :keys, :of)
43
+ type_opts[:items] = type_opts.delete(:of) if type_opts.has_key?(:of)
44
+ #
43
45
  @model = model
44
46
  @name = name.to_sym
45
- @type = self.factory_for(type)
47
+ @type = Gorillib::Factory.factory_for(type, type_opts)
46
48
  default_visabilities = visibilities
47
49
  @visibilities = default_visabilities.merge( options.extract!(*default_visabilities.keys) )
48
50
  @doc = options.delete(:name){ "#{name} field" }
@@ -56,29 +58,28 @@ module Gorillib
56
58
  name.to_s
57
59
  end
58
60
 
59
- def factory_for(type)
60
- Gorillib::Factory(type)
61
- end
62
-
63
61
  # @return [String] Human-readable presentation of the field definition
64
62
  def inspect
65
- args = [name.inspect, type.to_s]
66
- "field(#{args.join(", ")})"
63
+ args = [name.inspect, type.to_s, attributes.reject{|k,v| k =~ /^(name|type)$/}.inspect]
64
+ "field(#{args.join(",")})"
65
+ end
66
+ def inspect_compact
67
+ "field(#{name})"
67
68
  end
68
69
 
69
70
  def to_hash
70
- attributes.merge!(@visibility).merge!(@extra_attributes)
71
+ attributes.merge!(@visibility).merge!(@_extra_attributes)
71
72
  end
72
73
 
73
74
  def ==(val)
74
- super && (val.extra_attributes == self.extra_attributes) && (val.model == self.model)
75
+ super && (val._extra_attributes == self._extra_attributes) && (val.model == self.model)
75
76
  end
76
77
 
77
78
  def self.receive(hsh)
78
79
  name = hsh.fetch(:name)
79
80
  type = hsh.fetch(:type)
80
81
  model = hsh.fetch(:model)
81
- new(name, type, model, hsh)
82
+ new(model, name, type, hsh)
82
83
  end
83
84
 
84
85
  #
@@ -110,22 +111,43 @@ module Gorillib
110
111
  # Now we can construct the actual fields.
111
112
  #
112
113
 
114
+ field :position, Integer, :tester => true, :doc => "Indicates this is a positional initialization arg -- you can pass it as a plain value in the given slot to #initialize"
115
+
113
116
  # Name of this field. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` (required)
114
117
  # @macro [attach] field
115
118
  # @attribute $1
116
119
  # @return [$2] the $1 field $*
117
- field :name, String, :writer => false, :doc => "The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` (required)"
120
+ field :name, String, position: 0, writer: false, doc: "The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]` (required)"
118
121
 
119
- # Factory for the field's values
120
- field :type, Class
122
+ field :type, Class, position: 1, doc: "Factory to generate field's values"
121
123
 
122
- # Field's description
123
- field :doc, String
124
+ field :doc, String, doc: "Field's description"
124
125
 
125
126
  # remove the attr_reader method (needed for scaffolding), leaving the meta_module method to remain
126
127
  remove_possible_method(:name)
127
128
 
128
129
  end
130
+
131
+
132
+ class SimpleCollectionField < Gorillib::Model::Field
133
+ field :item_type, Class, default: Whatever, doc: "Factory for collection items"
134
+ # field :collection_attrs, Hash, default: Hash.new, doc: "Extra attributes to pass to the collection on creation -- eg. key_method"
135
+
136
+ def initialize(model, name, type, options={})
137
+ super
138
+ collection_type = self.type
139
+ item_type = self.item_type
140
+ key_method = options[:key_method] if options[:key_method]
141
+ raise "Please supply an item type for #{self.inspect} -- eg 'collection #{name.inspect}, of: FooClass'" unless item_type
142
+ self.default ||= ->{ collection_type.new(item_type: item_type, belongs_to: self, key_method: key_method) }
143
+ end
144
+
145
+ def inscribe_methods(model)
146
+ super
147
+ model.__send__(:define_collection_receiver, self)
148
+ end
149
+ end
150
+
129
151
  end
130
152
  end
131
153
 
@@ -0,0 +1,16 @@
1
+ ::Gorillib::Model::Field.class_eval do
2
+ field :fixup, Symbol, doc: 'key to remap before receive', tester: true
3
+ end
4
+
5
+ module ::Gorillib::StringFixup
6
+ extend Gorillib::Concern
7
+
8
+ # intercept to replace fixup-able hash keys with the proper field name in the receive hash
9
+ def receive!(hsh={})
10
+ self.class.fields.each do |field_name, field|
11
+ next unless field.fixup?
12
+ hsh[field_name] = hsh.delete(field.fixup) if hsh.has_key?(field.fixup)
13
+ end
14
+ super(hsh)
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Gorillib
2
+ module Model
3
+
4
+ #
5
+ # give each field the next position in order
6
+ #
7
+ # @example
8
+ # class Foo
9
+ # include Gorillib::Model
10
+ # field :bob, String # not positional
11
+ # field :zoe, String, position: 0 # positional 0 (explicit)
12
+ # end
13
+ # class Subby < Foo
14
+ # include Gorillib::Model::PositionalFields
15
+ # field :wun, String # positional 1
16
+ # end
17
+ # Foo.field :nope, String # not positional
18
+ # Subby.field :toofer, String # positional 2
19
+ #
20
+ # @note: make sure you're keeping positionals straight in super classes, or
21
+ # in anything added after this.
22
+ #
23
+ module PositionalFields
24
+ extend Gorillib::Concern
25
+
26
+ module ClassMethods
27
+ def field(*args)
28
+ options = args.extract_options!
29
+ super(*args, {position: positionals.count}.merge(options))
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,162 @@
1
+ module Gorillib
2
+ module Model
3
+
4
+ module ClassMethods
5
+
6
+ # Defines a new field
7
+ #
8
+ # For each field that is defined, a getter and setter will be added as
9
+ # an instance method to the model. An Field instance will be added to
10
+ # result of the fields class method.
11
+ #
12
+ # @example
13
+ # field :height, Integer
14
+ #
15
+ # @param [Symbol] field_name The field name. Must start with `[A-Za-z_]` and subsequently contain only `[A-Za-z0-9_]`
16
+ # @param [Class] type The field's type (required)
17
+ # @option options [String] doc Documentation string for the field (optional)
18
+ # @option options [Proc, Object] default Default value, or proc that instance can evaluate to find default value
19
+ #
20
+ # @return Gorillib::Model::Field
21
+ def field(field_name, type, options={})
22
+ options = options.symbolize_keys
23
+ field_type = options.delete(:field_type){ ::Gorillib::Model::Field }
24
+ fld = field_type.new(self, field_name, type, options)
25
+ @_own_fields[fld.name] = fld
26
+ _reset_descendant_fields
27
+ fld.send(:inscribe_methods, self)
28
+ fld
29
+ end
30
+
31
+ def collection(field_name, collection_type, options={})
32
+ options[:item_type] = options[:of] if options.has_key?(:of)
33
+ field(field_name, collection_type, {
34
+ field_type: ::Gorillib::Model::SimpleCollectionField}.merge(options))
35
+ end
36
+
37
+ # @return [{Symbol => Gorillib::Model::Field}]
38
+ def fields
39
+ return @_fields if defined?(@_fields)
40
+ @_fields = ancestors.reverse.inject({}){|acc, ancestor| acc.merge!(ancestor.try(:_own_fields) || {}) }
41
+ end
42
+
43
+ # @return [true, false] true if the field is defined on this class
44
+ def has_field?(field_name)
45
+ fields.has_key?(field_name)
46
+ end
47
+
48
+ # @return [Array<Symbol>] The attribute names
49
+ def field_names
50
+ @_field_names ||= fields.keys
51
+ end
52
+
53
+ def positionals
54
+ @_positionals ||= assemble_positionals
55
+ end
56
+
57
+ def assemble_positionals
58
+ positionals = fields.values.keep_if{|fld| fld.position? }.sort_by!{|fld| fld.position }
59
+ return [] if positionals.empty?
60
+ if (positionals.map(&:position) != (0..positionals.length-1).to_a) then raise ConflictingPositionError, "field positions #{positionals.map(&:position).join(",")} for #{positionals.map(&:name).join(",")} aren't in strict minimal order" ; end
61
+ positionals.map!(&:name)
62
+ end
63
+
64
+ # turn model constructor args (`*positional_args, {attrs}`) into a combined
65
+ # attrs hash. positional_args are mapped to the set of attribute names in
66
+ # order -- by default, the class' field names.
67
+ #
68
+ # Notes:
69
+ #
70
+ # * Positional args always clobber elements of the attribute hash.
71
+ # * Nil positional args are treated as present-and-nil (this might change).
72
+ # * Raises an error if positional args
73
+ #
74
+ # @param [Array[Symbol]] args list of attributes, in order, to map.
75
+ #
76
+ # @return [Hash] a combined, reconciled hash of attributes to set
77
+ def attrs_hash_from_args(args)
78
+ attrs = args.extract_options!
79
+ if args.present?
80
+ ArgumentError.check_arity!(args, 0..positionals.length){ "extracting args #{args} for #{self}" }
81
+ positionals_to_map = positionals[0..(args.length-1)]
82
+ attrs = attrs.merge(Hash[positionals_to_map.zip(args)])
83
+ end
84
+ attrs
85
+ end
86
+
87
+ protected
88
+
89
+ attr_reader :_own_fields
90
+
91
+ # Ensure that classes inherit all their parents' fields, even if fields
92
+ # are added after the child class is defined.
93
+ def _reset_descendant_fields
94
+ ObjectSpace.each_object(::Class) do |klass|
95
+ klass.__send__(:remove_instance_variable, '@_fields') if (klass <= self) && klass.instance_variable_defined?('@_fields')
96
+ klass.__send__(:remove_instance_variable, '@_field_names') if (klass <= self) && klass.instance_variable_defined?('@_field_names')
97
+ klass.__send__(:remove_instance_variable, '@_positionals') if (klass <= self) && klass.instance_variable_defined?('@_positionals')
98
+ end
99
+ end
100
+
101
+ # define the reader method `#foo` for a field named `:foo`
102
+ def define_attribute_reader(field_name, field_type, visibility)
103
+ define_meta_module_method(field_name, visibility) do
104
+ begin
105
+ read_attribute(field_name)
106
+ rescue StandardError => err ; err.polish("#{self.class}.#{field_name}") rescue nil ; raise ; end
107
+ end
108
+ end
109
+
110
+ # define the writer method `#foo=` for a field named `:foo`
111
+ def define_attribute_writer(field_name, field_type, visibility)
112
+ define_meta_module_method("#{field_name}=", visibility) do |val|
113
+ write_attribute(field_name, val)
114
+ end
115
+ end
116
+
117
+ # define the present method `#foo?` for a field named `:foo`
118
+ def define_attribute_tester(field_name, field_type, visibility)
119
+ field = fields[field_name]
120
+ define_meta_module_method("#{field_name}?", visibility) do
121
+ attribute_set?(field_name) || field.has_default?
122
+ end
123
+ end
124
+
125
+ def define_attribute_receiver(field_name, field_type, visibility)
126
+ # @param [Object] val the raw value to type-convert and adopt
127
+ # @return [Object] the attribute's new value
128
+ define_meta_module_method("receive_#{field_name}", visibility) do |val|
129
+ begin
130
+ val = field_type.receive(val)
131
+ write_attribute(field_name, val)
132
+ rescue StandardError => err ; err.polish("#{self.class}.#{field_name} type #{type} on #{val}") rescue nil ; raise ; end
133
+ end
134
+ end
135
+
136
+ #
137
+ # Collection receiver --
138
+ #
139
+ def define_collection_receiver(field)
140
+ collection_field_name = field.name; collection_type = field.type
141
+ # @param [Array[Object],Hash[Object]] the collection to merge
142
+ # @return [Gorillib::Collection] the updated collection
143
+ define_meta_module_method("receive_#{collection_field_name}", true) do |coll, &block|
144
+ begin
145
+ if collection_type.native?(coll)
146
+ write_attribute(collection_field_name, coll)
147
+ else
148
+ read_attribute(collection_field_name).receive!(coll, &block)
149
+ end
150
+ rescue StandardError => err ; err.polish("#{self.class} #{collection_field_name} collection on #{args}'") rescue nil ; raise ; end
151
+ end
152
+ end
153
+
154
+ def inherited(base)
155
+ base.instance_eval do
156
+ @_own_fields ||= {}
157
+ end
158
+ super
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,4 +1,3 @@
1
-
2
1
  class Array
3
2
  def to_tsv
4
3
  join("\t")
@@ -25,7 +24,7 @@ module Gorillib
25
24
 
26
25
  module ClassMethods
27
26
  def from_tuple(*vals)
28
- receive Hash[field_names[0..vals.length].zip(vals)]
27
+ receive Hash[field_names[0..vals.length-1].zip(vals)]
29
28
  end
30
29
  end
31
30
 
@@ -0,0 +1,59 @@
1
+ require 'csv'
2
+ require 'gorillib/pathname'
3
+
4
+ module Gorillib
5
+ module Model
6
+
7
+ module LoadFromCsv
8
+ extend Gorillib::Concern
9
+ included do |base|
10
+ # Options that will be passed to CSV. Be careful to modify with assignment (`+=`) and not in-place (`<<`)
11
+ base.class_attribute :csv_options
12
+ base.csv_options = Hash.new
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ # Iterate a block over each line of a CSV file
18
+ #
19
+ # @raise [Gorillib::Model::RawDataMismatchError] if a line has too many or too few fields
20
+ # @yield an object instantiated from each line in the file.
21
+ def each_in_csv(filename, options={})
22
+ filename = Pathname.path_to(filename)
23
+ options = csv_options.merge(options)
24
+ pop_headers = options.delete(:pop_headers)
25
+ num_fields = options.delete(:num_fields){ (fields.length .. fields.length) }
26
+ raise ArgumentError, "The :headers option to CSV changes its internal behavior; use 'pop_headers: true' to ignore the first line" if options[:headers]
27
+ CSV.open(filename, options) do |csv_file|
28
+ csv_file.shift if pop_headers
29
+ csv_file.each do |tuple|
30
+ next if tuple.empty?
31
+ unless num_fields.include?(tuple.length) then raise Gorillib::Model::RawDataMismatchError, "yark, spurious fields: #{tuple.inspect}" ; end
32
+ yield from_tuple(*tuple)
33
+ end
34
+ nil
35
+ end
36
+ end
37
+
38
+ # With a block, calls block on each object in turn (and returns nil)
39
+ #
40
+ # With no block, accumulates all the instances into the array it
41
+ # returns. As opposed to the with-a-block case, the memory footprint of
42
+ # this increases as the filesize does, so use caution with large files.
43
+ #
44
+ # @return with a block, returns nil; with no block, an array of this class' instances
45
+ def load_csv(*args)
46
+ if block_given?
47
+ each_in_csv(*args, &Proc.new)
48
+ else
49
+ objs = []
50
+ each_in_csv(*args){|obj| objs << obj }
51
+ objs
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -52,9 +52,11 @@ module Gorillib
52
52
  # any mixture of strings (literal sub-paths) and symbols (interpreted as references)
53
53
  # @return [Pathname] A single expanded Pathname
54
54
  #
55
- def path_to(*pathsegs)
56
- relative_path_to(*pathsegs).expand_path
55
+ def of(*pathsegs)
56
+ relpath_to(*pathsegs).expand_path
57
57
  end
58
+ alias_method :path_to, :of
59
+
58
60
 
59
61
  # Expand a path with late-evaluated segments
60
62
  # @see `.path_to`
@@ -62,16 +64,12 @@ module Gorillib
62
64
  # Calls cleanpath (removing `//` double slashes and useless `..`s), but does
63
65
  # not reference the filesystem or make paths absolute
64
66
  #
65
- def relative_path_to(*pathsegs)
67
+ def relpath_to(*pathsegs)
66
68
  ArgumentError.arity_at_least!(pathsegs, 1)
67
69
  pathsegs = pathsegs.map{|ps| expand_pathseg(ps) }.flatten
68
70
  self.new(File.join(*pathsegs)).cleanpath(true)
69
71
  end
70
- def relpath_to(*args) relative_path_to(*args) ; end
71
-
72
- # def make_pathname(*args)
73
- # Pathname.new(*args)
74
- # end
72
+ alias_method :relative_path_to, :relpath_to
75
73
 
76
74
  protected
77
75
  # Recursively expand a path handle
@@ -88,6 +86,19 @@ class Pathname
88
86
  extend Gorillib::Pathref
89
87
  class << self ; alias_method :new_pathname, :new ; end
90
88
 
89
+ def self.receive(obj)
90
+ return obj if obj.nil?
91
+ obj.is_a?(self) ? obj : new(obj)
92
+ end
93
+
94
+ # @return the basename without extension (using self.extname as the extension)
95
+ def corename
96
+ basename(self.extname)
97
+ end
98
+
99
+ # @return [String] compact string rendering
100
+ def inspect_compact() to_path.dump ; end
101
+
91
102
  # FIXME: find out if this is dangerous
92
103
  alias_method :to_str, :to_path
93
104
  end