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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c7eee39cf27764a49009037c603ebd8f9afe0b8f0d78ac651c2bdee1d65f329
4
- data.tar.gz: '09eefe65daeeb37ed5d49120cbee73018303156df11f25daa5258ce310a1be89'
3
+ metadata.gz: a8e1cdbb944bd8939745c07350aaa66e4ff3e9e3313802ca23aaa7fbd33b742d
4
+ data.tar.gz: fcd59e90fedc893492aa791e129d18a8b2e2e72e72b8ca61be6be8fd3e4ad6b7
5
5
  SHA512:
6
- metadata.gz: 163730827f44a4d9a5491b47f96e6d3f10d883921710b5fa2e6f9573a586e71ff84460710288b3388ef209c90744f01d7dfc03ca5f5daff5a62c2179e3da43c5
7
- data.tar.gz: 0ecc89dd396c151c07fcf6bc9de52c3dc183909bb29498715f2e703124db75e03847894a3d7394e79b9beabee750fa13db23d74bcc95b68a721553d6f5b8c46b
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', expression)
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(['lib']) # => ["lib/fast.rb"]
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))
@@ -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
+
@@ -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
- '_' => ->(node) { !node.nil? },
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.uniq.flatten
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 = suggest_combinations).any?
702
- if combinations.size > 50
703
- puts "Ignoring #{@file} because it have #{combinations.size} possible combinations"
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/MethodLength
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 experiment.ok_if.call(experimental_file)
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} #{experimental_file}"
781
+ puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
730
782
  else
731
783
  failed_with(combination)
732
- puts "🔴 #{combination} #{experimental_file}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fast
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
data/mkdocs.yml CHANGED
@@ -17,5 +17,6 @@ nav:
17
17
  - Introduction: index.md
18
18
  - Syntax: syntax.md
19
19
  - Command Line: command_line.md
20
+ - Pry Integration: pry-integration.md
20
21
  - Experiments: experiments.md
21
22
  - Code Similarity: similarity_tutorial.md
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
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: 2018-10-22 00:00:00.000000000 Z
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