yayaml 0.1.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.
data/en.yml ADDED
@@ -0,0 +1,22 @@
1
+ en:
2
+ hello: "Hello world"
3
+ timeline:
4
+ events:
5
+ invoice:
6
+ payment_failed: "Invoice payment failed"
7
+ outstanding_invoices_paid: Outstanding invoices paid
8
+ source_added: Customer added default credit card
9
+ source_updated: Customer updated default credit card
10
+ subscription:
11
+ canceled: "Subscription canceled"
12
+ canceled_at_period_end: "Subscription set to cancel at the end of the current billing cycle"
13
+ subscription_schedule_released: "Subscription schedule released"
14
+ updated_through_creation: "Subscription was updated via immediate downgrade in Stripe"
15
+ testing:
16
+ event_for_test_without_interpolation: "testing"
17
+ event_for_test_with_interpolation: "testing %{test_var}"
18
+ errors:
19
+ business_address:
20
+ argument_error:
21
+ already_exists: "A business address is already associated with this account"
22
+ incorrect_zip_format: "should be in the format of 12345 or 12345-6789"
data/example.rb ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "psych"
4
+ require "pry"
5
+
6
+ # Handler for detecting scalar values
7
+ class YayHandler < Psych::Handler
8
+ # States:
9
+ # a -> start_map -> b
10
+ # b -> scalar -> c (push key)
11
+ # b -> end_map -> b (pop key)
12
+ # c -> scalar -> b (push value, pop key)
13
+ # c -> start_map -> b (push map)
14
+ # c -> end_map -> b (pop key)
15
+
16
+ attr_accessor :parser
17
+
18
+ def initialize(*args)
19
+ @state = :a
20
+ @prev = nil
21
+ @keys = []
22
+ end
23
+
24
+ def start_mapping(*args)
25
+ @prev = @state
26
+ case @state
27
+ when :a
28
+ @state = :b
29
+ when :c
30
+ @state = :b
31
+ else
32
+ fail
33
+ end
34
+
35
+ transition("start map")
36
+ end
37
+
38
+ def end_mapping
39
+ @prev = @state
40
+ case @state
41
+ when :b, :c
42
+ @state = :b
43
+ @keys.pop
44
+ else
45
+ fail
46
+ end
47
+
48
+ transition("end map")
49
+ end
50
+
51
+ def scalar(value, *args)
52
+ @prev = @state
53
+ case @state
54
+ when :b
55
+ @state = :c
56
+ @keys << value
57
+ when :c
58
+ @state = :b
59
+ puts "#{@keys.join('.')}:#{parser.mark.line + 1}: #{value}"
60
+ @keys.pop
61
+ else
62
+ fail
63
+ end
64
+
65
+ transition("scalar")
66
+ end
67
+
68
+ def transition(event)
69
+ # puts "#@prev -> (#{event}) -> #@state"
70
+ end
71
+ end
72
+
73
+ # yaml = File.read("/Users/timuruski/en.yml")
74
+ yaml = DATA
75
+ handler = YayHandler.new
76
+ parser = Psych::Parser.new(handler)
77
+ handler.parser = parser
78
+ parser.parse(yaml)
79
+
80
+ __END__
81
+ ---
82
+ en:
83
+ page1:
84
+ button: Hello
85
+ page2:
86
+ button: Goodbye
87
+ fr:
88
+ page1:
89
+ button: Bonjour
data/example.yml ADDED
@@ -0,0 +1,17 @@
1
+ ---
2
+ en:
3
+ page1:
4
+ button: Hello world!
5
+ page2:
6
+ button: Goodbye
7
+ page3:
8
+ shopping:
9
+ - eggs
10
+ - butter
11
+ - bacon
12
+ button: Go shopping
13
+ es:
14
+ page1:
15
+ button: "¡Hola, el mundo!"
16
+ page2:
17
+ button: Adios
data/exe/ya ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "psych"
4
+ require "optparse"
5
+
6
+ at_exit do
7
+ Yay::Command.new.run
8
+ end
9
+
10
+ module Yay
11
+ BOLD_RED = "\e[1m\e[38;5;1m"
12
+ RESET = "\e[0m"
13
+
14
+ class Command
15
+ def initialize(args = ARGV)
16
+ parser = OptionParser.new do |parser|
17
+ parser.on("-d", "--debug") { $DEBUG = true }
18
+ parser.on("-i", "--ignore-case") { |value| @ignore_case = value }
19
+ parser.on("-p", "--search-path") { |value| @match_path = value }
20
+ end
21
+
22
+ parser.banner = "Usage yay [options] \"<search pattern>\" <inputs>"
23
+ if args.empty?
24
+ abort parser.help
25
+ else
26
+ parser.parse!
27
+ end
28
+
29
+ @search_pattern = args.shift
30
+ @paths = args.flat_map { |filename| File.directory?(filename) ? Dir.glob("#{filename}/**/*.yml") : filename }
31
+ @paths.delete("--")
32
+ end
33
+
34
+ # TODO: Use abort if all YAML paths fail to parse.
35
+ def run
36
+ if @paths.empty?
37
+ warn "Reading from STDIN..." if STDIN.tty?
38
+ parse_yaml(STDIN, "STDIN")
39
+ else
40
+ @paths.each do |path|
41
+ File.open(path) do |file|
42
+ parse_yaml(file, file.path)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ private def parse_yaml(input, filename)
49
+ handler = YayHandler.new(@search_pattern, @match_path, @ignore_case, filename)
50
+ Psych::Parser.new(handler).parse(input)
51
+ rescue Psych::SyntaxError => e
52
+ warn "Failed to parse YAML #{e.file}:#{e.line}:#{e.offset}"
53
+ end
54
+ end
55
+
56
+ # See these for details:
57
+ # https://ruby-doc.org/3.2.2/exts/psych/Psych/Handler.html
58
+ # https://github.com/ruby/psych/blob/master/lib/psych/handler.rb
59
+ class YayHandler < Psych::Handler
60
+ # State transitions:
61
+ # branch -> start_map -> branch (no-op)
62
+ # leaf -> start_map -> branch (no-op)
63
+ # branch -> scalar -> leaf (push key)
64
+ # leaf -> scalar -> leaf (pop key, handle value)
65
+ # branch -> end_map -> branch (pop key)
66
+ # leaf -> end_map -> branch (pop key)
67
+
68
+ def initialize(search_pattern, match_path, ignore_case, filename, *args)
69
+ @search_pattern = Regexp.new(search_pattern, ignore_case ? Regexp::IGNORECASE : nil)
70
+ @ignore_case = ignore_case
71
+ @match_path = match_path
72
+ @filename = filename
73
+ end
74
+
75
+ def start_document(*args)
76
+ debug "--> Start document #{@filename}"
77
+
78
+ @state = :branch
79
+ @prev = nil
80
+ @keys = []
81
+ @line = 0
82
+ @col = 0
83
+ end
84
+
85
+ def start_mapping(*args)
86
+ transition("start map") do
87
+ case @state
88
+ when :branch, :leaf
89
+ @state = :branch
90
+ else
91
+ panic("start map")
92
+ end
93
+ end
94
+ end
95
+
96
+ def end_mapping
97
+ transition("end map") do
98
+ case @state
99
+ when :branch, :leaf
100
+ @state = :branch
101
+ @keys.pop
102
+ else
103
+ panic("end map")
104
+ end
105
+ end
106
+ end
107
+
108
+ def scalar(value, *args)
109
+ transition("scalar") do
110
+ case @state
111
+ when :branch
112
+ @state = :leaf
113
+ @keys.push(value)
114
+ when :leaf
115
+ @state = :branch
116
+ handle_leaf(value)
117
+ @keys.pop
118
+ else
119
+ panic("scalar")
120
+ end
121
+ end
122
+ end
123
+
124
+ def event_location(start_line, start_column, _end_line, _end_column)
125
+ @line = start_line + 1
126
+ @col = start_column + 1
127
+ end
128
+
129
+ private def handle_leaf(value)
130
+ path = @keys.join(".")
131
+
132
+ if @match_path
133
+ if match = path.match(@search_pattern)
134
+ if $stdout.tty?
135
+ path_matched = path.sub(@search_pattern, "#{BOLD_RED}#{match}#{RESET}")
136
+ puts "#{@filename}:#{@line} #{path_matched}: #{value}"
137
+ else
138
+ puts "#{@filename}:#{@line} #{path}: #{value}"
139
+ end
140
+ end
141
+ else
142
+ if match = value.match(@search_pattern)
143
+ if $stdout.tty?
144
+ value_matched = value.sub(@search_pattern, "#{BOLD_RED}#{match}#{RESET}")
145
+ puts "#{@filename}:#{@line} #{path}: #{value_matched}"
146
+ else
147
+ puts "#{@filename}:#{@line} #{path}: #{value}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ private def transition(event)
154
+ @prev = @state
155
+ yield
156
+ debug "#@prev -> (#{event}) -> #@state"
157
+ end
158
+
159
+ private def debug(msg)
160
+ warn msg if $DEBUG || ENV["DEBUG"]
161
+ end
162
+
163
+ private def panic(event)
164
+ fail "Failed to parse #{event} at #{@filename}:#{@line}:#{@col}, state #{@state}"
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,88 @@
1
+ require "psych"
2
+
3
+ module Yayaml
4
+ class Handler < Psych::Handler
5
+ # State transitions:
6
+ # branch -> start_map -> branch (no-op)
7
+ # leaf -> start_map -> branch (no-op)
8
+ # branch -> scalar -> leaf (push key)
9
+ # leaf -> scalar -> leaf (pop key, handle value)
10
+ # branch -> end_map -> branch (pop key)
11
+ # leaf -> end_map -> branch (pop key)
12
+
13
+ attr_reader :matcher, :filename
14
+
15
+ def initialize(matcher, filename: nil)
16
+ @matcher = matcher
17
+ @filename = filename
18
+ end
19
+
20
+ def start_document(*args)
21
+ debug "--> Start document #{filename}"
22
+
23
+ @state = :branch
24
+ @prev = nil
25
+ @keys = []
26
+ @line = 0
27
+ @col = 0
28
+ end
29
+
30
+ def start_mapping(*args)
31
+ transition("start map") do
32
+ case @state
33
+ when :branch, :leaf
34
+ @state = :branch
35
+ else
36
+ panic("start_mapping")
37
+ end
38
+ end
39
+ end
40
+
41
+ def end_mapping
42
+ transition("end map") do
43
+ case @state
44
+ when :branch, :leaf
45
+ @state = :branch
46
+ @keys.pop
47
+ else
48
+ panic("end_mapping")
49
+ end
50
+ end
51
+ end
52
+
53
+ def scalar(value, *args)
54
+ transition("scalar") do
55
+ case @state
56
+ when :branch
57
+ @state = :leaf
58
+ @keys.push(value)
59
+ when :leaf
60
+ @state = :branch
61
+ matcher.on_node(@keys.dup, value)
62
+ @keys.pop
63
+ else
64
+ panic("scalar")
65
+ end
66
+ end
67
+ end
68
+
69
+ def event_location(start_line, start_column, end_line, end_column)
70
+ @line = start_line + 1
71
+ @col = start_column + 1
72
+ end
73
+
74
+ private def transition(event)
75
+ @prev = @state
76
+ yield
77
+ # debug "#@prev -> (#{event}) -> #@state"
78
+ end
79
+
80
+ private def debug(msg)
81
+ warn msg if @debug
82
+ end
83
+
84
+ private def panic(event)
85
+ fail "Failed to parse #{event} at #{filename}:#{@line}:#{@col}"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,52 @@
1
+ module Yayaml
2
+ class Matcher
3
+ BOLD_RED = "\e[1m\e[38;5;1m"
4
+ RESET = "\e[0m"
5
+
6
+ attr_reader :nodes
7
+
8
+ def initialize(pattern, match_path: false, ignore_case: false)
9
+ @pattern = Regexp.compile(pattern)
10
+ @match_path = match_path
11
+ @ignore_case = ignore_case
12
+
13
+ @nodes = []
14
+ end
15
+
16
+ def on_node(keys, value)
17
+ @nodes << [keys, value]
18
+
19
+ path = keys.join(".")
20
+
21
+ if @match_path
22
+ if (match = path.match(@pattern))
23
+ path_matched = path.sub(@pattern, "#{BOLD_RED}#{match}#{RESET}")
24
+ # puts "#{path_matched}:#{@line}: #{value}"
25
+ puts "#{@filename}:#{@line} #{path_matched}: #{value}"
26
+ end
27
+ else
28
+ if (match = value.match(@pattern))
29
+ value_matched = value.sub(@pattern, "#{BOLD_RED}#{match}#{RESET}")
30
+ # puts "#{path}:#{@line}: #{value_matched}"
31
+ puts "#{@filename}:#{@line} #{path}: #{value_matched}"
32
+ end
33
+ end
34
+ end
35
+
36
+ private def match_value(keys, value)
37
+ if (match = value.match(@pattern))
38
+ value_matched = value.sub(@pattern, "#{BOLD_RED}#{match}#{RESET}")
39
+ # puts "#{path}:#{@line}: #{value_matched}"
40
+ puts "#{@filename}:#{@line} #{path}: #{value_matched}"
41
+ end
42
+ end
43
+
44
+ private def match_path(keys, value)
45
+ if (match = path.match(@pattern))
46
+ path_matched = path.sub(@pattern, "#{BOLD_RED}#{match}#{RESET}")
47
+ # puts "#{path_matched}:#{@line}: #{value}"
48
+ puts "#{@filename}:#{@line} #{path_matched}: #{value}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yayaml
4
+ VERSION = "0.1.0"
5
+ end
data/lib/yayaml.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Yayaml
2
+ end
3
+
4
+ require_relative "yayaml/handler"
5
+ require_relative "yayaml/matcher"
data/sig/yay/rb.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Yay
2
+ module Rb
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
data/test.yml ADDED
@@ -0,0 +1,9 @@
1
+ ---
2
+ en:
3
+ page1:
4
+ button: Hello world
5
+ page2:
6
+ button: Goodbye
7
+ fr:
8
+ page1:
9
+ button: Bonjour
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yayaml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Uruski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: psych
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.5
27
+ description: A tool for searching the structure of YAML like i18n files
28
+ email:
29
+ - tim@uruski.xyz
30
+ executables:
31
+ - ya
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rspec"
36
+ - CHANGELOG.md
37
+ - CODE_OF_CONDUCT.md
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - args.rb
44
+ - discourse.yml
45
+ - en.yml
46
+ - example.rb
47
+ - example.yml
48
+ - exe/ya
49
+ - lib/yayaml.rb
50
+ - lib/yayaml/handler.rb
51
+ - lib/yayaml/matcher.rb
52
+ - lib/yayaml/version.rb
53
+ - sig/yay/rb.rbs
54
+ - test.yml
55
+ homepage: https://github.com/timuruski/yayaml
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ homepage_uri: https://github.com/timuruski/yayaml
60
+ source_code_uri: https://github.com/timuruski/yayaml
61
+ changelog_uri: https://github.com/timuruski/yayaml/tree/master/CHANGELOG.md
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 2.6.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.5.5
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: A tool for searching YAML files
81
+ test_files: []