sourcify 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ === 0.4.2 (Feb 06, 2011)
2
+
3
+ * fixes Sourcify::NoMatchingProcError when inline proc contains hashes (issue#7) [#ngty]
4
+ * uses RubyParser#parse to eval correctness of code instead of Kernel#eval [#ngty]
5
+
1
6
  === 0.4.1 (Jan 29, 2011)
2
7
 
3
8
  * fixes Sourcify::NoMatchingProcError when if/unless/until/while modifier follows
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 0.4.2
@@ -1,12 +1,12 @@
1
1
  Sourcify.require_rb('proc', 'scanner')
2
+ %w{normalizer code_scanner source_code converter}.each do |file|
3
+ Sourcify.require_rb('proc', 'parser', file)
4
+ end
2
5
 
3
6
  module Sourcify
4
7
  module Proc
5
8
  class Parser #:nodoc:all
6
9
 
7
- RUBY_PARSER = RubyParser.new
8
- RUBY_2_RUBY = Ruby2Ruby.new
9
-
10
10
  IS_19x = RUBY_VERSION.include?('1.9.')
11
11
 
12
12
  def initialize(_proc)
@@ -17,13 +17,12 @@ module Sourcify
17
17
  end
18
18
 
19
19
  def source(opts)
20
- (@sources ||= {})[opts.hash] ||=
21
- RUBY_2_RUBY.process(Sexp.from_array(sexp(opts).to_a))
20
+ (@sources ||= {})[opts.hash] ||= Converter.to_code(sexp(opts))
22
21
  end
23
22
 
24
23
  def sexp(opts)
25
24
  (@sexps ||= {})[opts.hash] ||= (
26
- raw_sexp = RUBY_PARSER.parse(raw_source(opts), @source_code.file)
25
+ raw_sexp = Converter.to_sexp(raw_source(opts), @source_code.file)
27
26
  sexp = Normalizer.process(raw_sexp, @binding)
28
27
  opts[:strip_enclosure] ? Sexp.from_array(sexp.to_a.last) : sexp
29
28
  )
@@ -41,110 +40,6 @@ module Sourcify
41
40
  end
42
41
  end
43
42
 
44
- class Normalizer
45
- class << self
46
-
47
- def process(sexp, binding)
48
- @binding = binding
49
- Sexp.from_array(fix_no_arg_method_calls(sexp.to_a))
50
- end
51
-
52
- def fix_no_arg_method_calls(array)
53
- return array if [:class, :sclass, :defn, :module].include?(array[0])
54
- array.map do |e|
55
- if e.is_a?(Array)
56
- no_arg_method_call?(e) or fix_no_arg_method_calls(e)
57
- else
58
- e
59
- end
60
- end
61
- end
62
-
63
- def no_arg_method_call?(e)
64
- if like_no_arg_method_call?(e)
65
- bounded_var?(var = e[2]) ? [:lvar, var] : e
66
- end
67
- end
68
-
69
- def like_no_arg_method_call?(e)
70
- e.size == 4 && e[0..1] == [:call, nil] &&
71
- e[3] == [:arglist] && (var = e[2]).is_a?(Symbol)
72
- end
73
-
74
- def bounded_var?(var)
75
- qvar = (@q ||= (IS_19x ? ":%s" : "'%s'")) % var
76
- @binding.eval("local_variables.include?(#{qvar})")
77
- end
78
-
79
- end
80
- end
81
-
82
- class CodeScanner
83
- class << self
84
-
85
- def process(source_code, opts, &matcher)
86
- results = rscan(source_code.to_s, {
87
- :start_pattern => scan_pattern_hint(opts[:attached_to]),
88
- :body_matcher => opts[:body_matcher],
89
- :ignore_nested => opts[:ignore_nested],
90
- :stop_on_newline => false,
91
- }).flatten.select(&matcher)
92
- case results.size
93
- when 0 then raise NoMatchingProcError
94
- when 1 then ("\n" * source_code.line) + results[0]
95
- else raise MultipleMatchingProcsPerLineError
96
- end
97
- end
98
-
99
- def scan_pattern_hint(val)
100
- case val
101
- when Regexp then val
102
- when String, Symbol then /^(?:.*?\W|)#{val}(?:\W)/
103
- when nil then /.*/
104
- else raise TypeError
105
- end
106
- end
107
-
108
- def rscan(str, opts)
109
- results = Scanner.process(str, opts) || []
110
- return results if opts[:ignore_nested]
111
- results.map do |outer|
112
- inner = rscan(outer.sub(/^proc\s*(do|\{)/,''), opts.merge(:stop_on_newline => true))
113
- [outer, inner]
114
- end
115
- end
116
-
117
- end
118
- end
119
-
120
- class SourceCode < Struct.new(:file, :line)
121
-
122
- def line
123
- super.pred
124
- end
125
-
126
- def to_s
127
- case file
128
- when /\(irb\)/ then from_irb_to_s
129
- else from_file_to_s
130
- end
131
- end
132
-
133
- def from_file_to_s
134
- File.open(file, 'r') do |fh|
135
- fh.extend(File::Tail).forward(line)
136
- fh.readlines.join
137
- end
138
- end
139
-
140
- def from_irb_to_s
141
- # Really owe it to Florian Groß's solution @ http://rubyquiz.com/quiz38.html ...
142
- # anyway, note that we use *line.succ* instead of *line* here.
143
- IRB.CurrentContext.io.line(line.succ .. -1).join
144
- end
145
-
146
- end
147
-
148
43
  end
149
44
  end
150
45
  end
@@ -0,0 +1,43 @@
1
+ module Sourcify
2
+ module Proc
3
+ class Parser #:nodoc:all
4
+ class CodeScanner
5
+ class << self
6
+
7
+ def process(source_code, opts, &matcher)
8
+ results = rscan(source_code.to_s, {
9
+ :start_pattern => scan_pattern_hint(opts[:attached_to]),
10
+ :body_matcher => opts[:body_matcher],
11
+ :ignore_nested => opts[:ignore_nested],
12
+ :stop_on_newline => false,
13
+ }).flatten.select(&matcher)
14
+ case results.size
15
+ when 0 then raise NoMatchingProcError
16
+ when 1 then ("\n" * source_code.line) + results[0]
17
+ else raise MultipleMatchingProcsPerLineError
18
+ end
19
+ end
20
+
21
+ def scan_pattern_hint(val)
22
+ case val
23
+ when Regexp then val
24
+ when String, Symbol then /^(?:.*?\W|)#{val}(?:\W)/
25
+ when nil then /.*/
26
+ else raise TypeError
27
+ end
28
+ end
29
+
30
+ def rscan(str, opts)
31
+ results = Scanner.process(str, opts) || []
32
+ return results if opts[:ignore_nested]
33
+ results.map do |outer|
34
+ inner = rscan(outer.sub(/^proc\s*(do|\{)/,''), opts.merge(:stop_on_newline => true))
35
+ [outer, inner]
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ module Sourcify
2
+ module Proc
3
+ class Parser #:nodoc:all
4
+ class Converter
5
+ class << self
6
+
7
+ RUBY_PARSER = RubyParser.new
8
+ RUBY_2_RUBY = Ruby2Ruby.new
9
+
10
+ def to_sexp(code, file = nil)
11
+ retried = false
12
+ begin
13
+ RUBY_PARSER.reset
14
+ (retried ? RubyParser.new : RUBY_PARSER).parse(*[code, file].compact)
15
+ rescue Racc::ParseError, SyntaxError
16
+ return nil if retried
17
+ retried = true; retry
18
+ end
19
+ end
20
+
21
+ def to_code(sexp)
22
+ RUBY_2_RUBY.process(Sexp.from_array(sexp.to_a))
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ module Sourcify
2
+ module Proc
3
+ class Parser #:nodoc:all
4
+ class Normalizer
5
+ class << self
6
+
7
+ def process(sexp, binding)
8
+ @binding = binding
9
+ Sexp.from_array(fix_no_arg_method_calls(sexp.to_a))
10
+ end
11
+
12
+ def fix_no_arg_method_calls(array)
13
+ return array if [:class, :sclass, :defn, :module].include?(array[0])
14
+ array.map do |e|
15
+ if e.is_a?(Array)
16
+ no_arg_method_call?(e) or fix_no_arg_method_calls(e)
17
+ else
18
+ e
19
+ end
20
+ end
21
+ end
22
+
23
+ def no_arg_method_call?(e)
24
+ if like_no_arg_method_call?(e)
25
+ bounded_var?(var = e[2]) ? [:lvar, var] : e
26
+ end
27
+ end
28
+
29
+ def like_no_arg_method_call?(e)
30
+ e.size == 4 && e[0..1] == [:call, nil] &&
31
+ e[3] == [:arglist] && (var = e[2]).is_a?(Symbol)
32
+ end
33
+
34
+ def bounded_var?(var)
35
+ qvar = (@q ||= (IS_19x ? ":%s" : "'%s'")) % var
36
+ @binding.eval("local_variables.include?(#{qvar})")
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ module Sourcify
2
+ module Proc
3
+ class Parser #:nodoc:all
4
+ class SourceCode < Struct.new(:file, :line)
5
+
6
+ def line
7
+ super.pred
8
+ end
9
+
10
+ def to_s
11
+ case file
12
+ when /\(irb\)/ then from_irb_to_s
13
+ else from_file_to_s
14
+ end
15
+ end
16
+
17
+ def from_file_to_s
18
+ File.open(file, 'r') do |fh|
19
+ fh.extend(File::Tail).forward(line)
20
+ fh.readlines.join
21
+ end
22
+ end
23
+
24
+ def from_irb_to_s
25
+ # Really owe it to Florian Groß's solution @ http://rubyquiz.com/quiz38.html ...
26
+ # anyway, note that we use *line.succ* instead of *line* here.
27
+ IRB.CurrentContext.io.line(line.succ .. -1).join
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -16,16 +16,7 @@ module Sourcify
16
16
  end
17
17
 
18
18
  def closed?
19
- # NOTE: The only real error is SyntaxError, other errors are due
20
- # to undefined variable or watever, which are perfectly ok when
21
- # testing for validity of the string.
22
- begin
23
- instance_eval(safe_contents) if evaluable?
24
- rescue SyntaxError
25
- false
26
- rescue Exception
27
- true
28
- end
19
+ evaluable? && parsable?
29
20
  end
30
21
 
31
22
  private
@@ -36,10 +27,20 @@ module Sourcify
36
27
  @contents[-1][-1].chr == end_tag
37
28
  end
38
29
 
30
+ def parsable?
31
+ begin
32
+ Parser::Converter.to_sexp <<-SOURCIFIED_HEREDOKIE.strip
33
+ #{safe_contents}
34
+ SOURCIFIED_HEREDOKIE
35
+ rescue Exception
36
+ true
37
+ end
38
+ end
39
+
39
40
  def safe_contents
40
41
  # NOTE: %x & ` strings are dangerous to eval cos they execute shell commands,
41
42
  # thus we convert them to normal strings 1st
42
- @contents.join.gsub(/(%x)(\W|\_)/, '%Q\2').gsub(/.{0,2}(`)/) do |s|
43
+ to_s.gsub(/(%x)(\W|\_)/, '%Q\2').gsub(/.{0,2}(`)/) do |s|
43
44
  s =~ /^(%Q|%W|%r|%x|.?%|.?\\)/ ? s : s.sub(/`$/,'%Q`')
44
45
  end
45
46
  end
@@ -8,6 +8,7 @@ module Sourcify
8
8
  module Extensions
9
9
 
10
10
  class Escape < Exception; end
11
+ RUBY_PARSER = RubyParser.new
11
12
 
12
13
  def process(data, opts={})
13
14
  begin
@@ -32,9 +33,7 @@ module Sourcify
32
33
 
33
34
  def push_dstring(ts, te)
34
35
  data = data_frag(ts .. te.pred)
35
- unless @dstring
36
- @dstring = DString.new(data[%r{^("|`|/|%(?:Q|W|r|x|)(?:\W|_))},1])
37
- end
36
+ @dstring ||= DString.new(data[%r{^("|`|/|%(?:Q|W|r|x|)(?:\W|_))},1])
38
37
  @dstring << data
39
38
  return true unless @dstring.closed?
40
39
  @tokens << [:dstring, @dstring.to_s]
@@ -104,6 +103,7 @@ module Sourcify
104
103
 
105
104
  def fix_counter_false_start(key)
106
105
  return unless this_counter(key).just_started?
106
+ return unless really_false_started?
107
107
  reset_attributes
108
108
  @tokens, @false_start_backup = @false_start_backup.dup, nil if @false_start_backup
109
109
  end
@@ -117,10 +117,12 @@ module Sourcify
117
117
  end
118
118
 
119
119
  def construct_result_code
120
+ code = <<-SOURCIFIED_HEREDOKIE.strip
121
+ proc #{codified_tokens}
122
+ SOURCIFIED_HEREDOKIE
123
+
120
124
  begin
121
- code = 'proc ' + codified_tokens
122
- eval(code) # TODO: any better way to check for completeness of proc code ??
123
- if @body_matcher.call(code)
125
+ if safe_eval(code) && @body_matcher.call(code)
124
126
  @results << code
125
127
  raise Escape if @stop_on_newline or @lineno != 1
126
128
  reset_attributes
@@ -143,6 +145,13 @@ module Sourcify
143
145
  @rejecting_block = false
144
146
  end
145
147
 
148
+ def really_false_started?
149
+ safe_eval(<<-SOURCIFIED_HEREDOKIE.strip
150
+ #{codified_tokens} 1}
151
+ SOURCIFIED_HEREDOKIE
152
+ ) && true
153
+ end
154
+
146
155
  def offset_attributes
147
156
  @lineno = 1 # Fixing JRuby's lineno bug (see http://jira.codehaus.org/browse/JRUBY-5014)
148
157
  unless @tokens.empty?
@@ -152,6 +161,10 @@ module Sourcify
152
161
  end
153
162
  end
154
163
 
164
+ def safe_eval(string)
165
+ Parser::Converter.to_sexp(string)
166
+ end
167
+
155
168
  end
156
169
  end
157
170
  end
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["NgTzeYang"]
12
- s.date = %q{2011-01-29}
12
+ s.date = %q{2011-02-06}
13
13
  s.description = %q{}
14
14
  s.email = %q{ngty77@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.files = [
20
20
  ".document",
21
21
  ".infinity_test",
22
+ ".rvmrc",
22
23
  "HISTORY.txt",
23
24
  "LICENSE",
24
25
  "README.rdoc",
@@ -31,6 +32,10 @@ Gem::Specification.new do |s|
31
32
  "lib/sourcify/proc/methods/to_sexp.rb",
32
33
  "lib/sourcify/proc/methods/to_source.rb",
33
34
  "lib/sourcify/proc/parser.rb",
35
+ "lib/sourcify/proc/parser/code_scanner.rb",
36
+ "lib/sourcify/proc/parser/converter.rb",
37
+ "lib/sourcify/proc/parser/normalizer.rb",
38
+ "lib/sourcify/proc/parser/source_code.rb",
34
39
  "lib/sourcify/proc/scanner.rb",
35
40
  "lib/sourcify/proc/scanner.rl",
36
41
  "lib/sourcify/proc/scanner/comment.rb",
@@ -162,15 +167,18 @@ Gem::Specification.new do |s|
162
167
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
163
168
  s.add_runtime_dependency(%q<ruby2ruby>, [">= 1.2.5"])
164
169
  s.add_runtime_dependency(%q<sexp_processor>, [">= 3.0.5"])
170
+ s.add_runtime_dependency(%q<file-tail>, [">= 1.0.5"])
165
171
  s.add_development_dependency(%q<bacon>, [">= 0"])
166
172
  else
167
173
  s.add_dependency(%q<ruby2ruby>, [">= 1.2.5"])
168
174
  s.add_dependency(%q<sexp_processor>, [">= 3.0.5"])
175
+ s.add_dependency(%q<file-tail>, [">= 1.0.5"])
169
176
  s.add_dependency(%q<bacon>, [">= 0"])
170
177
  end
171
178
  else
172
179
  s.add_dependency(%q<ruby2ruby>, [">= 1.2.5"])
173
180
  s.add_dependency(%q<sexp_processor>, [">= 3.0.5"])
181
+ s.add_dependency(%q<file-tail>, [">= 1.0.5"])
174
182
  s.add_dependency(%q<bacon>, [">= 0"])
175
183
  end
176
184
  end
@@ -26,6 +26,30 @@ describe "Proc#to_source from { ... } block (w nested hash)" do
26
26
  \)
27
27
  end
28
28
 
29
+ should 'handle method w only hash args' do
30
+ (
31
+ lambda {
32
+ test :a => 2
33
+ }
34
+ ).should.be having_source(%Q\
35
+ proc do
36
+ test(:a => 2)
37
+ end
38
+ \)
39
+ end
40
+
41
+ should 'handle method w mixed args' do
42
+ (
43
+ lambda {
44
+ test 1, :a => 2
45
+ }
46
+ ).should.be having_source(%Q\
47
+ proc do
48
+ test(1, :a => 2)
49
+ end
50
+ \)
51
+ end
52
+
29
53
  if RUBY_VERSION.include?('1.9.')
30
54
  require File.join(File.expand_path(File.dirname(__FILE__)), '19x_extras')
31
55
  behaves_like 'Proc#to_source from { ... } block (1.9.*)'
@@ -66,14 +66,15 @@ describe 'Proc#to_source w specified {:attached_to => ...}' do
66
66
  describe '>> w false start as a result of preceding hash' do
67
67
 
68
68
  option = {:attached_to => :watever}
69
+ aa = :aa
69
70
 
70
71
  should 'handle for do ... end block' do
71
- x = watever({:aa => 1, :bb => 3}) do :blah end
72
+ x = watever({aa => 1, :bb => 3}) do :blah end
72
73
  x.should.be having_source('proc { :blah }', option)
73
74
  end
74
75
 
75
76
  should 'handle for { ... } block' do
76
- x = watever({:aa => 1, :bb => 3}) { :blah }
77
+ x = watever({aa => 1, :bb => 3}) { :blah }
77
78
  x.should.be having_source('proc { :blah }', option)
78
79
  end
79
80
 
@@ -5,22 +5,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
5
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
6
  require 'sourcify'
7
7
 
8
- # ///////////////////////////////////////////////////////////
9
- # Regenerate ragel-based scanner
10
- # ///////////////////////////////////////////////////////////
11
-
12
- ragel_dir = File.join(File.dirname(__FILE__), '..', 'lib', 'sourcify', 'proc')
13
- ragel_file = File.join(ragel_dir, 'scanner.rl')
14
- ruby_file = File.join(ragel_dir, 'scanner.rb')
15
- File.delete(ruby_file) rescue nil
16
- system("ragel -R #{ragel_file}")
17
-
18
- begin
19
- require File.join(ragel_dir, 'scanner.rb')
20
- rescue LoadError
21
- raise $!
22
- end
23
-
24
8
  # ///////////////////////////////////////////////////////////
25
9
  # Bacon
26
10
  # ///////////////////////////////////////////////////////////
@@ -75,11 +59,9 @@ end
75
59
 
76
60
  def having_source(expected, opts={}, &matcher)
77
61
  lambda do |_proc|
78
- if block_given?
79
- normalize_code(_proc.to_source(&matcher)) == normalize_code(expected)
80
- else
81
- normalize_code(_proc.to_source(opts)) == normalize_code(expected)
82
- end
62
+ normalize_code(expected) # added for bug fixing
63
+ normalize_code(block_given? ? _proc.to_source(&matcher) : _proc.to_source(opts)) \
64
+ == normalize_code(expected)
83
65
  end
84
66
  end
85
67
 
@@ -117,3 +99,20 @@ def irb_exec(stdin_str)
117
99
  (values[-1].nil? && RUBY_VERSION.include?('1.9.2')) ? values[0 .. -2] : values
118
100
  end
119
101
 
102
+ # ///////////////////////////////////////////////////////////
103
+ # Regenerate ragel-based scanner
104
+ # ///////////////////////////////////////////////////////////
105
+
106
+ unless has_parsetree?
107
+ ragel_dir = File.join(File.dirname(__FILE__), '..', 'lib', 'sourcify', 'proc')
108
+ ragel_file = File.join(ragel_dir, 'scanner.rl')
109
+ ruby_file = File.join(ragel_dir, 'scanner.rb')
110
+ File.delete(ruby_file) rescue nil
111
+ system("ragel -R #{ragel_file}")
112
+
113
+ begin
114
+ require File.join(ragel_dir, 'scanner.rb')
115
+ rescue LoadError
116
+ raise $!
117
+ end
118
+ end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sourcify
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 4
9
- - 1
10
- version: 0.4.1
8
+ - 2
9
+ version: 0.4.2
11
10
  platform: ruby
12
11
  authors:
13
12
  - NgTzeYang
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2011-01-30 00:00:00 +08:00
17
+ date: 2011-02-06 00:00:00 +08:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,7 +25,6 @@ dependencies:
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- hash: 21
30
28
  segments:
31
29
  - 1
32
30
  - 2
@@ -42,7 +40,6 @@ dependencies:
42
40
  requirements:
43
41
  - - ">="
44
42
  - !ruby/object:Gem::Version
45
- hash: 13
46
43
  segments:
47
44
  - 3
48
45
  - 0
@@ -58,7 +55,6 @@ dependencies:
58
55
  requirements:
59
56
  - - ">="
60
57
  - !ruby/object:Gem::Version
61
- hash: 29
62
58
  segments:
63
59
  - 1
64
60
  - 0
@@ -74,7 +70,6 @@ dependencies:
74
70
  requirements:
75
71
  - - ">="
76
72
  - !ruby/object:Gem::Version
77
- hash: 3
78
73
  segments:
79
74
  - 0
80
75
  version: "0"
@@ -105,6 +100,10 @@ files:
105
100
  - lib/sourcify/proc/methods/to_sexp.rb
106
101
  - lib/sourcify/proc/methods/to_source.rb
107
102
  - lib/sourcify/proc/parser.rb
103
+ - lib/sourcify/proc/parser/code_scanner.rb
104
+ - lib/sourcify/proc/parser/converter.rb
105
+ - lib/sourcify/proc/parser/normalizer.rb
106
+ - lib/sourcify/proc/parser/source_code.rb
108
107
  - lib/sourcify/proc/scanner.rb
109
108
  - lib/sourcify/proc/scanner.rl
110
109
  - lib/sourcify/proc/scanner/comment.rb
@@ -181,7 +180,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
181
180
  requirements:
182
181
  - - ">="
183
182
  - !ruby/object:Gem::Version
184
- hash: 59
185
183
  segments:
186
184
  - 1
187
185
  - 8
@@ -192,7 +190,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
190
  requirements:
193
191
  - - ">="
194
192
  - !ruby/object:Gem::Version
195
- hash: 3
196
193
  segments:
197
194
  - 0
198
195
  version: "0"