ffast 0.0.7 → 0.0.8

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: 36aaca199777feb62d91a6e052c038f4a971ab20fd37f1adcb7b0c8d3a90553b
4
- data.tar.gz: 0ab405982443936ecaad957b43100496dce63d213e2a61c840b3002028c245e8
3
+ metadata.gz: 5e62b5be6bf84eabaa2ed601696164c248a6969cc504343f636b1d487bb4fbf2
4
+ data.tar.gz: a86d3f27396de8cb9aa097c02d1baf19c39ad7090f03b5894e1eddb087150f6b
5
5
  SHA512:
6
- metadata.gz: 32a5615982972c2e13582a8279cf94043a3a571044a6b1a04fee253f04c7ff93733eab4fca22f2878dc3e16d519cb3d78f06adafbe4b6bb998a72bea97929bc6
7
- data.tar.gz: 277f3082ab12c310be88c1bda1d8fa57be86bae02ddc39c8c5adb6e49704aec036c801abe6d6f18527f23996d560bd95d199cb6c0209c72474acc1c031600bc0
6
+ metadata.gz: 4a2a7db73282e36fd8ece6d9d78b55eb52e98227e5be77d77058a82ae97af634e94de7dd5f98640a844739956f5e8c5bf9f2fd91e88971d21da6a762cf69fdbb
7
+ data.tar.gz: 635bd2bb2a9e26975731fa4096f7980281ef929d2ae13d54f8f4897e8695d6e824deb15ed4cea6e6df9114191e99e210510a5878dc703910b711e1a17c446a68
@@ -4,6 +4,7 @@
4
4
  $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
5
 
6
6
  require 'fast'
7
+ require 'fast/experiment'
7
8
  require 'coderay'
8
9
 
9
10
  arguments = ARGV
@@ -69,7 +69,7 @@ module Fast
69
69
  Matcher.new(ast, search, *args).match?
70
70
  end
71
71
 
72
- def replace(ast, search, replacement)
72
+ def replace(ast, search, &replacement)
73
73
  buffer = Parser::Source::Buffer.new('replacement')
74
74
  buffer.source = ast.loc.expression.source
75
75
  to_replace = search(ast, search)
@@ -82,9 +82,9 @@ module Fast
82
82
  rewriter.rewrite(buffer, ast)
83
83
  end
84
84
 
85
- def replace_file(file, search, replacement)
85
+ def replace_file(file, search, &replacement)
86
86
  ast = ast_from_file(file)
87
- replace(ast, search, replacement)
87
+ replace(ast, search, &replacement)
88
88
  end
89
89
 
90
90
  def search_file(pattern, file)
@@ -152,13 +152,6 @@ module Fast
152
152
  def expression(string)
153
153
  ExpressionParser.new(string).parse
154
154
  end
155
-
156
- def experiment(name, &block)
157
- @experiments ||= {}
158
- @experiments[name] = Experiment.new(name, &block)
159
- end
160
-
161
- attr_reader :experiments
162
155
  attr_accessor :debugging
163
156
 
164
157
  def debug
@@ -540,7 +533,6 @@ module Fast
540
533
  end
541
534
  end
542
535
 
543
- # rubocop:disable Metrics/AbcSize
544
536
  def match?(ast = @ast, fast = @fast)
545
537
  head, *tail = fast
546
538
  return false unless head.match?(ast)
@@ -555,8 +547,6 @@ module Fast
555
547
  end && find_captures
556
548
  end
557
549
 
558
- # rubocop:enable Metrics/AbcSize
559
-
560
550
  def prepare_token(token)
561
551
  case token
562
552
  when Fast::FindWithCapture
@@ -582,224 +572,4 @@ module Fast
582
572
  end
583
573
  end
584
574
  end
585
-
586
- # You can define experiments and build experimental files to improve some code in
587
- # an automated way. Let's create a hook to check if a `before` or `after` block
588
- # is useless in a specific spec:
589
- #
590
- # ```ruby
591
- # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
592
- # lookup 'some_spec.rb'
593
- # search "(block (send nil {before after}))"
594
- # edit {|node| remove(node.loc.expression) }
595
- # policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
596
- # end
597
- # ```
598
- class Experiment
599
- attr_writer :files
600
- attr_reader :name, :replacement, :expression, :files_or_folders, :ok_if
601
-
602
- def initialize(name, &block)
603
- @name = name
604
- puts "\nStarting experiment: #{name}"
605
- instance_exec(&block)
606
- end
607
-
608
- def run_with(file)
609
- ExperimentFile.new(file, self).run
610
- end
611
-
612
- def search(expression)
613
- @expression = expression
614
- end
615
-
616
- def edit(&block)
617
- @replacement = block
618
- end
619
-
620
- def lookup(files_or_folders)
621
- @files_or_folders = files_or_folders
622
- end
623
-
624
- def policy(&block)
625
- @ok_if = block
626
- end
627
-
628
- def files
629
- @files ||= Fast.ruby_files_from(@files_or_folders)
630
- end
631
-
632
- def run
633
- files.map(&method(:run_with))
634
- end
635
- end
636
-
637
- # Suggest possible combinations of occurrences to replace.
638
- class ExperimentCombinations
639
- attr_reader :combinations
640
-
641
- def initialize(round:, occurrences_count:, ok_experiments:, fail_experiments:)
642
- @round = round
643
- @ok_experiments = ok_experiments
644
- @fail_experiments = fail_experiments
645
- @occurrences_count = occurrences_count
646
- end
647
-
648
- def generate_combinations
649
- case @round
650
- when 1
651
- individual_replacements
652
- when 2
653
- all_ok_replacements_combined
654
- else
655
- ok_replacements_pair_combinations
656
- end
657
- end
658
-
659
- # Replace a single occurrence at each iteration and identify which
660
- # individual replacements work.
661
- def individual_replacements
662
- (1..@occurrences_count).to_a
663
- end
664
-
665
- # After identifying all individual replacements that work, try combining all
666
- # of them.
667
- def all_ok_replacements_combined
668
- [@ok_experiments.uniq.sort]
669
- end
670
-
671
- # Combining all successful individual replacements has failed. Lets divide
672
- # and conquer.
673
- def ok_replacements_pair_combinations
674
- @ok_experiments
675
- .combination(2)
676
- .map { |e| e.flatten.uniq.sort }
677
- .uniq - @fail_experiments - @ok_experiments
678
- end
679
- end
680
-
681
- # Encapsulate the join of an Experiment with an specific file.
682
- # This is important to coordinate and regulate multiple experiments in the same file.
683
- # It can track successfull experiments and failures and suggest new combinations to keep replacing the file.
684
- class ExperimentFile
685
- attr_reader :ok_experiments, :fail_experiments, :experiment
686
-
687
- def initialize(file, experiment)
688
- @file = file
689
- @ast = Fast.ast_from_file(file) if file
690
- @experiment = experiment
691
- @ok_experiments = []
692
- @fail_experiments = []
693
- @round = 0
694
- end
695
-
696
- def search
697
- experiment.expression
698
- end
699
-
700
- def experimental_filename(combination)
701
- parts = @file.split('/')
702
- dir = parts[0..-2]
703
- filename = "experiment_#{[*combination].join('_')}_#{parts[-1]}"
704
- File.join(*dir, filename)
705
- end
706
-
707
- def ok_with(combination)
708
- @ok_experiments << combination
709
- return unless combination.is_a?(Array)
710
-
711
- combination.each do |element|
712
- @ok_experiments.delete(element)
713
- end
714
- end
715
-
716
- def failed_with(combination)
717
- @fail_experiments << combination
718
- end
719
-
720
- def search_cases
721
- Fast.search(@ast, experiment.expression) || []
722
- end
723
-
724
- # rubocop:disable Metrics/AbcSize
725
- # rubocop:disable Metrics/MethodLength
726
- def partial_replace(*indices)
727
- replacement = experiment.replacement
728
- new_content = Fast.replace_file @file, experiment.expression, ->(node, *captures) do # rubocop:disable Style/Lambda
729
- if indices.nil? || indices.empty? || indices.include?(match_index)
730
- if replacement.parameters.length == 1
731
- instance_exec node, &replacement
732
- else
733
- instance_exec node, *captures, &replacement
734
- end
735
- end
736
- end
737
- return unless new_content
738
-
739
- write_experiment_file(indices, new_content)
740
- new_content
741
- end
742
- # rubocop:enable Metrics/AbcSize
743
- # rubocop:enable Metrics/MethodLength
744
-
745
- def write_experiment_file(index, new_content)
746
- filename = experimental_filename(index)
747
- File.open(filename, 'w+') { |f| f.puts new_content }
748
- filename
749
- end
750
-
751
- def done!
752
- count_executed_combinations = @fail_experiments.size + @ok_experiments.size
753
- puts "Done with #{@file} after #{count_executed_combinations} combinations"
754
- return unless perfect_combination = @ok_experiments.last # rubocop:disable Lint/AssignmentInCondition
755
-
756
- puts 'The following changes were applied to the file:'
757
- `diff #{experimental_filename(perfect_combination)} #{@file}`
758
- puts "mv #{experimental_filename(perfect_combination)} #{@file}"
759
- `mv #{experimental_filename(perfect_combination)} #{@file}`
760
- end
761
-
762
- def build_combinations
763
- @round += 1
764
- ExperimentCombinations.new(
765
- round: @round,
766
- occurrences_count: search_cases.size,
767
- ok_experiments: @ok_experiments,
768
- fail_experiments: @fail_experiments
769
- ).generate_combinations
770
- end
771
-
772
- def run
773
- while (combinations = build_combinations).any?
774
- if combinations.size > 1000
775
- puts "Ignoring #{@file} because it has #{combinations.size} possible combinations"
776
- break
777
- end
778
- puts "#{@file} - Round #{@round} - Possible combinations: #{combinations.inspect}"
779
- while combination = combinations.shift # rubocop:disable Lint/AssignmentInCondition
780
- run_partial_replacement_with(combination)
781
- end
782
- end
783
- done!
784
- end
785
-
786
- def run_partial_replacement_with(combination) # rubocop:disable Metrics/AbcSize
787
- content = partial_replace(*combination)
788
- experimental_file = experimental_filename(combination)
789
-
790
- File.open(experimental_file, 'w+') { |f| f.puts content }
791
-
792
- raise 'No changes were made to the file.' if FileUtils.compare_file(@file, experimental_file)
793
-
794
- result = experiment.ok_if.call(experimental_file)
795
-
796
- if result.success
797
- ok_with(combination)
798
- puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
799
- else
800
- failed_with(combination)
801
- puts "🔴 #{experimental_file} - Combination: #{combination}"
802
- end
803
- end
804
- end
805
575
  end
@@ -117,9 +117,7 @@ module Fast
117
117
  end
118
118
 
119
119
  def debug(*info)
120
- return unless @debug
121
-
122
- puts info
120
+ puts(info) if debug_mode?
123
121
  end
124
122
 
125
123
  def report(result, file)
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fast'
4
+
5
+ module Fast
6
+ class << self
7
+ # Fast.experiment is a shortcut to define new experiments and allow them to
8
+ # work together in experiment combinantions.
9
+ #
10
+ # The following experiment look into `spec` folder and try to remove
11
+ # `before` and `after` blocks on testing code. Sometimes they're not
12
+ # effective and we can avoid the hard work of do it manually.
13
+ #
14
+ # If the spec does not fail, it keeps the change.
15
+ #
16
+ # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
17
+ # lookup 'spec'
18
+ # search "(block (send nil {before after}))"
19
+ # edit { |node| remove(node.loc.expression) }
20
+ # policy { |new_file| system("rspec --fail-fast #{new_file}") }
21
+ # end
22
+ def experiment(name, &block)
23
+ @experiments ||= {}
24
+ @experiments[name] = Experiment.new(name, &block)
25
+ end
26
+
27
+ attr_reader :experiments
28
+ end
29
+
30
+ # You can define experiments and build experimental files to improve some code in
31
+ # an automated way. Let's create a hook to check if a `before` or `after` block
32
+ # is useless in a specific spec:
33
+ #
34
+ # ```ruby
35
+ # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
36
+ # lookup 'some_spec.rb'
37
+ # search "(block (send nil {before after}))"
38
+ # edit {|node| remove(node.loc.expression) }
39
+ # policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
40
+ # end
41
+ # ```
42
+ class Experiment
43
+ attr_writer :files
44
+ attr_reader :name, :replacement, :expression, :files_or_folders, :ok_if
45
+
46
+ def initialize(name, &block)
47
+ @name = name
48
+ puts "\nStarting experiment: #{name}"
49
+ instance_exec(&block)
50
+ end
51
+
52
+ def run_with(file)
53
+ ExperimentFile.new(file, self).run
54
+ end
55
+
56
+ def search(expression)
57
+ @expression = expression
58
+ end
59
+
60
+ def edit(&block)
61
+ @replacement = block
62
+ end
63
+
64
+ def lookup(files_or_folders)
65
+ @files_or_folders = files_or_folders
66
+ end
67
+
68
+ def policy(&block)
69
+ @ok_if = block
70
+ end
71
+
72
+ def files
73
+ @files ||= Fast.ruby_files_from(@files_or_folders)
74
+ end
75
+
76
+ def run
77
+ files.map(&method(:run_with))
78
+ end
79
+ end
80
+
81
+ # Suggest possible combinations of occurrences to replace.
82
+ class ExperimentCombinations
83
+ attr_reader :combinations
84
+
85
+ def initialize(round:, occurrences_count:, ok_experiments:, fail_experiments:)
86
+ @round = round
87
+ @ok_experiments = ok_experiments
88
+ @fail_experiments = fail_experiments
89
+ @occurrences_count = occurrences_count
90
+ end
91
+
92
+ def generate_combinations
93
+ case @round
94
+ when 1
95
+ individual_replacements
96
+ when 2
97
+ all_ok_replacements_combined
98
+ else
99
+ ok_replacements_pair_combinations
100
+ end
101
+ end
102
+
103
+ # Replace a single occurrence at each iteration and identify which
104
+ # individual replacements work.
105
+ def individual_replacements
106
+ (1..@occurrences_count).to_a
107
+ end
108
+
109
+ # After identifying all individual replacements that work, try combining all
110
+ # of them.
111
+ def all_ok_replacements_combined
112
+ [@ok_experiments.uniq.sort]
113
+ end
114
+
115
+ # Combining all successful individual replacements has failed. Lets divide
116
+ # and conquer.
117
+ def ok_replacements_pair_combinations
118
+ @ok_experiments
119
+ .combination(2)
120
+ .map { |e| e.flatten.uniq.sort }
121
+ .uniq - @fail_experiments - @ok_experiments
122
+ end
123
+ end
124
+
125
+ # Encapsulate the join of an Experiment with an specific file.
126
+ # This is important to coordinate and regulate multiple experiments in the same file.
127
+ # It can track successfull experiments and failures and suggest new combinations to keep replacing the file.
128
+ class ExperimentFile
129
+ attr_reader :ok_experiments, :fail_experiments, :experiment
130
+
131
+ def initialize(file, experiment)
132
+ @file = file
133
+ @ast = Fast.ast_from_file(file) if file
134
+ @experiment = experiment
135
+ @ok_experiments = []
136
+ @fail_experiments = []
137
+ @round = 0
138
+ end
139
+
140
+ def search
141
+ experiment.expression
142
+ end
143
+
144
+ def experimental_filename(combination)
145
+ parts = @file.split('/')
146
+ dir = parts[0..-2]
147
+ filename = "experiment_#{[*combination].join('_')}_#{parts[-1]}"
148
+ File.join(*dir, filename)
149
+ end
150
+
151
+ def ok_with(combination)
152
+ @ok_experiments << combination
153
+ return unless combination.is_a?(Array)
154
+
155
+ combination.each do |element|
156
+ @ok_experiments.delete(element)
157
+ end
158
+ end
159
+
160
+ def failed_with(combination)
161
+ @fail_experiments << combination
162
+ end
163
+
164
+ def search_cases
165
+ Fast.search(@ast, experiment.expression) || []
166
+ end
167
+
168
+ # rubocop:disable Metrics/AbcSize
169
+ # rubocop:disable Metrics/MethodLength
170
+ def partial_replace(*indices)
171
+ replacement = experiment.replacement
172
+ new_content = Fast.replace_file @file, experiment.expression do |node, *captures|
173
+ if indices.nil? || indices.empty? || indices.include?(match_index)
174
+ if replacement.parameters.length == 1
175
+ instance_exec node, &replacement
176
+ else
177
+ instance_exec node, *captures, &replacement
178
+ end
179
+ end
180
+ end
181
+ return unless new_content
182
+
183
+ write_experiment_file(indices, new_content)
184
+ new_content
185
+ end
186
+ # rubocop:enable Metrics/AbcSize
187
+ # rubocop:enable Metrics/MethodLength
188
+
189
+ def write_experiment_file(index, new_content)
190
+ filename = experimental_filename(index)
191
+ File.open(filename, 'w+') { |f| f.puts new_content }
192
+ filename
193
+ end
194
+
195
+ def done!
196
+ count_executed_combinations = @fail_experiments.size + @ok_experiments.size
197
+ puts "Done with #{@file} after #{count_executed_combinations} combinations"
198
+ return unless perfect_combination = @ok_experiments.last # rubocop:disable Lint/AssignmentInCondition
199
+
200
+ puts 'The following changes were applied to the file:'
201
+ `diff #{experimental_filename(perfect_combination)} #{@file}`
202
+ puts "mv #{experimental_filename(perfect_combination)} #{@file}"
203
+ `mv #{experimental_filename(perfect_combination)} #{@file}`
204
+ end
205
+
206
+ def build_combinations
207
+ @round += 1
208
+ ExperimentCombinations.new(
209
+ round: @round,
210
+ occurrences_count: search_cases.size,
211
+ ok_experiments: @ok_experiments,
212
+ fail_experiments: @fail_experiments
213
+ ).generate_combinations
214
+ end
215
+
216
+ def run
217
+ while (combinations = build_combinations).any?
218
+ if combinations.size > 1000
219
+ puts "Ignoring #{@file} because it has #{combinations.size} possible combinations"
220
+ break
221
+ end
222
+ puts "#{@file} - Round #{@round} - Possible combinations: #{combinations.inspect}"
223
+ while combination = combinations.shift # rubocop:disable Lint/AssignmentInCondition
224
+ run_partial_replacement_with(combination)
225
+ end
226
+ end
227
+ done!
228
+ end
229
+
230
+ def run_partial_replacement_with(combination) # rubocop:disable Metrics/AbcSize
231
+ content = partial_replace(*combination)
232
+ experimental_file = experimental_filename(combination)
233
+
234
+ File.open(experimental_file, 'w+') { |f| f.puts content }
235
+
236
+ raise 'No changes were made to the file.' if FileUtils.compare_file(@file, experimental_file)
237
+
238
+ result = experiment.ok_if.call(experimental_file)
239
+
240
+ if result.success
241
+ ok_with(combination)
242
+ puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
243
+ else
244
+ failed_with(combination)
245
+ puts "🔴 #{experimental_file} - Combination: #{combination}"
246
+ end
247
+ end
248
+ end
249
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fast
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
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.7
4
+ version: 0.0.8
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: 2019-03-24 00:00:00.000000000 Z
11
+ date: 2019-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: astrolabe
@@ -235,6 +235,7 @@ files:
235
235
  - fast.gemspec
236
236
  - lib/fast.rb
237
237
  - lib/fast/cli.rb
238
+ - lib/fast/experiment.rb
238
239
  - lib/fast/version.rb
239
240
  - mkdocs.yml
240
241
  homepage: https://jonatas.github.io/fast/