ruby_tree_sitter 1.10.0-x86_64-linux-musl → 1.11.1-x86_64-linux-musl

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: 83b4b512477acc8e23f7ea89074d3a8487559c8db88569506d987f26a0091069
4
- data.tar.gz: bff2ad0febb0e15e252cc62c46077e6f5d82be917250d6feb9ca9f207c336510
3
+ metadata.gz: df942821ba34095b2c4a9f77815b70df21961e9577dc79c5dff58b6bf674f50d
4
+ data.tar.gz: 82511c94e386184c135828ecc94f1221ee6f24845dcccc7300fbd9a17ca4bad5
5
5
  SHA512:
6
- metadata.gz: 781cc09c0d1df97c50932dbf3f9aa7d03f663eaaecc4ac9a36b0fa0f2daa5aa7a1305b3d15b1bdc21c990976165428d7b738767c2e078c414c95ed593d244882
7
- data.tar.gz: 429d5dc8ae5d850d74ec3cf65db2ae036a985af19c0adf10a2a5cbf430f88d576779997bab83853cf308c1d81f0e191cdf5fb6f5fee4eb4e55d0759cab3926e4
6
+ metadata.gz: 2da484e34553bdbaab6174326c726dc822af7a0946e63f197d6c7836d1e8774e4a1bf3349a6c36d88ec2bdb3a7943c9997baf8b5c156abb7ea46686b3b59b457
7
+ data.tar.gz: cb0e8dcad43101c08cde3070d90a7b7f9a425d553c6fadebc909f5a73f754c1b48dfbfbe40fab92387395b46fd26b74378fe82aed807b5f3e617c5075d411fba
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
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.6'
6
6
  # The current version of the gem.
7
- VERSION = '1.10.0'
7
+ VERSION = '1.11.1'
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.1
5
5
  platform: x86_64-linux-musl
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
@@ -78,6 +96,7 @@ files:
78
96
  - lib/tree_sitter/3.1/tree_sitter.so
79
97
  - lib/tree_sitter/3.2/tree_sitter.so
80
98
  - lib/tree_sitter/3.3/tree_sitter.so
99
+ - lib/tree_sitter/3.4/tree_sitter.so
81
100
  - lib/tree_sitter/error.rb
82
101
  - lib/tree_sitter/helpers.rb
83
102
  - lib/tree_sitter/mixins/language.rb
@@ -93,12 +112,13 @@ files:
93
112
  - lib/tree_stand.rb
94
113
  - lib/tree_stand/ast_modifier.rb
95
114
  - lib/tree_stand/breadth_first_visitor.rb
115
+ - lib/tree_stand/cli.rb
116
+ - lib/tree_stand/cli/options.rb
96
117
  - lib/tree_stand/config.rb
97
118
  - lib/tree_stand/node.rb
98
119
  - lib/tree_stand/parser.rb
99
120
  - lib/tree_stand/range.rb
100
121
  - lib/tree_stand/tree.rb
101
- - lib/tree_stand/utils/printer.rb
102
122
  - lib/tree_stand/version.rb
103
123
  - lib/tree_stand/visitor.rb
104
124
  - lib/tree_stand/visitors/tree_walker.rb
@@ -122,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
142
  version: '3.0'
123
143
  - - "<"
124
144
  - !ruby/object:Gem::Version
125
- version: 3.4.dev
145
+ version: 3.5.dev
126
146
  required_rubygems_version: !ruby/object:Gem::Requirement
127
147
  requirements:
128
148
  - - ">="
@@ -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