nrser 0.0.26 → 0.0.27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser.rb +1 -0
  3. data/lib/nrser/array.rb +15 -0
  4. data/lib/nrser/binding.rb +7 -1
  5. data/lib/nrser/enumerable.rb +21 -1
  6. data/lib/nrser/errors.rb +56 -6
  7. data/lib/nrser/hash/deep_merge.rb +1 -1
  8. data/lib/nrser/message.rb +33 -0
  9. data/lib/nrser/meta/props.rb +77 -15
  10. data/lib/nrser/meta/props/prop.rb +276 -44
  11. data/lib/nrser/proc.rb +7 -3
  12. data/lib/nrser/refinements/array.rb +5 -0
  13. data/lib/nrser/refinements/enumerable.rb +5 -0
  14. data/lib/nrser/refinements/hash.rb +8 -0
  15. data/lib/nrser/refinements/object.rb +11 -1
  16. data/lib/nrser/refinements/string.rb +17 -3
  17. data/lib/nrser/refinements/symbol.rb +8 -0
  18. data/lib/nrser/refinements/tree.rb +22 -0
  19. data/lib/nrser/rspex.rb +312 -70
  20. data/lib/nrser/rspex/shared_examples.rb +116 -0
  21. data/lib/nrser/string.rb +159 -27
  22. data/lib/nrser/temp/unicode_math.rb +48 -0
  23. data/lib/nrser/text.rb +3 -0
  24. data/lib/nrser/text/indentation.rb +210 -0
  25. data/lib/nrser/text/lines.rb +52 -0
  26. data/lib/nrser/text/word_wrap.rb +29 -0
  27. data/lib/nrser/tree.rb +4 -78
  28. data/lib/nrser/tree/each_branch.rb +76 -0
  29. data/lib/nrser/tree/map_branches.rb +91 -0
  30. data/lib/nrser/tree/map_tree.rb +97 -0
  31. data/lib/nrser/tree/transform.rb +56 -13
  32. data/lib/nrser/types.rb +1 -0
  33. data/lib/nrser/types/array.rb +15 -3
  34. data/lib/nrser/types/is_a.rb +40 -1
  35. data/lib/nrser/types/nil.rb +17 -0
  36. data/lib/nrser/types/paths.rb +17 -2
  37. data/lib/nrser/types/strings.rb +57 -22
  38. data/lib/nrser/types/tuples.rb +5 -0
  39. data/lib/nrser/types/type.rb +47 -6
  40. data/lib/nrser/version.rb +1 -1
  41. data/spec/nrser/errors/abstract_method_error_spec.rb +46 -0
  42. data/spec/nrser/meta/props/to_and_from_data_spec.rb +74 -0
  43. data/spec/nrser/meta/props_spec.rb +6 -2
  44. data/spec/nrser/refinements/erb_spec.rb +100 -1
  45. data/spec/nrser/{common_prefix_spec.rb → string/common_prefix_spec.rb} +9 -0
  46. data/spec/nrser/text/dedent_spec.rb +80 -0
  47. data/spec/nrser/tree/map_branch_spec.rb +83 -0
  48. data/spec/nrser/tree/map_tree_spec.rb +123 -0
  49. data/spec/nrser/tree/transform_spec.rb +26 -29
  50. data/spec/nrser/tree/transformer_spec.rb +179 -0
  51. data/spec/nrser/types/paths_spec.rb +73 -45
  52. data/spec/spec_helper.rb +10 -0
  53. metadata +27 -7
  54. data/spec/nrser/dedent_spec.rb +0 -36
@@ -0,0 +1,52 @@
1
+
2
+ module NRSER
3
+ # @!group Text
4
+
5
+ # Classes
6
+ # =====================================================================
7
+
8
+ # @todo document Lines class.
9
+ class Lines < Array
10
+
11
+ # Constants
12
+ # ======================================================================
13
+
14
+
15
+ # Class Methods
16
+ # ======================================================================
17
+
18
+
19
+ # Attributes
20
+ # ======================================================================
21
+
22
+
23
+ # Constructor
24
+ # ======================================================================
25
+
26
+ # Instantiate a new `Lines`.
27
+ def initialize
28
+
29
+ end # #initialize
30
+
31
+
32
+ # Instance Methods
33
+ # ======================================================================
34
+
35
+ end # class Lines
36
+
37
+
38
+ # Functions
39
+ # =====================================================================
40
+
41
+ def self.lines text
42
+ case text
43
+ when String
44
+ text.lines
45
+ when Array
46
+ text
47
+ else
48
+ raise TypeError, "Expected String or Array, found #{ text.class.name }"
49
+ end
50
+ end
51
+
52
+ end # module NRSER
@@ -0,0 +1,29 @@
1
+
2
+ module NRSER
3
+ # @!group Text
4
+
5
+ # Split text at whitespace to fit in line length. Lifted from Rails'
6
+ # ActionView.
7
+ #
8
+ # @see http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-word_wrap
9
+ #
10
+ # @param [String] text
11
+ # Text to word wrap.
12
+ #
13
+ # @param [Fixnum] line_width:
14
+ # Line with in number of character to wrap at.
15
+ #
16
+ # @param [String] break_sequence:
17
+ # String to join lines with.
18
+ #
19
+ # @return [String]
20
+ # @todo Document return value.
21
+ #
22
+ def self.word_wrap text, line_width: 80, break_sequence: "\n"
23
+ text.split("\n").collect! do |line|
24
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
25
+ end * break_sequence
26
+ end # .word_wrap
27
+
28
+
29
+ end # module NRSER
@@ -3,84 +3,10 @@
3
3
 
4
4
  # Project / Package
5
5
  # -----------------------------------------------------------------------
6
+
7
+ require_relative './tree/each_branch'
8
+ require_relative './tree/map_branches'
9
+ require_relative './tree/map_tree'
6
10
  require_relative './tree/leaves'
7
11
  require_relative './tree/map_leaves'
8
12
  require_relative './tree/transform'
9
-
10
-
11
- # Definitions
12
- # =======================================================================
13
-
14
- module NRSER
15
-
16
- # Enumerate over the immediate "branches" of a structure that can be used
17
- # to compose our idea of a *tree*: nested hash-like and array-like structures
18
- # like you would get from parsing a JSON document.
19
- #
20
- # Written and tested against Hash and Array instances, but should work with
21
- # anything hash-like that responds to `#each_pair` appropriately or
22
- # array-like that responds to `#each_index` and `#each_with_index`.
23
- #
24
- # @note Not sure what will happen if the tree has circular references!
25
- #
26
- # @param [#each_pair | (#each_index & #each_with_index)] tree
27
- # Structure representing a tree via hash-like and array-like containers.
28
- #
29
- # @yieldparam [Object] key
30
- # The first yielded param is the key or index for the value branch at the
31
- # top level of `tree`.
32
- #
33
- # @yieldparam [Object] value
34
- # The second yielded param is the branch at the key or index at the top
35
- # level of `tree`.
36
- #
37
- # @yieldreturn
38
- # Ignored.
39
- #
40
- # @return [Enumerator]
41
- # If no block is provided.
42
- #
43
- # @return [#each_pair | (#each_index & #each_with_index)]
44
- # If a block is provided, the result of the `#each_pair` or
45
- # `#each_with_index` call.
46
- #
47
- # @raise [NoMethodError]
48
- # If `tree` does not respond to `#each_pair` or to `#each_index` and
49
- # `#each_with_index`.
50
- #
51
- def self.each_branch tree, &block
52
- if tree.respond_to? :each_pair
53
- # Hash-like
54
- tree.each_pair &block
55
-
56
- elsif tree.respond_to? :each_index
57
- # Array-like... we test for `each_index` because - unintuitively -
58
- # `#each_with_index` is a method of {Enumerable}, meaning that {Set}
59
- # responds to it, though sets are unordered and the values can't be
60
- # accessed via those indexes. Hence we look for `#each_index`, which
61
- # {Set} does not respond to.
62
-
63
- if block.nil?
64
- index_enumerator = tree.each_with_index
65
-
66
- Enumerator.new( index_enumerator.size ) { |yielder|
67
- index_enumerator.each { |value, index|
68
- yielder.yield index, value
69
- }
70
- }
71
- else
72
- tree.each_with_index.map { |value, index|
73
- block.call index, value
74
- }
75
- end
76
-
77
- else
78
- raise NoMethodError.new NRSER.squish <<-END
79
- `tree` param must respond to `#each_pair` or `#each_index`,
80
- found #{ tree.inspect }
81
- END
82
-
83
- end # if / else
84
- end # .each_branch
85
-
86
- end # module NRSER
@@ -0,0 +1,76 @@
1
+ # Definitions
2
+ # =======================================================================
3
+
4
+ module NRSER
5
+
6
+ # Enumerate over the immediate "branches" of a structure that can be used
7
+ # to compose our idea of a *tree*: nested hash-like and array-like structures
8
+ # like you would get from parsing a JSON document.
9
+ #
10
+ # Written and tested against Hash and Array instances, but should work with
11
+ # anything hash-like that responds to `#each_pair` appropriately or
12
+ # array-like that responds to `#each_index` and `#each_with_index`.
13
+ #
14
+ # @note Not sure what will happen if the tree has circular references!
15
+ #
16
+ # @param [#each_pair | (#each_index & #each_with_index)] tree
17
+ # Structure representing a tree via hash-like and array-like containers.
18
+ #
19
+ # @yieldparam [Object] key
20
+ # The first yielded param is the key or index for the value branch at the
21
+ # top level of `tree`.
22
+ #
23
+ # @yieldparam [Object] value
24
+ # The second yielded param is the branch at the key or index at the top
25
+ # level of `tree`.
26
+ #
27
+ # @yieldreturn
28
+ # Ignored.
29
+ #
30
+ # @return [Enumerator]
31
+ # If no block is provided.
32
+ #
33
+ # @return [#each_pair | (#each_index & #each_with_index)]
34
+ # If a block is provided, the result of the `#each_pair` or
35
+ # `#each_with_index` call.
36
+ #
37
+ # @raise [NoMethodError]
38
+ # If `tree` does not respond to `#each_pair` or to `#each_index` and
39
+ # `#each_with_index`.
40
+ #
41
+ def self.each_branch tree, &block
42
+ if tree.respond_to? :each_pair
43
+ # Hash-like
44
+ tree.each_pair &block
45
+
46
+ elsif tree.respond_to? :each_index
47
+ # Array-like... we test for `each_index` because - unintuitively -
48
+ # `#each_with_index` is a method of {Enumerable}, meaning that {Set}
49
+ # responds to it, though sets are unordered and the values can't be
50
+ # accessed via those indexes. Hence we look for `#each_index`, which
51
+ # {Set} does not respond to.
52
+
53
+ if block.nil?
54
+ index_enumerator = tree.each_with_index
55
+
56
+ Enumerator.new( index_enumerator.size ) { |yielder|
57
+ index_enumerator.each { |value, index|
58
+ yielder.yield [index, value]
59
+ }
60
+ }
61
+ else
62
+ tree.each_with_index.map { |value, index|
63
+ block.call [index, value]
64
+ }
65
+ end
66
+
67
+ else
68
+ raise NoMethodError.new NRSER.squish <<-END
69
+ `tree` param must respond to `#each_pair` or `#each_index`,
70
+ found #{ tree.inspect }
71
+ END
72
+
73
+ end # if / else
74
+ end # .each_branch
75
+
76
+ end # module NRSER
@@ -0,0 +1,91 @@
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Project / Package
5
+ # -----------------------------------------------------------------------
6
+ require 'nrser/types/trees'
7
+
8
+ require_relative './each_branch'
9
+
10
+
11
+ # Definitions
12
+ # =======================================================================
13
+
14
+ module NRSER
15
+
16
+ # Map the immediate "branches" of a structure that can be used
17
+ # to compose our idea of a *tree*: nested hash-like and array-like structures
18
+ # like you would get from parsing a JSON document.
19
+ #
20
+ # The `block` **MUST** return a pair ({Array} of length 2), the first value
21
+ # of which is the key or index in the new {Hash} or {Array}.
22
+ #
23
+ # These pairs are then converted into a {Hash} or {Array} depending on it
24
+ # `tree` was {NRSER::Types.hash_like} or {NRSER::Types.array_like}, and
25
+ # that value is returned.
26
+ #
27
+ # Uses {NRSER.each_branch} internally.
28
+ #
29
+ # Written and tested against Hash and Array instances, but should work with
30
+ # anything:
31
+ #
32
+ # 1. *hash-like* that responds to `#each_pair` appropriately.
33
+ #
34
+ # 2. *array-like* that responds to `#each_index` and `#each_with_index`
35
+ # appropriately.
36
+ #
37
+ # @note Not sure what will happen if the tree has circular references!
38
+ #
39
+ # @todo
40
+ # Might be nice to have an option to preserve the tree class that creates
41
+ # a new instance of *whatever* it was and populates that, though I could
42
+ # see this relying on problematic assumptions and producing confusing
43
+ # results depending on the actual classes.
44
+ #
45
+ # Maybe this could be encoded in a mixin that we would detect or something.
46
+ #
47
+ # @example
48
+ #
49
+ #
50
+ #
51
+ # @param [#each_pair | (#each_index & #each_with_index)] tree
52
+ # Structure representing a tree via hash-like and array-like containers.
53
+ #
54
+ # @yieldparam [Object] key
55
+ # The first yielded param is the key or index for the value branch at the
56
+ # top level of `tree`.
57
+ #
58
+ # @yieldparam [Object] value
59
+ # The second yielded param is the branch at the key or index at the top
60
+ # level of `tree`.
61
+ #
62
+ # @yieldreturn [Array]
63
+ # Pair of key (/index) in new array or hash followed by value.
64
+ #
65
+ # @return [Array | Hash]
66
+ # If no block is provided.
67
+ #
68
+ # @raise [NoMethodError]
69
+ # If `tree` does not respond to `#each_pair` or to `#each_index` and
70
+ # `#each_with_index`.
71
+ #
72
+ def self.map_branches tree, &block
73
+ if block.nil?
74
+ raise ArgumentError, "Must provide block"
75
+ end
76
+
77
+ pairs = each_branch( tree ).map &block
78
+
79
+ Types.match tree,
80
+ Types.hash_like, ->( _ ) {
81
+ pairs.to_h
82
+ },
83
+
84
+ Types.array_like, ->( _ ) {
85
+ pairs.each_with_object( [] ) { |(index, value), array|
86
+ array[index] = value
87
+ }
88
+ }
89
+ end # .map_branches
90
+
91
+ end # module NRSER
@@ -0,0 +1,97 @@
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Project / Package
5
+ # -----------------------------------------------------------------------
6
+ require_relative './map_branches'
7
+
8
+
9
+ # Definitions
10
+ # =======================================================================
11
+
12
+ module NRSER
13
+ # Recursively descend through a tree mapping *all* non-structural elements
14
+ # - anything not {NRSER::Types.hash_like} or {NRSER::Types.array_like}, both
15
+ # hash keys *and* values, as well as array entries - through `block` to
16
+ # produce a new structure.
17
+ #
18
+ # Useful when you want to translate pieces of a tree structure depending on
19
+ # their type or some other property that can be determined *from the element
20
+ # alone* - `block` receives only the value as an argument, no location
21
+ # information (because it's weirder to represent for keys and I didn't need
22
+ # it for the {NRSER.transformer} stuff this was written for).
23
+ #
24
+ # @note
25
+ # Array indexes **are not mapped** through `block` and can not be changed
26
+ # via this method. This makes it easier to do things like "convert all the
27
+ # integers to strings" when you mean the data entries, not the array
28
+ # indexes (which would fail since the new array wouldn't accept string
29
+ # indices).
30
+ #
31
+ # If you don't want to map hash keys use {NRSER.map_leaves}.
32
+ #
33
+ # See the specs for examples. Used in {NRSER.transformer}.
34
+ #
35
+ # @param tree (see NRSER.each_branch)
36
+ #
37
+ # @param [Boolean] prune:
38
+ # When `true`, prunes out values whose labels end with `?` and values are
39
+ # `nil`.
40
+ #
41
+ # @yieldparam [Object] element
42
+ # Anything reached from the root that is not structural (hash-like or
43
+ # array-like), including / inside hash keys (though array
44
+ # indexes are **not** passed).
45
+ #
46
+ def self.map_tree tree, prune: false, &block
47
+ # TODO type check tree?
48
+
49
+ mapped = tree.map { |element|
50
+ # Recur if `element` is a tree.
51
+ #
52
+ # Since `element` will be an {Array} of `key`, `value` when `tree` is a
53
+ # {Hash} (or similar), this will descend into hash keys that are also
54
+ # trees, as well as into hash values and array entries.
55
+ #
56
+ if Types.tree.test element
57
+ map_tree element, prune: prune, &block
58
+ else
59
+ # When we've run out of trees, finally pipe through the block:
60
+ block.call element
61
+ end
62
+ }
63
+
64
+ # If `tree` is hash-like, we want to convert the array of pair arrays
65
+ # back into a hash.
66
+ if Types.hash_like.test tree
67
+ if prune
68
+ pruned = {}
69
+
70
+ mapped.each { |key, value|
71
+ if Types.label.test( key ) &&
72
+ key.to_s.end_with?( '?' )
73
+ unless value.nil?
74
+ new_key = key.to_s[0..-2]
75
+
76
+ if key.is_a?( Symbol )
77
+ new_key = new_key.to_sym
78
+ end
79
+
80
+ pruned[new_key] = value
81
+ end
82
+ else
83
+ pruned[key] = value
84
+ end
85
+ }
86
+
87
+ pruned
88
+ else
89
+ mapped.to_h
90
+ end
91
+ else
92
+ # Getting here means it was array-like, so it's already fine
93
+ mapped
94
+ end
95
+ end # .map_branches
96
+
97
+ end # module NRSER
@@ -1,3 +1,10 @@
1
+ # Requirements
2
+ # =======================================================================
3
+
4
+ # Project / Package
5
+ # -----------------------------------------------------------------------
6
+ require_relative './map_tree'
7
+
1
8
  # Definitions
2
9
  # =======================================================================
3
10
 
@@ -12,19 +19,55 @@ module NRSER
12
19
  # @todo Document return value.
13
20
  #
14
21
  def self.transform tree, source
15
- each_branch( tree ).map { |pair|
16
- pair.map { |value|
17
- if NRSER::Types.tree.test value
18
- transform value, source
19
- else
20
- if value.is_a? Proc
21
- value.call source
22
- else
23
- value
24
- end
25
- end
26
- }
27
- }.to_h
22
+ map_tree( tree, prune: true ) { |value|
23
+ if value.is_a? Proc
24
+ value.call source
25
+ else
26
+ value
27
+ end
28
+ }
28
29
  end # .transform
29
30
 
31
+
32
+ class SendSerializer
33
+ def initialize messages = []
34
+ @messages = messages
35
+ end
36
+
37
+ def method_missing symbol, *args, &block
38
+ messages = [
39
+ *@messages,
40
+ ::NRSER::Message.new( symbol, *args, &block )
41
+ ]
42
+
43
+ self.class.new messages
44
+ end
45
+
46
+ def to_proc publicly: true
47
+ ::NRSER.chainer @messages, publicly: publicly
48
+ end
49
+ end
50
+
51
+
52
+
53
+ # @todo Document transformer method.
54
+ #
55
+ # @param [type] arg_name
56
+ # @todo Add name param description.
57
+ #
58
+ # @return [return_type]
59
+ # @todo Document return value.
60
+ #
61
+ def self.transformer &block
62
+ map_tree( block.call SendSerializer.new ) { |value|
63
+ if value.is_a? SendSerializer
64
+ value.to_proc
65
+ else
66
+ value
67
+ end
68
+ }
69
+ end # .transformer
70
+
71
+
72
+
30
73
  end # module NRSER