ruby_tree_sitter 1.10.0-x86_64-darwin → 1.11.0-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8c5144410762c39463ba56e6524acea04f11ae0eb634ed392d59523bcffdec7
4
- data.tar.gz: c1b5ed8af53aa9e6d5869a13cbe85ff110f796157414976c0147b8a4811363b0
3
+ metadata.gz: bdc265f5dda9c4123b013602244d37e9c00da87535cee01297cb0c9c0e543107
4
+ data.tar.gz: 0a0dfc57b524ea7d9fef98982f48ea64fa5d3ceac325cfdff82eda8fcde06447
5
5
  SHA512:
6
- metadata.gz: 804c1ac45131bc4c27feccd9394f393d918c185115dddce4a3d0e539154bbbbe8aeb158ff82afb2e90bd8e24f548041b10937bb7cd5f4862020dd188215ec133
7
- data.tar.gz: b5eb50f2d06cc45c97a64ad4f258133232dcf52668dfa0be94b688f4511d1a3ffe0d54cab06256851ff86d6b3a7b56284e74d960fec6efd8852f69b35d86043c
6
+ metadata.gz: 68f29d8ae25d85c620e86400340361e4f53a56b8dcb4b60ef74baecbd6ec9165de1b9aec762a821eadc3b15460df5932fa416af1264bac0e96a8adff76fde78e
7
+ data.tar.gz: f35912cc08fa32058500b7cf935e22a2da112977ecdce12403887a97aa3ca1b89dbcb058af2f6cf5dd7842902581a5b709bfa88c84e5867e40eed7c37571c32c
data/README.md CHANGED
@@ -175,6 +175,20 @@ You will have to install parsers yourself, either by:
175
175
  [Faveod/tree-sitter-parsers](https://github.com/Faveod/tree-sitter-parsers)
176
176
  which supports numerous architectures.
177
177
 
178
+ ### Utilities
179
+
180
+ `ruby_tree_sitter` ships with some useful utility programs to help work with parsers & queries.
181
+
182
+ #### `rbts`
183
+
184
+ ```sh
185
+ $ rbts --source SOURCE --query QUERY --parser PARSER
186
+ ```
187
+
188
+ Watches a source and a query file and prints the matches when one of the files are updated. Uses [entr](https://github.com/eradman/entr), if available, otherwise [watch(1)](https://man7.org/linux/man-pages/man1/watch.1.html) is used by default or if --watch is specified.
189
+
190
+ See `rbts --help` for more information.
191
+
178
192
  ## Examples
179
193
 
180
194
  See `examples` directory.
data/exe/print_matches ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'tree_stand/cli'
6
+
7
+ program = TreeStand::Cli::Options.new
8
+ OptionParser.new do |parser|
9
+ program.define_options(parser)
10
+ parser.parse!(ARGV)
11
+ end
12
+ program.check!
13
+
14
+ def main(program)
15
+ program.tree.query(program.query).each do |match|
16
+ pp match.transform_values(&:text)
17
+ end
18
+ end
19
+
20
+ main(program)
data/exe/rbts ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'tree_stand/cli'
6
+
7
+ BANNER = <<~BANNER
8
+ Usage: rbts --source SOURCE --query QUERY --parser PARSER
9
+
10
+ Watches a source and a query file and prints the matches when one of the
11
+ files are updated. Uses entr, if available, otherwise watch(1) is used by
12
+ default or if --watch is specified.
13
+
14
+ Check out the tree-sitter repository & documentation for information on how
15
+ to write S-expression queries.
16
+
17
+ Repo: https://github.com/tree-sitter/tree-sitter
18
+ Docs: https://tree-sitter.github.io/tree-sitter/using-parsers/queries
19
+
20
+ Example:
21
+
22
+ rbts --source tmp/rbts/code.rb \\
23
+ --query tmp/rbts/query.scm \\
24
+ --parser tmp/rbts/ruby.so
25
+ BANNER
26
+
27
+ program = TreeStand::Cli::Options.new
28
+ OptionParser.new do |parser|
29
+ parser.banner = BANNER
30
+ parser.separator('')
31
+ program.define_options(parser)
32
+ parser.separator('')
33
+ parser.on_tail(
34
+ '-w',
35
+ '--watch',
36
+ "use watch(1) to continuously print matches, by default entr is used if it's available",
37
+ TrueClass,
38
+ ) do |watch|
39
+ program.watch = watch
40
+ end
41
+
42
+ parser.parse!(ARGV)
43
+ end
44
+ program.check!
45
+
46
+ def print_matches = File.join(__dir__, 'print_matches')
47
+
48
+ # ls -1 SOURCE QUERY |
49
+ # entr -c exe/print_matches
50
+ # --source SOURCE
51
+ # --query QUERY
52
+ # --parser PARSER
53
+ def entr_cmd(source, query, parser)
54
+ [
55
+ 'ls', '-1', source, query, '|',
56
+ 'entr', '-c',
57
+ print_matches,
58
+ '--source', source,
59
+ '--query', query,
60
+ '--parser', parser
61
+ ].join(' ')
62
+ end
63
+
64
+ # watch 'exe/print_matches --source SOURCE --query QUERY --parser PARSER'
65
+ def watch_cmd(source, query, parser)
66
+ [
67
+ 'watch',
68
+ "'#{print_matches} --source #{source} --query #{query} --parser #{parser}'",
69
+ ].join(' ')
70
+ end
71
+
72
+ def main(program)
73
+ cmd = if !program.watch && system('which', 'entr')
74
+ entr_cmd(program.source_file, program.query_file, program.parser_file)
75
+ else
76
+ watch_cmd(program.source_file, program.query_file, program.parser_file)
77
+ end
78
+
79
+ program.logger.debug("Running `#{cmd}`")
80
+ exec(cmd)
81
+ end
82
+
83
+ main(program)
Binary file
Binary file
Binary file
@@ -56,7 +56,7 @@ module TreeSitter
56
56
  case k = keys.first
57
57
  when Integer then named_child(k)
58
58
  when String, Symbol
59
- raise IndexError, "Cannot find field #{k}. Available: #{fields}" unless fields.include?(k.to_sym)
59
+ raise IndexError, "Cannot find field #{k.to_sym}. Available: #{fields.to_a}" unless fields.include?(k.to_sym)
60
60
 
61
61
  child_by_field_name(k.to_s)
62
62
  else raise ArgumentError, <<~ERR
@@ -163,5 +163,94 @@ module TreeSitter
163
163
  end
164
164
  fields.values_at(*keys)
165
165
  end
166
+
167
+ # Regex for line annotation extraction from sexpr with source.
168
+ #
169
+ # @!visibility private
170
+ LINE_ANNOTATION = /\0\{(.*?)\0\}/
171
+
172
+ # Pretty-prints the node's sexp.
173
+ #
174
+ # The default call to {to_s} or {to_string} calls tree-sitter's
175
+ # `ts_node_string`. It's displayed on a single line, so reading a rich node
176
+ # becomes tiresome.
177
+ #
178
+ # This provides a better sexpr where you can control the "screen" width to
179
+ # decide when to break.
180
+ #
181
+ # @param indent [Integer]
182
+ # indentation for nested nodes.
183
+ # @param width [Integer]
184
+ # the screen's width.
185
+ # @param source [Nil|String]
186
+ # display source on the margin if not `nil`.
187
+ # @param vertical [Nil|Boolean]
188
+ # fit as much sexpr on a single line if `false`, else, go vertical.
189
+ # This is always `true` if `source` is not `nil`.
190
+ #
191
+ # @return [String] the pretty-printed sexpr.
192
+ def sexpr(indent: 2, width: 120, source: nil, vertical: nil)
193
+ res =
194
+ sexpr_recur(
195
+ indent: indent,
196
+ width: width,
197
+ source: source,
198
+ vertical: !source.nil? || !!vertical,
199
+ ).output
200
+ return res if source.nil?
201
+
202
+ max_width = 0
203
+ res
204
+ .lines
205
+ .map { |line|
206
+ extracted = line.scan(LINE_ANNOTATION).flatten.first || ''
207
+ base = line.gsub(LINE_ANNOTATION, '').rstrip
208
+ max_width = [max_width, base.length].max
209
+ [base, extracted]
210
+ }
211
+ .map { |base, extracted|
212
+ ("%-#{max_width}s | %s" % [base, extracted]).rstrip
213
+ }
214
+ .join("\n")
215
+ end
216
+
217
+ # Helper function for {sexpr}.
218
+ #
219
+ # @!visibility private
220
+ def sexpr_recur(indent: 2, width: 120, out: nil, source: nil, vertical: false)
221
+ out ||= Oppen::Wadler.new(width: width)
222
+ out.group(indent) {
223
+ out.text "(#{type}"
224
+ if source.is_a?(String) && child_count.zero?
225
+ out.text "\0{#{source.byteslice(start_byte...end_byte)}\0}", width: 0
226
+ end
227
+ brk(out, vertical) if child_count.positive?
228
+ each.with_index do |child, index|
229
+ if field_name = field_name_for_child(index)
230
+ out.text "#{field_name}:"
231
+ out.group(indent) {
232
+ brk(out, vertical)
233
+ child.sexpr_recur(indent: indent, width: width, out: out, vertical: vertical, source: source)
234
+ }
235
+ else
236
+ child.sexpr_recur(indent: indent, width: width, out: out, vertical: vertical, source: source)
237
+ end
238
+ brk(out, vertical) if index < child_count - 1
239
+ end
240
+ out.text ')'
241
+ }
242
+ out
243
+ end
244
+
245
+ # Break helper
246
+ #
247
+ # !@visibility private
248
+ def brk(out, vertical)
249
+ if vertical
250
+ out.break
251
+ else
252
+ out.breakable
253
+ end
254
+ end
166
255
  end
167
256
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module TreeSitter
4
4
  # The version of the tree-sitter library.
5
- TREESITTER_VERSION = '0.24.4'
5
+ TREESITTER_VERSION = '0.24.5'
6
6
  # The current version of the gem.
7
- VERSION = '1.10.0'
7
+ VERSION = '1.11.0'
8
8
  end
data/lib/tree_sitter.rb CHANGED
@@ -23,6 +23,8 @@ require 'tree_sitter/query_matches'
23
23
  require 'tree_sitter/query_predicate'
24
24
  require 'tree_sitter/text_predicate_capture'
25
25
 
26
+ require 'oppen'
27
+
26
28
  # TreeSitter is a Ruby interface to the tree-sitter parsing library.
27
29
  module TreeSitter
28
30
  extend Mixins::Language
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module TreeStand
5
+ module Cli
6
+ # @!visibility private
7
+ class Options
8
+ attr_accessor :source_file, :query_file, :parser_file, :watch
9
+
10
+ attr_reader :logger, :progname
11
+
12
+ def initialize(progname = File.basename($PROGRAM_NAME))
13
+ @logger = Logger.new($stderr, level: T.unsafe(ENV.fetch('LOG_LEVEL', Logger::INFO)), progname: progname)
14
+ @progname = progname
15
+ end
16
+
17
+ # @!visibility private
18
+ def define_options(parser)
19
+ parser.on_tail('-v', '--verbose', 'Enable verbose logging') { logger.level -= 1 }
20
+
21
+ parser.separator('Required options:')
22
+ parser.on('-s', '--source SOURCE', 'The filepath to the source code to be parsed') do |filepath|
23
+ self.source_file = error_no_file(File.expand_path(filepath), "Source file not found: #{filepath}", 1)
24
+ end
25
+ parser.on('-q', '--query QUERY', 'The filepath to the query to be run against the source code') do |filepath|
26
+ self.query_file = error_no_file(File.expand_path(filepath), "Query file not found: #{filepath}", 2)
27
+ end
28
+ parser.on('-p', '--parser PARSER', 'The parser to use to parse the source code') do |filepath|
29
+ self.parser_file = error_no_file(File.expand_path(filepath), "Parser file not found: #{filepath}", 3)
30
+ end
31
+ end
32
+
33
+ # @!visibility private
34
+ def check!
35
+ error!('No source file provided, specify with --source', 4) unless source_file
36
+ error!('No query file provided, specify with --query', 5) unless query_file
37
+ error!('No parser file provided, specify with --parser', 6) unless parser_file
38
+
39
+ parser_path = File.dirname(parser_file)
40
+ config = TreeStand.config
41
+
42
+ TreeStand.configure do
43
+ config.parser_path = parser_path
44
+ end
45
+ end
46
+
47
+ # @!visibility private
48
+ def source = @source ||= File.read(source_file)
49
+ # @!visibility private
50
+ def query = @query ||= File.read(query_file)
51
+ # @!visibility private
52
+ def parser = @parser ||= TreeStand::Parser.new(File.extname(source_file).delete_prefix('.'))
53
+ # @!visibility private
54
+ def tree = @tree ||= parser.parse_string(source)
55
+
56
+ private
57
+
58
+ def error_no_file(filepath, message, code)
59
+ return filepath if File.exist?(filepath)
60
+
61
+ error!(message, code)
62
+ end
63
+
64
+ def error!(message, code = 1)
65
+ logger.error(message)
66
+ exit(code)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'logger'
5
+ require 'optparse'
6
+ require 'tree_stand'
7
+
8
+ require 'tree_stand/cli/options'
9
+
10
+ $stdout.sync = $stderr.sync = true
11
+
12
+ module TreeStand
13
+ # @!visibility private
14
+ module Cli
15
+ end
16
+ end
@@ -15,6 +15,8 @@ module TreeStand
15
15
  # @return [Boolean] true if a syntax node has been edited.
16
16
  # @!method child_count
17
17
  # @return [Integer] the number of child nodes.
18
+ # @!method error?
19
+ # @return [bool] true if the node is an error node.
18
20
  # @!method extra?
19
21
  # @return [Boolean] true if the node is *extra* (e.g. comments).
20
22
  # @!method has_error?
@@ -25,10 +27,10 @@ module TreeStand
25
27
  # @return [Boolean] true if the node is not a literal in the grammar.
26
28
  # @!method named_child_count
27
29
  # @return [Integer] the number of *named* children.
30
+ # @!method sexpr
31
+ # @return [String] a pretty-printed sexpr.
28
32
  # @!method type
29
33
  # @return [Symbol] the type of the node in the tree-sitter grammar.
30
- # @!method error?
31
- # @return [bool] true if the node is an error node.
32
34
  def_delegators(
33
35
  :@ts_node,
34
36
  :changed?,
@@ -39,6 +41,7 @@ module TreeStand
39
41
  :missing?,
40
42
  :named?,
41
43
  :named_child_count,
44
+ :sexpr,
42
45
  :type,
43
46
  )
44
47
 
@@ -310,13 +313,10 @@ module TreeStand
310
313
  T.must(range == other.range && type == other.type && text == other.text)
311
314
  end
312
315
 
313
- # (see TreeStand::Utils::Printer)
314
- # Backed by {TreeStand::Utils::Printer}.
315
- #
316
- # @see TreeStand::Utils::Printer
316
+ # @see TreeSitter:Node::sexpr
317
317
  sig { params(pp: PP).void }
318
318
  def pretty_print(pp)
319
- Utils::Printer.new(ralign: 80).print(self, io: pp.output)
319
+ pp.output << sexpr(source: text)
320
320
  end
321
321
 
322
322
  private
data/lib/tree_stand.rb CHANGED
@@ -10,6 +10,8 @@ require 'zeitwerk'
10
10
  loader = Zeitwerk::Loader.for_gem
11
11
  loader.ignore("#{__dir__}/tree_sitter")
12
12
  loader.ignore("#{__dir__}/tree_sitter.rb")
13
+ loader.ignore("#{__dir__}/tree_stand/cli")
14
+ loader.ignore("#{__dir__}/tree_stand/cli.rb")
13
15
  loader.setup
14
16
 
15
17
  # TreeStand is a high-level Ruby wrapper for {https://tree-sitter.github.io/tree-sitter tree-sitter} bindings. It makes
data/tree_sitter.gemspec CHANGED
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)
6
6
  require 'tree_sitter/version'
7
7
 
8
8
  Gem::Specification.new do |spec|
9
- spec.required_ruby_version = '>= 3.0'
9
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
10
10
 
11
11
  spec.authors = ['Firas al-Khalil', 'Derek Stride']
12
12
  spec.email = ['firasalkhalil@gmail.com', 'derek@stride.host']
@@ -27,8 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.files = %w[LICENSE README.md tree_sitter.gemspec]
28
28
  spec.files += Dir.glob('ext/**/*.{c,h,rb}')
29
29
  spec.files += Dir.glob('lib/**/*.rb')
30
+ spec.bindir = 'exe'
31
+ spec.executables << 'rbts' << 'print_matches'
30
32
  spec.require_paths = ['lib']
31
33
 
34
+ spec.add_dependency 'oppen', '0.9.8'
32
35
  spec.add_dependency 'sorbet-runtime'
33
36
  spec.add_dependency 'zeitwerk'
34
37
  end
metadata CHANGED
@@ -1,16 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_tree_sitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.11.0
5
5
  platform: x86_64-darwin
6
6
  authors:
7
7
  - Firas al-Khalil
8
8
  - Derek Stride
9
9
  autorequire:
10
- bindir: bin
10
+ bindir: exe
11
11
  cert_chain: []
12
- date: 2024-12-10 00:00:00.000000000 Z
12
+ date: 2025-01-02 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oppen
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '='
19
+ - !ruby/object:Gem::Version
20
+ version: 0.9.8
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '='
26
+ - !ruby/object:Gem::Version
27
+ version: 0.9.8
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: sorbet-runtime
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -43,12 +57,16 @@ description:
43
57
  email:
44
58
  - firasalkhalil@gmail.com
45
59
  - derek@stride.host
46
- executables: []
60
+ executables:
61
+ - rbts
62
+ - print_matches
47
63
  extensions: []
48
64
  extra_rdoc_files: []
49
65
  files:
50
66
  - LICENSE
51
67
  - README.md
68
+ - exe/print_matches
69
+ - exe/rbts
52
70
  - ext/tree_sitter/encoding.c
53
71
  - ext/tree_sitter/extconf.rb
54
72
  - ext/tree_sitter/input.c
@@ -77,7 +95,7 @@ files:
77
95
  - lib/tree_sitter/3.0/tree_sitter.bundle
78
96
  - lib/tree_sitter/3.1/tree_sitter.bundle
79
97
  - lib/tree_sitter/3.2/tree_sitter.bundle
80
- - lib/tree_sitter/3.3/tree_sitter.bundle
98
+ - lib/tree_sitter/3.4/tree_sitter.bundle
81
99
  - lib/tree_sitter/error.rb
82
100
  - lib/tree_sitter/helpers.rb
83
101
  - lib/tree_sitter/mixins/language.rb
@@ -93,12 +111,13 @@ files:
93
111
  - lib/tree_stand.rb
94
112
  - lib/tree_stand/ast_modifier.rb
95
113
  - lib/tree_stand/breadth_first_visitor.rb
114
+ - lib/tree_stand/cli.rb
115
+ - lib/tree_stand/cli/options.rb
96
116
  - lib/tree_stand/config.rb
97
117
  - lib/tree_stand/node.rb
98
118
  - lib/tree_stand/parser.rb
99
119
  - lib/tree_stand/range.rb
100
120
  - lib/tree_stand/tree.rb
101
- - lib/tree_stand/utils/printer.rb
102
121
  - lib/tree_stand/version.rb
103
122
  - lib/tree_stand/visitor.rb
104
123
  - lib/tree_stand/visitors/tree_walker.rb
@@ -122,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
141
  version: '3.0'
123
142
  - - "<"
124
143
  - !ruby/object:Gem::Version
125
- version: 3.4.dev
144
+ version: 3.5.dev
126
145
  required_rubygems_version: !ruby/object:Gem::Requirement
127
146
  requirements:
128
147
  - - ">="
Binary file
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
- # typed: true
3
-
4
- module TreeStand
5
- # A collection of useful methods for working with syntax trees.
6
- module Utils
7
- # Used to {TreeStand::Node#pretty_print pretty-print} the node.
8
- #
9
- # @example
10
- # pp node
11
- # # (expression
12
- # # (sum
13
- # # left: (number) | 1
14
- # # ("+") | +
15
- # # right: (variable))) | x
16
- class Printer
17
- extend T::Sig
18
-
19
- # @param ralign the right alignment for the text column.
20
- sig { params(ralign: Integer).void }
21
- def initialize(ralign:)
22
- @ralign = ralign
23
- end
24
-
25
- # (see TreeStand::Utils::Printer)
26
- sig { params(node: TreeStand::Node, io: T.any(IO, StringIO, String)).returns(T.any(IO, StringIO, String)) }
27
- def print(node, io: StringIO.new)
28
- lines = pretty_output_lines(node)
29
-
30
- lines.each do |line|
31
- if line.text.empty?
32
- io << line.sexpr << "\n"
33
- next
34
- end
35
-
36
- io << "#{line.sexpr}#{' ' * [(@ralign - line.sexpr.size), 0].max}| #{line.text}\n"
37
- end
38
-
39
- io
40
- end
41
-
42
- private
43
-
44
- Line = Struct.new(:sexpr, :text)
45
- private_constant :Line
46
-
47
- def pretty_output_lines(node, prefix: '', depth: 0)
48
- indent = ' ' * depth
49
- ts_node = node.ts_node
50
- if indent.size + prefix.size + ts_node.to_s.size < @ralign || ts_node.child_count.zero?
51
- return [Line.new("#{indent}#{prefix}#{ts_node}", node.text)]
52
- end
53
-
54
- lines = T.let([Line.new("#{indent}#{prefix}(#{ts_node.type}", '')], T::Array[Line])
55
-
56
- node.each.with_index do |child, index|
57
- lines += if field_name = ts_node.field_name_for_child(index)
58
- pretty_output_lines(
59
- child,
60
- prefix: "#{field_name}: ",
61
- depth: depth + 1,
62
- )
63
- else
64
- pretty_output_lines(child, depth: depth + 1)
65
- end
66
- end
67
-
68
- T.must(lines.last).sexpr << ')'
69
- lines
70
- end
71
- end
72
- end
73
- end