tcepsni 0.1.0 → 0.2.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: 83d6d7256e483e7259f7ca6a630969fdea2b00a94674130f33af6fc8992d50e1
4
- data.tar.gz: a4c891f49b92e1fa79fc36759665357eb47e5de1fe774f6a9e4c6c1e904d2a89
3
+ metadata.gz: 3695b6a1a12979fa76824819d4f839ce4e3b9a2af040f97eca9e474609c5c1f2
4
+ data.tar.gz: 373da53b2e230d3510f2043f25b8ae7cf017fae689fdfbaa92a388be4155ebcd
5
5
  SHA512:
6
- metadata.gz: 584b8d5ca524b82d8b1ff0e0ba04a8b76768c9ca64488699f2ad737bf337790c50fc9c938bdad75fe0630480b24c125bff80629e820e26a556c11d13e599463b
7
- data.tar.gz: 678dd3a11bdcaacc47f7a26d545dd5b4466b2e1f471b4b8f233c23c816658c25bc01a1c0bae2b688eb411f68518718fe571863917bccc108b8fa51b230d6da3d
6
+ metadata.gz: f646aecf1094e4e171581b52ca0e48dccddc4464bf158f4c30b93e8df7ef7b8989b240e9c61abf25a7cb65c2c9a29ec6978529f627a1c360f0fede299624d297
7
+ data.tar.gz: 573e5e82c5c7a0b476ee93255fe6b6fc7992febcee911e54ee1b852c9acacca29950bc67183ec01cbed7c3cf3cfcce3edd21137f1c04400bff42959d6f1d72c8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.0 - 2023-10-20
6
+
7
+ ### Added
8
+
9
+ - `Time` support
10
+ - `Pathname` support
11
+ - `Encoding` support
12
+ - `IPAddr` support
13
+ - `ActiveSupport::OrderedOptions` support
14
+ - `Rails::Paths::Root` support
15
+ - `Float` support
16
+
17
+ ### Fixed
18
+
19
+ - Parsing the empty array
20
+ - Parsing the object with some attributes
21
+ - Parsing the `nil`, `true`, and `false` literals
22
+ - Detecting `StringScanner`-like object
23
+
24
+ ## 0.1.1 - 2023-10-02
25
+
26
+ ### Changed
27
+
28
+ - Object's memory reference attribute has changed to integer from string
29
+
30
+ ### Fixed
31
+
32
+ - String scanner parser now supports string with quotes
33
+
5
34
  ## 0.1.0 - 2023-10-01
6
35
 
7
36
  - Initial release
@@ -1,3 +1,3 @@
1
1
  module Tcepsni
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/tcepsni.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  require 'strscan'
2
+ require 'pathname'
3
+ require 'ipaddr'
4
+ require 'active_support/ordered_options'
5
+ require 'rails/paths'
2
6
  require_relative 'tcepsni/version'
3
7
 
4
8
  module Tcepsni
@@ -12,32 +16,53 @@ module Tcepsni
12
16
  class << self
13
17
  private
14
18
 
19
+ # If it returned nil when failed, it collides with normal nil value.
15
20
  def parse_expression(scanner)
16
- if (integer = scanner.scan(/\d+/))
17
- Integer(integer)
18
- elsif scanner.skip('nil')
21
+ if scanner.skip(/nil\b/)
19
22
  nil
20
- elsif scanner.skip('true')
23
+ elsif scanner.skip(/true\b/)
21
24
  true
22
- elsif scanner.skip('false')
25
+ elsif scanner.skip(/false\b/)
23
26
  false
24
- elsif scanner.skip(/:(?<name>[A-Za-z_][A-Za-z0-9_]*)/)
25
- scanner[:name].intern
27
+ elsif scanner.skip(':')
28
+ parse_identifier(scanner)
26
29
  elsif scanner.skip('"')
27
30
  parse_string(scanner)
28
31
  elsif scanner.skip('[')
29
32
  parse_array(scanner)
30
33
  elsif scanner.skip('{')
31
34
  parse_hash(scanner)
32
- elsif (name = scanner.scan(/[A-Z][A-Za-z0-9_]*/))
33
- parse_class(scanner, name: name.intern)
34
35
  elsif scanner.skip('#<')
35
36
  parse_object(scanner)
36
37
  else
37
- raise Error, "invalid format: #{ scanner.inspect }"
38
+ if (digits = scanner.scan(/\d+/))
39
+ int = Integer(digits)
40
+ if scanner.skip('.')
41
+ int2 = parse_integer(scanner)
42
+ Float("#{int}.#{int2}")
43
+ elsif digits.size == 4 && scanner.skip(/-(?<month>[01]\d)-(?<day>\d\d) (?<hour>[012]\d):(?<minute>[0-5]\d):(?<second>[0-5]\d) (?<zone>[+-]\d\d\d\d)/)
44
+ Time.new(Integer(int), scanner[:month], scanner[:day],
45
+ scanner[:hour], scanner[:minute], scanner[:second],
46
+ scanner[:zone])
47
+ else
48
+ int
49
+ end
50
+ else
51
+ parse_class(scanner)
52
+ end
38
53
  end
39
54
  end
40
55
 
56
+ def parse_integer(scanner)
57
+ int = scanner.scan(/\d+/) or raise Error, 'no digits'
58
+ Integer(int)
59
+ end
60
+
61
+ def parse_identifier(scanner)
62
+ id = scanner.scan(/[A-Za-z_][A-Za-z0-9_]*/) or raise Error, 'no identifier'
63
+ id.intern
64
+ end
65
+
41
66
  def parse_string(scanner)
42
67
  result = ''
43
68
  until scanner.eos?
@@ -53,13 +78,12 @@ module Tcepsni
53
78
  end
54
79
 
55
80
  def parse_array(scanner)
56
- result = [parse_expression(scanner)]
81
+ result = []
57
82
  until scanner.eos?
83
+ result << parse_expression(scanner) rescue break
58
84
  scanner.skip(', ') or break
59
- scanner.eos? and raise Error, 'incomplete array'
60
- result << parse_expression(scanner)
61
85
  end
62
- scanner.skip(']') or raise Error, 'no array closer'
86
+ scanner.skip(']') or raise Error, "no array closer: #{scanner.inspect}"
63
87
  result
64
88
  end
65
89
 
@@ -76,42 +100,90 @@ module Tcepsni
76
100
  result
77
101
  end
78
102
 
79
- def parse_class(scanner, name: nil)
80
- unless name
81
- name = scanner.scan(/[A-Z][A-Za-z0-9_]*/) or raise Error, 'no class name'
82
- name = name.intern
83
- end
84
- namespaces = [name]
103
+ def parse_class(scanner)
104
+ namespaces = [parse_capitalized_identifier(scanner)]
85
105
  until scanner.eos?
86
106
  scanner.skip('::') or break
87
107
  scanner.eos? and raise Error, 'incomplete class'
88
- namespace = scanner.scan(/[A-Z][A-Za-z0-9_]*/) or raise Error, 'no namespace'
89
- namespaces << namespace.intern
108
+ namespaces << parse_capitalized_identifier(scanner)
90
109
  end
91
110
  *namespaces, name = namespaces
92
111
  Tcepsni::Class.new(name: name, namespaces: namespaces)
93
112
  end
94
113
 
114
+ def parse_capitalized_identifier(scanner)
115
+ id = scanner.scan(/[A-Z][A-Za-z0-9_]*/) or raise Error, "no capitalized identifier: #{scanner.inspect}"
116
+ id.intern
117
+ end
118
+
95
119
  def parse_object(scanner)
96
120
  klass = parse_class(scanner)
97
121
  if klass.string_scanner?
98
- scanner.skip(%r{ (?<pos>\d+)/(?<total>\d+) "(?<scanned>[^"]+)" @ "(?<rest>[^"]+)">}) or raise Error, 'invalid string scanner'
99
- Tcepsni::StringScanner.new(pos: Integer(scanner[:pos]), total: Integer(scanner[:total]), scanned: scanner[:scanned], rest: scanner[:rest])
122
+ scanner.skip(' ') or raise Error, 'no space after string scanner class name'
123
+ pos = parse_integer(scanner)
124
+ scanner.skip('/') or raise Error, 'no string scanner progress delimiter slash'
125
+ total = parse_integer(scanner)
126
+ scanner.skip(' "') or raise Error, 'no space after string scanner progress'
127
+ scanned = parse_string(scanner)
128
+ scanner.skip(' @ "') or raise Error, "no string scanner mark in string: #{scanner.inspect}"
129
+ rest = parse_string(scanner)
130
+ scanner.skip('>') or raise Error, 'no string scanner closer'
131
+ Tcepsni::Vendor::StringScanner.new(pos: pos, total: total, scanned: scanned, rest: rest)
132
+ elsif klass.pathname?
133
+ scanner.skip(':') or raise Error, 'no colon between Pathname class name and path string'
134
+ scanner.skip(/(?<path>[^>]+)>/) or raise Error, 'invalid path string and closer'
135
+ Pathname(scanner[:path])
136
+ elsif klass.encoding?
137
+ scanner.skip(':') or raise Error, 'no colon separator in encoding object'
138
+ scanner.skip(/(?<name>[^>]+)>/) or raise Error, 'invalid encoding name'
139
+ Encoding.find(scanner[:name])
140
+ elsif klass.ipaddr?
141
+ scanner.skip(/: IPv[46]:(?<addr>[^>]+)>/) or raise Error, 'invalid IP address format error'
142
+ IPAddr.new(scanner[:addr])
143
+ elsif klass.active_support_ordered_options?
144
+ scanner.skip(' {') or raise Error, 'no space between ActiveSupport::OrderedOptions class name and hash opener'
145
+ hash = parse_hash(scanner)
146
+ scanner.skip('>') or raise Error, "no ActiveSupport ordered options closer: #{scanner.inspect}"
147
+ options = ActiveSupport::OrderedOptions.new
148
+ hash.each do |key, val|
149
+ options[key] = val
150
+ end
151
+ options
152
+ elsif klass.rails_paths_root?
153
+ memory_reference = parse_memory_reference(scanner)
154
+ if scanner.skip(' ...>')
155
+ attributes = {}
156
+ else
157
+ attributes = parse_attributes(scanner)
158
+ end
159
+ Tcepsni::Vendor::Rails::Paths::Root.new(memory_reference, attributes)
100
160
  else
101
- scanner.skip(/:(?<memory_reference>0x[0-9a-f]{16})/) or raise Error, "no memory reference: #{scanner.inspect}"
102
- memory_reference = scanner[:memory_reference]
103
- attributes = {}
104
- unless scanner.skip('>')
105
- until scanner.eos?
106
- scanner.skip(/ @(?<key>[A-Za-z_][A-Za-z_0-9]*)=/) or break
107
- key = scanner[:key].intern
108
- value = parse_expression(scanner)
109
- attributes[key] = value
110
- scanner.skip(',') or break
161
+ memory_reference = parse_memory_reference(scanner)
162
+ attributes = parse_attributes(scanner)
163
+ Tcepsni::Object.new(klass: klass, memory_reference: memory_reference, attributes: attributes)
164
+ end
165
+ end
166
+
167
+ def parse_attributes(scanner)
168
+ attributes = {}
169
+ unless scanner.skip('>')
170
+ until scanner.eos?
171
+ scanner.skip(' @') or break
172
+ key = parse_identifier(scanner)
173
+ scanner.skip('=') or raise Error, 'no attribute keyval equal char'
174
+ attributes[key.intern] = parse_expression(scanner)
175
+ unless scanner.skip(',')
176
+ scanner.skip('>') or raise Error, "no object closer: #{scanner.inspect}"
177
+ break
111
178
  end
112
179
  end
113
- Tcepsni::Object.new(klass: klass, memory_reference: memory_reference, attributes: attributes)
114
180
  end
181
+ attributes
182
+ end
183
+
184
+ def parse_memory_reference(scanner)
185
+ scanner.skip(/:(?<memory_reference>0x[0-9a-f]{16})/) or raise Error, "no memory reference: #{scanner.inspect}"
186
+ Integer(scanner[:memory_reference])
115
187
  end
116
188
  end
117
189
 
@@ -124,7 +196,27 @@ module Tcepsni
124
196
  end
125
197
 
126
198
  def string_scanner?
127
- [name, *namespaces].include?(:StringScanner)
199
+ @name == :StringScanner && @namespaces.empty?
200
+ end
201
+
202
+ def pathname?
203
+ @name == :Pathname
204
+ end
205
+
206
+ def encoding?
207
+ @name == :Encoding && @namespaces.empty?
208
+ end
209
+
210
+ def ipaddr?
211
+ @name == :IPAddr && @namespaces.empty?
212
+ end
213
+
214
+ def active_support_ordered_options?
215
+ @name == :OrderedOptions && @namespaces == [:ActiveSupport]
216
+ end
217
+
218
+ def rails_paths_root?
219
+ @name == :Root && @namespaces == [:Rails, :Paths]
128
220
  end
129
221
  end
130
222
 
@@ -138,14 +230,29 @@ module Tcepsni
138
230
  end
139
231
  end
140
232
 
141
- class StringScanner
142
- attr_reader :pos, :total, :scanned, :rest
233
+ module Vendor
234
+ class StringScanner
235
+ attr_reader :pos, :total, :scanned, :rest
236
+
237
+ def initialize(pos:, total:, scanned:, rest:)
238
+ @pos = pos
239
+ @total = total
240
+ @scanned = scanned
241
+ @rest = rest
242
+ end
243
+ end
143
244
 
144
- def initialize(pos:, total:, scanned:, rest:)
145
- @pos = pos
146
- @total = total
147
- @scanned = scanned
148
- @rest = rest
245
+ module Rails
246
+ module Paths
247
+ class Root
248
+ attr_reader :memory_reference, :attributes
249
+
250
+ def initialize(memory_reference, attributes)
251
+ @memory_reference = memory_reference
252
+ @attributes = attributes
253
+ end
254
+ end
255
+ end
149
256
  end
150
257
  end
151
258
  end
data/sig/tcepsni.gen.rbs CHANGED
@@ -5,17 +5,27 @@ module Tcepsni
5
5
  VERSION: String
6
6
 
7
7
  # def self.parse: (String str) -> parsed
8
- def self.parse_expression: (untyped scanner) -> ((Array[(Array[(Class | Integer | Object | String | StringScanner | bool)?] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Class | Integer | Object | String | StringScanner | bool)?] | Integer | Object | String | StringScanner | bool)?] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Array[(Class | Integer | Object | String | StringScanner | bool)?] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Class | Integer | Object | String | StringScanner | bool)?] | Integer | Object | String | StringScanner | bool)?] | Integer | Object | String | StringScanner | bool)?)
8
+ def self.parse_expression: (untyped scanner) -> ((Array[(Array[(Class | Float | Integer | Object | String | Time | bool)?] | Hash[Object?, Object?] | Object)?] | Hash[Object?, (Array[Object?] | Hash[Object?, Object?] | Object)?] | Object)?)
9
+ def self.parse_integer: (untyped scanner) -> Integer
10
+ def self.parse_identifier: (untyped scanner) -> untyped
9
11
  def self.parse_string: (untyped scanner) -> String
10
- def self.parse_array: (untyped scanner) -> (Array[(Array[(Array[untyped] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, untyped] | Integer | Object | String | StringScanner | bool)?] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Array[untyped] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, untyped] | Integer | Object | String | StringScanner | bool)?] | Integer | Object | String | StringScanner | bool)?])
11
- def self.parse_hash: (untyped scanner) -> (Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Array[(Array[untyped] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, untyped] | Integer | Object | String | StringScanner | bool)?] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, (Array[untyped] | Class | Hash[(Class | Integer | Object | String | StringScanner | bool)?, untyped] | Integer | Object | String | StringScanner | bool)?] | Integer | Object | String | StringScanner | bool)?])
12
- def self.parse_class: (untyped scanner, ?name: nil) -> Class
13
- def self.parse_object: (untyped scanner) -> (Object | StringScanner)
12
+ def self.parse_array: (untyped scanner) -> (Array[(Array[(Array[untyped] | Class | Float | Hash[(Class | Float | Integer | Object | String | Time | bool)?, untyped] | Integer | Object | String | Time | bool)?] | Hash[Object?, (Array[untyped] | Hash[Object?, untyped] | Object)?] | Object)?])
13
+ def self.parse_hash: (untyped scanner) -> (Hash[Object?, (Array[(Array[untyped] | Hash[Object?, untyped] | Object)?] | Hash[Object?, (Array[untyped] | Hash[Object?, untyped] | Object)?] | Object)?])
14
+ def self.parse_class: (untyped scanner) -> Class
15
+ def self.parse_capitalized_identifier: (untyped scanner) -> untyped
16
+ def self.parse_object: (untyped scanner) -> (Encoding | IPAddr | Object | Pathname | Vendor::Rails::Paths::Root | Vendor::StringScanner)
17
+ def self.parse_attributes: (untyped scanner) -> (Hash[untyped, (Array[(Array[untyped] | Hash[Object?, untyped] | Object)?] | Hash[Object?, (Array[untyped] | Hash[Object?, untyped] | Object)?] | Object)?])
18
+ def self.parse_memory_reference: (untyped scanner) -> Integer
14
19
 
15
20
  class Class
16
21
  # def initialize: (name: Symbol?, namespaces: Array[Symbol]) -> void
17
22
  # def ==: (Class other) -> bool
18
23
  # def string_scanner?: -> bool
24
+ # def pathname?: -> bool
25
+ # def encoding?: -> bool
26
+ # def ipaddr?: -> bool
27
+ # def active_support_ordered_options?: -> bool
28
+ # def rails_paths_root?: -> bool
19
29
  # attr_reader name: Symbol?
20
30
  # attr_reader namespaces: Array[Symbol]
21
31
  end
@@ -27,11 +37,23 @@ module Tcepsni
27
37
  # attr_reader memory_reference: Integer
28
38
  end
29
39
 
30
- class StringScanner
31
- # def initialize: (pos: Integer, total: Integer, scanned: String, rest: String) -> void
32
- # attr_reader pos: Integer
33
- # attr_reader total: Integer
34
- # attr_reader scanned: String
35
- # attr_reader rest: String
40
+ module Vendor
41
+ class StringScanner
42
+ # def initialize: (pos: Integer, total: Integer, scanned: String, rest: String) -> void
43
+ # attr_reader pos: Integer
44
+ # attr_reader total: Integer
45
+ # attr_reader scanned: String
46
+ # attr_reader rest: String
47
+ end
48
+
49
+ module Rails
50
+ module Paths
51
+ class Root
52
+ # def initialize: (Integer memory_reference, Hash[Symbol, parsed?] attributes) -> void
53
+ # attr_reader memory_reference: Integer
54
+ # attr_reader attributes: Hash[Symbol, (Array[untyped] | Array[untyped] | Class | Hash[untyped, untyped] | Hash[untyped, untyped] | Integer | Object | String | StringScanner | Symbol | bool)?]
55
+ end
56
+ end
57
+ end
36
58
  end
37
59
  end
data/sig/tcepsni.rbs CHANGED
@@ -16,6 +16,11 @@ module Tcepsni
16
16
  def initialize: (name: Symbol?, namespaces: Array[Symbol]) -> void
17
17
  def ==: (Class other) -> bool
18
18
  def string_scanner?: -> bool
19
+ def pathname?: -> bool
20
+ def encoding?: -> bool
21
+ def ipaddr?: -> bool
22
+ def active_support_ordered_options?: -> bool
23
+ def rails_paths_root?: -> bool
19
24
  end
20
25
 
21
26
  class Object
@@ -25,11 +30,23 @@ module Tcepsni
25
30
  def initialize: (klass: Class, attributes: Hash[Symbol, parsed], memory_reference: Integer) -> void
26
31
  end
27
32
 
28
- class StringScanner
29
- attr_reader pos: Integer
30
- attr_reader total: Integer
31
- attr_reader scanned: String
32
- attr_reader rest: String
33
- def initialize: (pos: Integer, total: Integer, scanned: String, rest: String) -> void
33
+ module Vendor
34
+ class StringScanner
35
+ attr_reader pos: Integer
36
+ attr_reader total: Integer
37
+ attr_reader scanned: String
38
+ attr_reader rest: String
39
+ def initialize: (pos: Integer, total: Integer, scanned: String, rest: String) -> void
40
+ end
41
+
42
+ module Rails
43
+ module Paths
44
+ class Root
45
+ attr_reader memory_reference: Integer
46
+ attr_reader attributes: Hash[Symbol, parsed?]
47
+ def initialize: (Integer memory_reference, Hash[Symbol, parsed?] attributes) -> void
48
+ end
49
+ end
50
+ end
34
51
  end
35
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcepsni
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gemmaro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-01 00:00:00.000000000 Z
11
+ date: 2023-10-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Tcepsni gem is a parser library for inspected strings of Ruby objects.
14
14
  email:
@@ -45,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  requirements: []
48
- rubygems_version: 3.4.10
48
+ rubygems_version: 3.3.26
49
49
  signing_key:
50
50
  specification_version: 4
51
51
  summary: inspected Ruby objects parser