nrser 0.0.26 → 0.0.27

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.
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