yps 0.1.0 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d89afcf4628612d4c00e6d902eb95bd92757e755b79297c72d73eac901ad53
4
- data.tar.gz: e55453abcb53201a61704a790966ea0f7765e75e40856cf91f0365acd355a729
3
+ metadata.gz: 0aaa3d8a3ac39f8b0890701d45db64be1d9bbdad6d95d67bdf5a0bd4cac4c841
4
+ data.tar.gz: 9ef25972d03876b137661e62d366641c2392fd47465f6628ab2c1c99f685be65
5
5
  SHA512:
6
- metadata.gz: 29070662eff8e6b0d3210d444e88466b6c8fed647c0ca9742d1b4ca5196a195b90740d154836d8439eec89117fbcbfd99f1a67bab6d627c18508a7add5e754d1
7
- data.tar.gz: aa948cb48fac9b1b83b306c093fa8df9c573b676928a00848fd7e8c03a012fab040f2829b2e2ac584ee103ff3018be9e22c2f34645e985606a67a3731bd45bc6
6
+ metadata.gz: 64f564d9d9e5509cccf28cbcb274f8bb2bab852b5e00d87954c11564cf22b20e9b24073a11b78a7ac6fa5d3a2255830245b42ac3b30cde8005c5441d28684048
7
+ data.tar.gz: 75f09a3bf9c5eab84240c14fd0a28d2386f3c58aa31a33b2cd4add9244a6e6184e64368aa65dfb27eefb1150e2d3d46c4cec1fddd6002356f5e45caed2975cae
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  # YPS: YAML Positioning System
9
9
 
10
- YPS is a gem to parse YAML and add position information (file name, line and column) to each parsed elements.
10
+ YPS is a gem that parses YAML and adds position information (file name, line, and column) to each parsed element.
11
11
  This is useful for error reporting and debugging, allowing developers to precisely locate an issue within the original YAML file.
12
12
 
13
13
  ## Installation
@@ -26,12 +26,33 @@ gem install yps
26
26
 
27
27
  ## Usage
28
28
 
29
- You can use the methods below to load a YAML code into Ruby objects with their position information (file name, line, and column).
29
+ You can use the methods below to load YAML content into Ruby objects with position information (file name, line, and column).
30
30
 
31
- * `YPS.safe_load`/`YPS.load`
32
- * Load the given YAML string into Ruby objects with position information.
33
- * `YPS.safe_load_file`/`YPS.load_file`
34
- * Load the YAML code read from the given file path into Ruby objects with position information.
31
+ * Load the given YAML string into Ruby objects with position information
32
+ * `YPS.safe_load`
33
+ * `YPS.load`
34
+ * `YPS.safe_load_stream`
35
+ * `YPS.load_stream`
36
+
37
+ * Load the YAML read from the given file path into Ruby objects with position information
38
+ * `YPS.safe_load_file`
39
+ * `YPS.load_file`
40
+ * `YPS.safe_load_stream_file`
41
+ * `YPS.load_stream_file`
42
+
43
+ For YAML that contains multiple documents, the following methods load only the first document.
44
+
45
+ * `YPS.safe_load`
46
+ * `YPS.load`
47
+ * `YPS.safe_load_file`
48
+ * `YPS.load_file`
49
+
50
+ In contrast, the following methods load all documents and return them as a list.
51
+
52
+ * `YPS.safe_load_stream`
53
+ * `YPS.load_stream`
54
+ * `YPS.safe_load_stream_file`
55
+ * `YPS.load_stream_file`
35
56
 
36
57
  Parsed objects, except for hash keys, have their own position information.
37
58
  You can use the `position` method to get position information in the original YAML of the receiver object.
@@ -57,14 +78,66 @@ yaml['children'].each do |child|
57
78
  puts "#{key}: #{value} (#{value.position})"
58
79
  end
59
80
  end
81
+
82
+ yaml = YPS.load_stream(<<~'YAML')
83
+ - 0
84
+ - 1
85
+ ---
86
+ - foo
87
+ - bar
88
+ YAML
89
+
90
+ # output
91
+ # 0 (filename: unknown line 1 column 3)
92
+ # 1 (filename: unknown line 2 column 3)
93
+ # foo (filename: unknown line 4 column 3)
94
+ # bar (filename: unknown line 5 column 3)
95
+ yaml.each do |list|
96
+ list.each do |item|
97
+ puts "#{item} (#{item.position})"
98
+ end
99
+ end
100
+ ```
101
+
102
+ ### Handling of `false` and `nil` values
103
+
104
+ By default, all objects, including `false` and `nil`, are wrapped in a wrapper class.
105
+ Note that wrapped `false` and `nil` values will not be treated as falsy.
106
+ You can use the `unwrapped_classes` option to avoid this situation.
107
+ Objects belonging to classes specified by this option are returned unwrapped but will not have access to their position information.
108
+
109
+ ```ruby
110
+ yaml = YPS.load(<<~'YAML')
111
+ - false
112
+ - null
113
+ YAML
114
+
115
+ # output
116
+ # false
117
+ # nil
118
+ puts (yaml[0] || 'foo').inspect
119
+ puts (yaml[1] || 'bar').inspect
120
+
121
+ yaml = YPS.load(<<~'YAML', unwrapped_classes: [FalseClass, NilClass])
122
+ - false
123
+ - null
124
+ YAML
125
+
126
+ # output
127
+ # "foo"
128
+ # "bar"
129
+ puts (yaml[0] || 'foo').inspect
130
+ puts (yaml[1] || 'bar').inspect
60
131
  ```
61
132
 
133
+ For more details about these APIs, please visit [here](https://taichi-ishitani.github.io/yps/).
134
+
62
135
  ## Contributing
63
136
 
64
137
  Bug reports and pull requests are welcome on GitHub at https://github.com/taichi-ishitani/yps.
65
138
 
66
139
  * [Issue Tracker](https://github.com/taichi-ishitani/yps/issues)
67
- * [Pull Requesst](https://github.com/taichi-ishitani/yps/pulls)
140
+ * [Pull Request](https://github.com/taichi-ishitani/yps/pulls)
68
141
  * [Discussion](https://github.com/taichi-ishitani/yps/discussions)
69
142
 
70
143
  ## Copyright & License
data/lib/yps/parser.rb CHANGED
@@ -25,6 +25,10 @@ module YPS # :nodoc: all
25
25
  end
26
26
 
27
27
  class Parser < Psych::Parser
28
+ def self.parse(yaml, filename, &)
29
+ new(&).parse(yaml, filename)
30
+ end
31
+
28
32
  def initialize(&)
29
33
  super(Handler.new(&))
30
34
  end
data/lib/yps/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module YPS
4
4
  # The version of YPS
5
- VERSION = '0.1.0'
5
+ VERSION = '1.0.0'
6
6
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YPS # :nodoc: all
4
+ module Visitor
5
+ using NodeExtension
6
+
7
+ module Common
8
+ def initialize( # rubocop:disable Metrics/ParameterLists
9
+ scanner, class_loader, unwrapped_classes, value_class,
10
+ symbolize_names:, freeze:
11
+ )
12
+ super(scanner, class_loader, symbolize_names:, freeze:)
13
+ @unwrapped_classes = unwrapped_classes
14
+ @value_class = value_class
15
+ end
16
+
17
+ def accept(node)
18
+ object = super
19
+ if unwrap?(object, node)
20
+ object
21
+ else
22
+ create_wrapped_object(object, node)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def create_wrapped_object(object, node)
29
+ pos = Position.new(node.filename, node.start_line + 1, node.start_column + 1)
30
+ obj = @value_class.new(object, pos)
31
+ @freeze && obj.freeze || obj
32
+ end
33
+
34
+ def unwrap?(object, node)
35
+ node.document? ||
36
+ node.mapping_key? ||
37
+ @unwrapped_classes.any? { |klass| object.instance_of?(klass) }
38
+ end
39
+ end
40
+
41
+ class ToRuby < Psych::Visitors::ToRuby
42
+ include Common
43
+ end
44
+
45
+ class NoAliasRuby < Psych::Visitors::NoAliasRuby
46
+ include Common
47
+ end
48
+
49
+ def self.create( # rubocop:disable Metrics/ParameterLists
50
+ permitted_classes, permitted_symbols, unwrapped_classes,
51
+ aliases, symbolize_names, freeze, strict_integer, value_class
52
+ )
53
+ class_loader = Psych::ClassLoader::Restricted.new(
54
+ permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s)
55
+ )
56
+ scanner =
57
+ if RUBY_VERSION >= '3.2.0'
58
+ Psych::ScalarScanner.new(class_loader, strict_integer:)
59
+ else
60
+ Psych::ScalarScanner.new(class_loader)
61
+ end
62
+ (aliases && ToRuby || NoAliasRuby)
63
+ .new(scanner, class_loader, unwrapped_classes, value_class, symbolize_names:, freeze:)
64
+ end
65
+ end
66
+ end
data/lib/yps.rb CHANGED
@@ -7,7 +7,7 @@ require_relative 'yps/version'
7
7
  require_relative 'yps/value'
8
8
  require_relative 'yps/node_extension'
9
9
  require_relative 'yps/parser'
10
- require_relative 'yps/visitors'
10
+ require_relative 'yps/visitor'
11
11
 
12
12
  ##
13
13
  # = YPS: YAML Positioning Sysmte
@@ -22,6 +22,8 @@ module YPS
22
22
  # Safely load the YAML string in +yaml+ and add position information (file name line and column)
23
23
  # to each parsed objects except for hash keys.
24
24
  #
25
+ # Load the 1st documetns only if the given YAML contains multiple documents.
26
+ #
25
27
  # Parsed objects will be wrapped by YPS::Value class to add the accessor returning the position information.
26
28
  # You can use the +value_class+ to specify your own wrapper class.
27
29
  #
@@ -35,6 +37,9 @@ module YPS
35
37
  # Array containing additional classes allowed to be loaded.
36
38
  # +permitted_symbols+::
37
39
  # Array containing Symbols allowed to be loaded. By default, any symbol can be loaded.
40
+ # +unwrapped_classes+::
41
+ # Array containing classes whose objects are not wrapped with the wrapper class.
42
+ # By default, all objects are wrapped.
38
43
  # +aliases+::
39
44
  # Aliases can be used if set to true. By default, aliases are not allowed.
40
45
  # +filename+::
@@ -55,31 +60,20 @@ module YPS
55
60
  # See also Psych.safe_load[https://docs.ruby-lang.org/en/master/Psych.html#method-c-safe_load].
56
61
  def safe_load( # rubocop:disable Metrics/ParameterLists
57
62
  yaml,
58
- permitted_classes: [], permitted_symbols: [], aliases: false,
59
- filename: nil, fallback: nil, symbolize_names: false, freeze: false,
60
- strict_integer: false, value_class: Value
63
+ permitted_classes: [], permitted_symbols: [], unwrapped_classes: [],
64
+ aliases: false, filename: nil, fallback: nil, symbolize_names: false,
65
+ freeze: false, strict_integer: false, value_class: Value
61
66
  )
62
- result = parse(yaml, filename)
63
- return fallback unless result
64
-
65
- class_loader =
66
- Psych::ClassLoader::Restricted.new(
67
- permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s)
68
- )
69
- scanner =
70
- if RUBY_VERSION >= '3.2.0'
71
- Psych::ScalarScanner.new(class_loader, strict_integer:)
72
- else
73
- Psych::ScalarScanner.new(class_loader)
74
- end
75
- visitor =
76
- if aliases
77
- Visitors::ToRuby.new(scanner, class_loader, value_class, symbolize_names:, freeze:)
78
- else
79
- Visitors::NoAliasRuby.new(scanner, class_loader, value_class, symbolize_names:, freeze:)
80
- end
67
+ Parser.parse(yaml, filename) do |node|
68
+ visitor =
69
+ Visitor.create(
70
+ permitted_classes, permitted_symbols, unwrapped_classes,
71
+ aliases, symbolize_names, freeze, strict_integer, value_class
72
+ )
73
+ return visitor.accept(node)
74
+ end
81
75
 
82
- visitor.accept(result)
76
+ fallback
83
77
  end
84
78
 
85
79
  ##
@@ -95,9 +89,7 @@ module YPS
95
89
  #
96
90
  # See also YPS.safe_load
97
91
  def safe_load_file(filename, **kwargs)
98
- File.open(filename, 'r:bom|utf-8') do |f|
99
- safe_load(f, filename:, **kwargs)
100
- end
92
+ open_file(filename) { |f| safe_load(f, filename:, **kwargs) }
101
93
  end
102
94
 
103
95
  ##
@@ -105,19 +97,67 @@ module YPS
105
97
  #
106
98
  # See also YPS.load
107
99
  def load_file(filename, **kwargs)
108
- File.open(filename, 'r:bom|utf-8') do |f|
109
- load(f, filename:, **kwargs)
100
+ open_file(filename) { |f| load(f, filename:, **kwargs) }
101
+ end
102
+
103
+ DEFAULT_VALUE = Object.new.freeze
104
+ private_constant :DEFAULT_VALUE
105
+
106
+ ##
107
+ # Similar to +YPS.safe_load+, but load all documents given in +yaml+ and return them as a list.
108
+ #
109
+ # See also YPS.safe_load
110
+ def safe_load_stream( # rubocop:disable Metrics/ParameterLists
111
+ yaml,
112
+ permitted_classes: [], permitted_symbols: [], unwrapped_classes: [],
113
+ aliases: false, filename: nil, fallback: DEFAULT_VALUE, symbolize_names: false,
114
+ freeze: false, strict_integer: false, value_class: Value
115
+ )
116
+ visitor = nil
117
+ results = []
118
+ Parser.parse(yaml, filename) do |node|
119
+ visitor ||=
120
+ Visitor.create(
121
+ permitted_classes, permitted_symbols, unwrapped_classes,
122
+ aliases, symbolize_names, freeze, strict_integer, value_class
123
+ )
124
+ results << visitor.accept(node)
110
125
  end
126
+ return fallback if results.empty? && !fallback.equal?(DEFAULT_VALUE)
127
+
128
+ results
111
129
  end
112
130
 
113
- private
131
+ ##
132
+ # Similar to +YPS.safe_load_sream+, but Symbol is allowed to be loaded by default.
133
+ #
134
+ # See also YPS.safe_load_stream
135
+ def load_stream(yaml, permitted_classes: [Symbol], **kwargs)
136
+ safe_load_stream(yaml, permitted_classes:, **kwargs)
137
+ end
114
138
 
115
- def parse(yaml, filename)
116
- Parser
117
- .new { |node| return node }
118
- .parse(yaml, filename)
139
+ ##
140
+ # Similar to +YPS.safe_load_stream+,
141
+ # but the YAML string is read from the file specified by the +filename+ argument.
142
+ #
143
+ # See also YPS.safe_load_stream
144
+ def safe_load_stream_file(filename, **kwargs)
145
+ open_file(filename) { |f| safe_load_stream(f, filename:, **kwargs) }
146
+ end
147
+
148
+ ##
149
+ # Similar to +YPS.load_stream+,
150
+ # but the YAML string is read from the file specified by the +filename+ argument.
151
+ #
152
+ # See also YPS.load_stream
153
+ def load_stream_file(filename, **kwargs)
154
+ open_file(filename) { |f| load_stream(f, filename:, **kwargs) }
155
+ end
156
+
157
+ private
119
158
 
120
- false
159
+ def open_file(filename, &)
160
+ File.open(filename, 'r:bom|utf-8', &)
121
161
  end
122
162
  end
123
163
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yps
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taichi Ishitani
@@ -29,14 +29,17 @@ files:
29
29
  - lib/yps/parser.rb
30
30
  - lib/yps/value.rb
31
31
  - lib/yps/version.rb
32
- - lib/yps/visitors.rb
32
+ - lib/yps/visitor.rb
33
33
  homepage: https://github.com/taichi-ishitani/yps
34
34
  licenses:
35
35
  - MIT
36
36
  metadata:
37
+ bug_tracker_uri: https://github.com/taichi-ishitani/yps/issues
38
+ changelog_uri: https://github.com/taichi-ishitani/yps/releases
39
+ documentation_uri: https://taichi-ishitani.github.io/yps/
37
40
  homepage_uri: https://github.com/taichi-ishitani/yps
38
- source_code_uri: https://github.com/taichi-ishitani/yps
39
41
  rubygems_mfa_required: 'true'
42
+ source_code_uri: https://github.com/taichi-ishitani/yps
40
43
  rdoc_options:
41
44
  - "--main"
42
45
  - README.md
@@ -57,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
60
  - !ruby/object:Gem::Version
58
61
  version: '0'
59
62
  requirements: []
60
- rubygems_version: 3.7.2
63
+ rubygems_version: 4.0.3
61
64
  specification_version: 4
62
65
  summary: 'YPS: YAML Positioning System'
63
66
  test_files: []
data/lib/yps/visitors.rb DELETED
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module YPS # :nodoc: all
4
- module Visitors
5
- using NodeExtension
6
-
7
- module Common
8
- def initialize(scanner, class_loader, value_class, symbolize_names:, freeze:)
9
- super(scanner, class_loader, symbolize_names:, freeze:)
10
- @value_class = value_class
11
- end
12
-
13
- def accept(node)
14
- object = super
15
- create_wrapped_object(object, node)
16
- end
17
-
18
- private
19
-
20
- def create_wrapped_object(object, node)
21
- return object if node.document? || node.mapping_key?
22
-
23
- pos = Position.new(node.filename, node.start_line + 1, node.start_column + 1)
24
- obj = @value_class.new(object, pos)
25
- @freeze && obj.freeze || obj
26
- end
27
- end
28
-
29
- class ToRuby < Psych::Visitors::ToRuby
30
- include Common
31
- end
32
-
33
- class NoAliasRuby < Psych::Visitors::NoAliasRuby
34
- include Common
35
- end
36
- end
37
- end