ffast 0.2.0 → 0.2.3

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +27 -0
  3. data/.github/workflows/ruby.yml +34 -0
  4. data/.gitignore +2 -0
  5. data/Fastfile +146 -3
  6. data/README.md +244 -132
  7. data/bin/console +6 -1
  8. data/bin/fast-experiment +3 -0
  9. data/bin/fast-mcp +7 -0
  10. data/fast.gemspec +24 -7
  11. data/lib/fast/cli.rb +129 -38
  12. data/lib/fast/experiment.rb +19 -2
  13. data/lib/fast/git.rb +1 -1
  14. data/lib/fast/mcp_server.rb +317 -0
  15. data/lib/fast/node.rb +258 -0
  16. data/lib/fast/prism_adapter.rb +310 -0
  17. data/lib/fast/rewriter.rb +64 -10
  18. data/lib/fast/scan.rb +203 -0
  19. data/lib/fast/shortcut.rb +23 -6
  20. data/lib/fast/source.rb +116 -0
  21. data/lib/fast/source_rewriter.rb +153 -0
  22. data/lib/fast/sql/rewriter.rb +98 -0
  23. data/lib/fast/sql.rb +165 -0
  24. data/lib/fast/summary.rb +435 -0
  25. data/lib/fast/version.rb +1 -1
  26. data/lib/fast.rb +165 -79
  27. data/mkdocs.yml +27 -3
  28. data/requirements-docs.txt +3 -0
  29. metadata +48 -62
  30. data/docs/command_line.md +0 -238
  31. data/docs/editors-integration.md +0 -46
  32. data/docs/experiments.md +0 -153
  33. data/docs/ideas.md +0 -80
  34. data/docs/index.md +0 -402
  35. data/docs/pry-integration.md +0 -27
  36. data/docs/research.md +0 -93
  37. data/docs/shortcuts.md +0 -323
  38. data/docs/similarity_tutorial.md +0 -176
  39. data/docs/syntax.md +0 -395
  40. data/docs/videos.md +0 -16
  41. data/examples/build_stubbed_and_let_it_be_experiment.rb +0 -51
  42. data/examples/experimental_replacement.rb +0 -46
  43. data/examples/find_usage.rb +0 -26
  44. data/examples/let_it_be_experiment.rb +0 -11
  45. data/examples/method_complexity.rb +0 -37
  46. data/examples/search_duplicated.rb +0 -15
  47. data/examples/similarity_research.rb +0 -58
  48. data/examples/simple_rewriter.rb +0 -6
  49. data/experiments/let_it_be_experiment.rb +0 -9
  50. data/experiments/remove_useless_hook.rb +0 -9
  51. data/experiments/replace_create_with_build_stubbed.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3942d57d80b4c4a1cd1c8ff40f18cc56508a51f4b6cae53480536f7c36a60b05
4
- data.tar.gz: b60976ea570cf722ae6face8541881c6a1231b2d645727f25231a05c9f33f7ee
3
+ metadata.gz: da30bc7abc983e2ac54c1c4d1f454878d75dce0dd285a0e245d6ed54d73e4040
4
+ data.tar.gz: f286ce93d5ce2970d1c3a528d423bf8b8ed9239140f798f895a67e0aa1b8e2d2
5
5
  SHA512:
6
- metadata.gz: c4df11570f2b2cc164dbd43815a37443541c98c955d6c84ea0b24564fd03b8534cf73479844393ecd72c794231ad40baf678784f9863a8f90c157ac8477f3c9d
7
- data.tar.gz: d9481952776fc77d251a3c9f518f41e20d672298e9ff01b53b8673a0c0bc38f4919e4dd7234365e8044f5cef8e0738823d847900bea790f9796e6d8554342a06
6
+ metadata.gz: 83c218849cd4a9184b34f9d760d537cae650fe8a96428aa83fdeff12ed6afc57129fff3ce4a0c6d6924225e178f80c114afb021e19ca41bc2677c85b0668ee99
7
+ data.tar.gz: 6d4499e8a11398c3f9bc8a04adcd5efd65ef1b385c9bc37370edc31f9375b8d844e8ed61981e33831b2b83961edd4245059a38167c2f7a5ffbc52dd49acb9fff
@@ -0,0 +1,27 @@
1
+ name: Release Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*' # Triggers when a new tag like v0.2.0 is pushed
7
+
8
+ jobs:
9
+ release:
10
+ name: Build and Release
11
+ runs-on: ubuntu-22.04
12
+ permissions:
13
+ contents: read # Required to checkout the code
14
+ id-token: write # Required for RubyGems Trusted Publishing
15
+
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: '3.3'
24
+ bundler-cache: true
25
+
26
+ - name: Publish to RubyGems
27
+ uses: rubygems/release-gem@v1
@@ -0,0 +1,34 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "master" ]
13
+ pull_request:
14
+ branches: [ "master" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-22.04
22
+ strategy:
23
+ matrix:
24
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
25
+
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - name: Set up Ruby
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby-version }}
32
+ bundler-cache: true
33
+ - name: Run tests
34
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -5,8 +5,10 @@
5
5
  /coverage/
6
6
  /doc/
7
7
  /pkg/
8
+ /site/
8
9
  /spec/reports/
9
10
  /tmp/
11
+ .venv/
10
12
 
11
13
  # rspec failure tracking
12
14
  .rspec_status
data/Fastfile CHANGED
@@ -1,4 +1,9 @@
1
1
  # frozen_string_literal: true
2
+ begin
3
+ require 'fast/source'
4
+ rescue LoadError
5
+ nil
6
+ end
2
7
 
3
8
  # Fastfile is loaded when you start an expression with a dot.
4
9
  #
@@ -6,7 +11,8 @@
6
11
  # command line interactions with fast.
7
12
  #
8
13
  # Let's say you'd like to show the version that is over the version file
9
- Fast.shortcut(:version, '(casgn nil VERSION (str _))', 'lib/fast/version.rb')
14
+ version_file = Dir['lib/*/version.rb'].first
15
+ Fast.shortcut(:version, '(casgn nil VERSION (str _))', version_file)
10
16
 
11
17
  # Show all classes that inherits Fast::Find
12
18
  Fast.shortcut(:finders, '(class ... (const nil Find)', 'lib')
@@ -18,10 +24,12 @@ Fast.shortcut(:finders, '(class ... (const nil Find)', 'lib')
18
24
 
19
25
  # Simple shortcut that I used often to show how the expression parser works
20
26
  Fast.shortcut(:parser, '(class (const nil ExpressionParser)', 'lib/fast.rb')
27
+ Fast.shortcut(:sql_parser, '(def parse', 'lib/fast/sql.rb')
21
28
 
22
29
  # Use `fast .bump_version` to rewrite the version file
23
30
  Fast.shortcut :bump_version do
24
- rewrite_file('(casgn nil VERSION (str _)', 'lib/fast/version.rb') do |node|
31
+ new_version = nil
32
+ rewrite_file('(casgn nil VERSION (str _)', version_file) do |node|
25
33
  target = node.children.last.loc.expression
26
34
  pieces = target.source.split('.').map(&:to_i)
27
35
  pieces.reverse.each_with_index do |fragment, i|
@@ -32,6 +40,141 @@ Fast.shortcut :bump_version do
32
40
  pieces[-(i + 1)] = 0
33
41
  end
34
42
  end
35
- replace(target, "'#{pieces.join('.')}'")
43
+ new_version = pieces.join('.')
44
+ replace(target, "'#{new_version}'")
45
+ end
46
+
47
+ print "Commit bumped version v#{new_version}? (y/n) "
48
+ if $stdin.gets.chomp.downcase == 'y'
49
+ system("git add #{version_file} && git commit -m 'Bump version to v#{new_version}'")
50
+
51
+ print "Tag version v#{new_version}? (y/n) "
52
+ if $stdin.gets.chomp.downcase == 'y'
53
+ system("git tag v#{new_version}")
54
+
55
+ print "Push commit and tag to origin? (y/n) "
56
+ if $stdin.gets.chomp.downcase == 'y'
57
+ system("git push origin HEAD")
58
+ system("git push origin v#{new_version}")
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # List all shortcut with comments
65
+ Fast.shortcut :shortcuts do
66
+ fast_files.each do |file|
67
+ lines = File.readlines(file).map { |line| line.chomp.gsub(/\s*#/, '').strip }
68
+ result = capture_file('(send _ :shortcut $(sym _) ...)', file)
69
+ result = [result] unless result.is_a? Array
70
+ result.each do |capture|
71
+ target = capture.loc.expression
72
+ puts "fast .#{target.source[1..].ljust(30)} # #{lines[target.line - 2]}"
73
+ end
74
+ end
75
+ end
76
+
77
+ # Use to walkthrough the docs files with fast examples
78
+ # fast .intro
79
+ Fast.shortcut :intro do
80
+ ARGV << File.join(File.dirname(__FILE__), 'docs', 'walkthrough.md')
81
+
82
+ Fast.shortcuts[:walk].run
83
+ end
84
+
85
+ # Interactive command line walkthrough
86
+ # fast .walk docs/walkthrough.md
87
+ Fast.shortcut :walk do
88
+ file = ARGV.last
89
+ execute = ->(line) { system(line) }
90
+ walk = ->(line) { line.each_char { |c| sleep(0.02) and print(c) } }
91
+ File.readlines(file).each do |line|
92
+ case line
93
+ when /^fast /
94
+ walk[line]
95
+ execute[line]
96
+ when /^\$ /
97
+ walk[line]
98
+ execute[line[2..]]
99
+ when /^!{3}\s/
100
+ # Skip warnings that are only for web tutorials
101
+ else
102
+ walk[Fast.render_markdown_for_terminal(line)]
103
+ end
36
104
  end
37
105
  end
106
+
107
+ # Format SQL
108
+ Fast.shortcut :format_sql do
109
+ require 'fast/sql'
110
+ file = ARGV.last
111
+ method = File.exist?(file) ? :parse_sql_file : :parse_sql
112
+ ast = Fast.public_send(method, file)
113
+ ast = ast.first if ast.is_a? Array
114
+ eligible_kw = [:RESERVED_KEYWORD]
115
+ eligible_tokens = [:BY]
116
+
117
+ output = Fast::SQL.replace('_', ast) do |root|
118
+ sb = root.loc.expression.source_buffer
119
+ sb.tokens.each do |token|
120
+ if eligible_kw.include?(token.keyword_kind) || eligible_tokens.include?(token.token)
121
+ range = Fast::Source.range(sb, token.start, token.end)
122
+ replace(range, range.source.upcase)
123
+ end
124
+ end
125
+ end
126
+ require 'fast/cli'
127
+ puts Fast.highlight(output, sql: true)
128
+ end
129
+
130
+ # Anonymize SQL
131
+ # fast .anonymize_sql file.sql
132
+ Fast.shortcut :anonymize_sql do
133
+ require 'fast/sql'
134
+ file = ARGV.last
135
+ method = File.exist?(file) ? :parse_sql_file : :parse_sql
136
+ ast = Fast.public_send(method, file)
137
+ memo = {}
138
+
139
+ relnames = search("(relname $_)", ast).grep(String).uniq
140
+ pattern = "{relname (sval {#{relnames.map(&:inspect).join(' ')}})}"
141
+ puts "searching with #{pattern}"
142
+
143
+ content = Fast::SQL.replace(pattern, ast) do |node|
144
+ new_name = memo[node.source.tr(%|"'|, '')] ||= "x#{memo.size}"
145
+ new_name = "'#{new_name}'" if node.type == :sval
146
+ replace(node.loc.expression, new_name)
147
+ end
148
+ puts Fast.highlight(content, sql: true)
149
+ end
150
+
151
+ # Give all details in a shorter format
152
+ # fast .summary file.rb
153
+ Fast.shortcut :summary do
154
+ file = ARGV.reverse.find { |arg| !arg.start_with?('-') && File.exist?(arg) }
155
+ if file && File.exist?(file)
156
+ Fast.summary(IO.read(file), file: file, level: fast_option_value(ARGV, '-l', '--level')).summarize
157
+ else
158
+ puts "Please provide a valid file to summarize."
159
+ end
160
+ end
161
+
162
+ # Group and classify multiple Ruby files without printing full bodies
163
+ # fast .scan lib app/models --no-color
164
+ Fast.shortcut :scan do
165
+ locations = ARGV.select { |arg| !arg.start_with?('-') && File.exist?(arg) }
166
+ if locations.any?
167
+ Fast.scan(locations, level: fast_option_value(ARGV, '-l', '--level')).scan
168
+ else
169
+ puts "Please provide at least one valid file or directory to scan."
170
+ end
171
+ end
172
+
173
+ def fast_option_value(args, short_name, long_name)
174
+ args.each_with_index do |arg, index|
175
+ return args[index + 1] if arg == short_name || arg == long_name
176
+ return arg.split('=', 2).last if arg.start_with?("#{long_name}=")
177
+ end
178
+
179
+ nil
180
+ end