ffast 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|