ffast 0.0.4 → 0.0.5
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 +4 -4
- data/README.md +14 -3
- data/bin/fast-experiment +1 -3
- data/docs/pry-integration.md +27 -0
- data/examples/build_stubbed_and_let_it_be_experiment.rb +51 -0
- data/lib/fast.rb +82 -32
- data/lib/fast/version.rb +1 -1
- data/mkdocs.yml +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8e1cdbb944bd8939745c07350aaa66e4ff3e9e3313802ca23aaa7fbd33b742d
|
4
|
+
data.tar.gz: fcd59e90fedc893492aa791e129d18a8b2e2e72e72b8ca61be6be8fd3e4ad6b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f33ebcf4e4275e783d75a761b78fc54a70f789f89ea735011a5a5628c7d2f8630271e29436aa1d3a15418f4186fc42764fad3ca5d72ef69bf2ccc860424299a3
|
7
|
+
data.tar.gz: ab1c3275a3415aba183ac6e558284d4f30bb5e462454758808df430059f077eb141f18a8ec725ad7738e1e9e3fd350688d674f6691a4d6165e17f039db34c653
|
data/README.md
CHANGED
@@ -319,18 +319,29 @@ You can use `search_file` and pass the path for search for expressions inside
|
|
319
319
|
files.
|
320
320
|
|
321
321
|
```ruby
|
322
|
-
Fast.search_file('file.rb'
|
322
|
+
Fast.search_file(expression, 'file.rb')
|
323
323
|
```
|
324
324
|
|
325
325
|
It's simple combination of `Fast.ast_from_file` with `Fast.search`.
|
326
326
|
|
327
|
+
|
328
|
+
## Fast.capture_file
|
329
|
+
|
330
|
+
You can also use `Fast.capture_file` that is similar but return only the
|
331
|
+
captures:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
Fast.capture_file('(class (const nil $_))', 'lib/fast.rb')
|
335
|
+
# => [:Rewriter, :ExpressionParser, :Find, :FindString, ...]
|
336
|
+
```
|
337
|
+
|
327
338
|
## Fast.ruby_files_from(arguments)
|
328
339
|
|
329
340
|
You'll be probably looking for multiple ruby files, then this method fetches
|
330
341
|
all internal `.rb` files
|
331
342
|
|
332
343
|
```ruby
|
333
|
-
Fast.ruby_files_from(
|
344
|
+
Fast.ruby_files_from('lib') # => ["lib/fast.rb"]
|
334
345
|
```
|
335
346
|
|
336
347
|
## `fast` in the command line
|
@@ -423,7 +434,7 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
423
434
|
|
424
435
|
On the console we have a few functions like `s` and `code` to make it easy ;)
|
425
436
|
|
426
|
-
$ bin/console
|
437
|
+
$ bin/console
|
427
438
|
|
428
439
|
```ruby
|
429
440
|
code("a = 1") # => s(:lvasgn, s(:int, 1))
|
data/bin/fast-experiment
CHANGED
@@ -13,9 +13,7 @@ experiment_files.each(&method(:require))
|
|
13
13
|
experiments = []
|
14
14
|
|
15
15
|
if arguments.any?
|
16
|
-
while arguments.any? && !(File.exist?(arguments.first) || Dir.exist?(arguments.first))
|
17
|
-
experiments << arguments.shift
|
18
|
-
end
|
16
|
+
experiments << arguments.shift while arguments.any? && !(File.exist?(arguments.first) || Dir.exist?(arguments.first))
|
19
17
|
experiments.map! { |name| Fast.experiments[name] }
|
20
18
|
else
|
21
19
|
experiments = Fast.experiments.values
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
You can create a custom command in pry to reuse fast in any session.
|
3
|
+
|
4
|
+
Start simply dropping it on your `.pryrc`:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
Pry::Commands.block_command "fast", "Fast search" do |expression, file|
|
8
|
+
require "fast"
|
9
|
+
files = Fast.ruby_files_from(file || '.')
|
10
|
+
files.each do |f|
|
11
|
+
results = Fast.search_file(expression, f)
|
12
|
+
next if results.nil? || results.empty?
|
13
|
+
output.puts Fast.highlight("# #{f}")
|
14
|
+
|
15
|
+
results.each do |result|
|
16
|
+
output.puts Fast.highlight(result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
And use it in the console:
|
23
|
+
|
24
|
+
`pry
|
25
|
+
fast '(def match?)' lib/fast.rb`
|
26
|
+
```
|
27
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fast'
|
4
|
+
require 'open3'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
# Usage instructions:
|
9
|
+
# 1. Add the following to your project's Gemfile: gem 'ffast' (yes, two "f"s)
|
10
|
+
# 2. Copy this file to your Rails project root directory
|
11
|
+
# 3. Run: bundle exec ruby build_stubbed_and_let_it_be_experiment.rb
|
12
|
+
|
13
|
+
# List of spec files you want to experiment with. One per line.
|
14
|
+
FILE_NAMES = %w[
|
15
|
+
# spec/model/foo_spec.rb
|
16
|
+
# spec/model/bar_spec.rb
|
17
|
+
]
|
18
|
+
|
19
|
+
def execute_rspec(file_name)
|
20
|
+
rspec_command = "bin/spring rspec --fail-fast --format progress #{file_name}"
|
21
|
+
stdout_str, stderr_str, status = Open3.capture3(rspec_command)
|
22
|
+
execution_time = /Finished in (.*?) seconds/.match(stdout_str)[1]
|
23
|
+
print stderr_str.gsub(/Running via Spring preloader.*?$/, '').chomp unless status.success?
|
24
|
+
OpenStruct.new(success: status.success?, execution_time: execution_time)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_temp_files(original_file_name)
|
28
|
+
file_path = File.dirname(original_file_name)
|
29
|
+
file_name = File.basename(original_file_name)
|
30
|
+
Dir.glob("#{file_path}/experiment*#{file_name}").each { |file| File.delete(file)}
|
31
|
+
end
|
32
|
+
|
33
|
+
FILE_NAMES.each do |original_file_name|
|
34
|
+
Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
|
35
|
+
lookup original_file_name
|
36
|
+
search '(block (send nil let (sym _)) (args) $(send nil create))'
|
37
|
+
edit { |_, (create)| replace(create.loc.selector, 'build_stubbed') }
|
38
|
+
policy { |experiment_file_name| execute_rspec(experiment_file_name) }
|
39
|
+
end.run
|
40
|
+
|
41
|
+
Fast.experiment('RSpec/LetItBe') do
|
42
|
+
lookup original_file_name
|
43
|
+
search '(block $(send nil let! (sym _)) (args) (send nil create))'
|
44
|
+
edit { |_, (let)| replace(let.loc.selector, 'let_it_be') }
|
45
|
+
policy { |experiment_file_name| execute_rspec(experiment_file_name) }
|
46
|
+
end.run
|
47
|
+
|
48
|
+
delete_temp_files(original_file_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
data/lib/fast.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
4
|
+
|
3
5
|
# suppress output to avoid parser gem warnings'
|
4
6
|
def suppress_output
|
5
7
|
original_stdout = $stdout.clone
|
@@ -21,7 +23,7 @@ end
|
|
21
23
|
module Fast
|
22
24
|
LITERAL = {
|
23
25
|
'...' => ->(node) { node&.children&.any? },
|
24
|
-
'_'
|
26
|
+
'_' => ->(node) { !node.nil? },
|
25
27
|
'nil' => nil
|
26
28
|
}.freeze
|
27
29
|
|
@@ -55,7 +57,7 @@ module Fast
|
|
55
57
|
\\\d # find using captured expression
|
56
58
|
|
|
57
59
|
%\d # find using binded argument
|
58
|
-
/x
|
60
|
+
/x.freeze
|
59
61
|
|
60
62
|
class << self
|
61
63
|
def match?(ast, search, *args)
|
@@ -85,6 +87,11 @@ module Fast
|
|
85
87
|
search node, pattern
|
86
88
|
end
|
87
89
|
|
90
|
+
def capture_file(pattern, file)
|
91
|
+
node = ast_from_file(file)
|
92
|
+
capture node, pattern
|
93
|
+
end
|
94
|
+
|
88
95
|
def search(node, pattern)
|
89
96
|
if (match = Fast.match?(node, pattern))
|
90
97
|
yield node, match if block_given?
|
@@ -93,7 +100,7 @@ module Fast
|
|
93
100
|
node.children
|
94
101
|
.grep(Parser::AST::Node)
|
95
102
|
.flat_map { |e| search(e, pattern) }
|
96
|
-
.compact.
|
103
|
+
.compact.flatten
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
@@ -577,6 +584,7 @@ module Fast
|
|
577
584
|
|
578
585
|
def initialize(name, &block)
|
579
586
|
@name = name
|
587
|
+
puts "\nStarting experiment: #{name}"
|
580
588
|
instance_exec(&block)
|
581
589
|
end
|
582
590
|
|
@@ -609,17 +617,63 @@ module Fast
|
|
609
617
|
end
|
610
618
|
end
|
611
619
|
|
620
|
+
# Suggest possible combinations of occurrences to replace.
|
621
|
+
class ExperimentCombinations
|
622
|
+
attr_reader :combinations
|
623
|
+
|
624
|
+
def initialize(round:, occurrences_count:, ok_experiments:, fail_experiments:)
|
625
|
+
@round = round
|
626
|
+
@ok_experiments = ok_experiments
|
627
|
+
@fail_experiments = fail_experiments
|
628
|
+
@occurrences_count = occurrences_count
|
629
|
+
end
|
630
|
+
|
631
|
+
def generate_combinations
|
632
|
+
case @round
|
633
|
+
when 1
|
634
|
+
individual_replacements
|
635
|
+
when 2
|
636
|
+
all_ok_replacements_combined
|
637
|
+
else
|
638
|
+
ok_replacements_pair_combinations
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
# Replace a single occurrence at each iteration and identify which
|
643
|
+
# individual replacements work.
|
644
|
+
def individual_replacements
|
645
|
+
(1..@occurrences_count).to_a
|
646
|
+
end
|
647
|
+
|
648
|
+
# After identifying all individual replacements that work, try combining all
|
649
|
+
# of them.
|
650
|
+
def all_ok_replacements_combined
|
651
|
+
[@ok_experiments.uniq.sort]
|
652
|
+
end
|
653
|
+
|
654
|
+
# Combining all successful individual replacements has failed. Lets divide
|
655
|
+
# and conquer.
|
656
|
+
def ok_replacements_pair_combinations
|
657
|
+
@ok_experiments
|
658
|
+
.combination(2)
|
659
|
+
.map { |e| e.flatten.uniq.sort }
|
660
|
+
.uniq - @fail_experiments - @ok_experiments
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
612
664
|
# Encapsulate the join of an Experiment with an specific file.
|
613
665
|
# This is important to coordinate and regulate multiple experiments in the same file.
|
614
666
|
# It can track successfull experiments and failures and suggest new combinations to keep replacing the file.
|
615
667
|
class ExperimentFile
|
616
668
|
attr_reader :ok_experiments, :fail_experiments, :experiment
|
669
|
+
|
617
670
|
def initialize(file, experiment)
|
618
671
|
@file = file
|
619
672
|
@ast = Fast.ast_from_file(file) if file
|
620
673
|
@experiment = experiment
|
621
674
|
@ok_experiments = []
|
622
675
|
@fail_experiments = []
|
676
|
+
@round = 0
|
623
677
|
end
|
624
678
|
|
625
679
|
def search
|
@@ -677,33 +731,34 @@ module Fast
|
|
677
731
|
filename
|
678
732
|
end
|
679
733
|
|
680
|
-
def suggest_combinations
|
681
|
-
if @ok_experiments.empty? && @fail_experiments.empty?
|
682
|
-
(1..search_cases.size).to_a
|
683
|
-
else
|
684
|
-
@ok_experiments
|
685
|
-
.combination(2)
|
686
|
-
.map { |e| e.flatten.uniq.sort }
|
687
|
-
.uniq - @fail_experiments - @ok_experiments
|
688
|
-
end
|
689
|
-
end
|
690
|
-
|
691
734
|
def done!
|
692
735
|
count_executed_combinations = @fail_experiments.size + @ok_experiments.size
|
693
|
-
puts "Done with #{@file} after #{count_executed_combinations}"
|
736
|
+
puts "Done with #{@file} after #{count_executed_combinations} combinations"
|
694
737
|
return unless perfect_combination = @ok_experiments.last # rubocop:disable Lint/AssignmentInCondition
|
695
738
|
|
739
|
+
puts 'The following changes were applied to the file:'
|
740
|
+
`diff #{experimental_filename(perfect_combination)} #{@file}`
|
696
741
|
puts "mv #{experimental_filename(perfect_combination)} #{@file}"
|
697
742
|
`mv #{experimental_filename(perfect_combination)} #{@file}`
|
698
743
|
end
|
699
744
|
|
745
|
+
def build_combinations
|
746
|
+
@round += 1
|
747
|
+
ExperimentCombinations.new(
|
748
|
+
round: @round,
|
749
|
+
occurrences_count: search_cases.size,
|
750
|
+
ok_experiments: @ok_experiments,
|
751
|
+
fail_experiments: @fail_experiments
|
752
|
+
).generate_combinations
|
753
|
+
end
|
754
|
+
|
700
755
|
def run
|
701
|
-
while (combinations =
|
702
|
-
if combinations.size >
|
703
|
-
puts "Ignoring #{@file} because it
|
756
|
+
while (combinations = build_combinations).any?
|
757
|
+
if combinations.size > 1000
|
758
|
+
puts "Ignoring #{@file} because it has #{combinations.size} possible combinations"
|
704
759
|
break
|
705
760
|
end
|
706
|
-
puts "#{@file} - Possible combinations: #{combinations.inspect}"
|
761
|
+
puts "#{@file} - Round #{@round} - Possible combinations: #{combinations.inspect}"
|
707
762
|
while combination = combinations.shift # rubocop:disable Lint/AssignmentInCondition
|
708
763
|
run_partial_replacement_with(combination)
|
709
764
|
end
|
@@ -711,28 +766,23 @@ module Fast
|
|
711
766
|
done!
|
712
767
|
end
|
713
768
|
|
714
|
-
# rubocop:disable Metrics/
|
715
|
-
# rubocop:disable Metrics/AbcSize
|
716
|
-
def run_partial_replacement_with(combination)
|
717
|
-
puts "#{@file} applying partial replacement with: #{combination}"
|
769
|
+
def run_partial_replacement_with(combination) # rubocop:disable Metrics/AbcSize
|
718
770
|
content = partial_replace(*combination)
|
719
771
|
experimental_file = experimental_filename(combination)
|
720
|
-
puts `diff #{experimental_file} #{@file}`
|
721
|
-
if experimental_file == IO.read(@file)
|
722
|
-
raise 'Returned the same file thinking:'
|
723
|
-
end
|
724
772
|
|
725
773
|
File.open(experimental_file, 'w+') { |f| f.puts content }
|
726
774
|
|
727
|
-
if
|
775
|
+
raise 'No changes were made to the file.' if FileUtils.compare_file(@file, experimental_file)
|
776
|
+
|
777
|
+
result = experiment.ok_if.call(experimental_file)
|
778
|
+
|
779
|
+
if result.success
|
728
780
|
ok_with(combination)
|
729
|
-
puts "✅ #{combination} #{
|
781
|
+
puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
|
730
782
|
else
|
731
783
|
failed_with(combination)
|
732
|
-
puts "🔴 #{
|
784
|
+
puts "🔴 #{experimental_file} - Combination: #{combination}"
|
733
785
|
end
|
734
786
|
end
|
735
|
-
# rubocop:enable Metrics/MethodLength
|
736
|
-
# rubocop:enable Metrics/AbcSize
|
737
787
|
end
|
738
788
|
end
|
data/lib/fast/version.rb
CHANGED
data/mkdocs.yml
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jônatas Davi Paganini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: coderay
|
@@ -205,8 +205,10 @@ files:
|
|
205
205
|
- docs/command_line.md
|
206
206
|
- docs/experiments.md
|
207
207
|
- docs/index.md
|
208
|
+
- docs/pry-integration.md
|
208
209
|
- docs/similarity_tutorial.md
|
209
210
|
- docs/syntax.md
|
211
|
+
- examples/build_stubbed_and_let_it_be_experiment.rb
|
210
212
|
- examples/experimental_replacement.rb
|
211
213
|
- examples/find_usage.rb
|
212
214
|
- examples/let_it_be_experiment.rb
|