yayaml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []