annotaterb 4.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE.txt +55 -0
  4. data/README.md +91 -0
  5. data/VERSION +1 -0
  6. data/exe/annotaterb +21 -0
  7. data/lib/annotate_rb/active_record_patch.rb +9 -0
  8. data/lib/annotate_rb/commands/annotate_models.rb +22 -0
  9. data/lib/annotate_rb/commands/annotate_routes.rb +19 -0
  10. data/lib/annotate_rb/commands/print_help.rb +16 -0
  11. data/lib/annotate_rb/commands/print_version.rb +12 -0
  12. data/lib/annotate_rb/commands.rb +10 -0
  13. data/lib/annotate_rb/config_finder.rb +21 -0
  14. data/lib/annotate_rb/config_loader.rb +63 -0
  15. data/lib/annotate_rb/core.rb +23 -0
  16. data/lib/annotate_rb/eager_loader.rb +23 -0
  17. data/lib/annotate_rb/env.rb +30 -0
  18. data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +19 -0
  19. data/lib/annotate_rb/model_annotator/annotator.rb +74 -0
  20. data/lib/annotate_rb/model_annotator/bad_model_file_error.rb +11 -0
  21. data/lib/annotate_rb/model_annotator/constants.rb +22 -0
  22. data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +25 -0
  23. data/lib/annotate_rb/model_annotator/file_annotator.rb +79 -0
  24. data/lib/annotate_rb/model_annotator/file_name_resolver.rb +16 -0
  25. data/lib/annotate_rb/model_annotator/file_patterns.rb +129 -0
  26. data/lib/annotate_rb/model_annotator/helper.rb +54 -0
  27. data/lib/annotate_rb/model_annotator/model_class_getter.rb +63 -0
  28. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +118 -0
  29. data/lib/annotate_rb/model_annotator/model_files_getter.rb +62 -0
  30. data/lib/annotate_rb/model_annotator/pattern_getter.rb +27 -0
  31. data/lib/annotate_rb/model_annotator/schema_info.rb +480 -0
  32. data/lib/annotate_rb/model_annotator.rb +20 -0
  33. data/lib/annotate_rb/options.rb +204 -0
  34. data/lib/annotate_rb/parser.rb +385 -0
  35. data/lib/annotate_rb/rake_bootstrapper.rb +34 -0
  36. data/lib/annotate_rb/route_annotator/annotation_processor.rb +56 -0
  37. data/lib/annotate_rb/route_annotator/annotator.rb +40 -0
  38. data/lib/annotate_rb/route_annotator/base_processor.rb +104 -0
  39. data/lib/annotate_rb/route_annotator/header_generator.rb +113 -0
  40. data/lib/annotate_rb/route_annotator/helper.rb +104 -0
  41. data/lib/annotate_rb/route_annotator/removal_processor.rb +40 -0
  42. data/lib/annotate_rb/route_annotator.rb +12 -0
  43. data/lib/annotate_rb/runner.rb +34 -0
  44. data/lib/annotate_rb/tasks/annotate_models_migrate.rake +30 -0
  45. data/lib/annotate_rb.rb +30 -0
  46. data/lib/generators/annotate_rb/USAGE +4 -0
  47. data/lib/generators/annotate_rb/install_generator.rb +15 -0
  48. data/lib/generators/annotate_rb/templates/auto_annotate_models.rake +7 -0
  49. metadata +96 -0
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class FileNameResolver
6
+ class << self
7
+ def call(filename_template, model_name, table_name)
8
+ filename_template
9
+ .gsub('%MODEL_NAME%', model_name)
10
+ .gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize)
11
+ .gsub('%TABLE_NAME%', table_name || model_name.pluralize)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,129 @@
1
+ module AnnotateRb
2
+ module ModelAnnotator
3
+ # This module provides module method to get file paths.
4
+ module FilePatterns
5
+ # Controller files
6
+ CONTROLLER_DIR = File.join('app', 'controllers')
7
+
8
+ # Active admin registry files
9
+ ACTIVEADMIN_DIR = File.join('app', 'admin')
10
+
11
+ # Helper files
12
+ HELPER_DIR = File.join('app', 'helpers')
13
+
14
+ # File.join for windows reverse bar compat?
15
+ # I dont use windows, can`t test
16
+ UNIT_TEST_DIR = File.join('test', 'unit')
17
+ MODEL_TEST_DIR = File.join('test', 'models') # since rails 4.0
18
+ SPEC_MODEL_DIR = File.join('spec', 'models')
19
+
20
+ FIXTURE_TEST_DIR = File.join('test', 'fixtures')
21
+ FIXTURE_SPEC_DIR = File.join('spec', 'fixtures')
22
+
23
+ # Other test files
24
+ CONTROLLER_TEST_DIR = File.join('test', 'controllers')
25
+ CONTROLLER_SPEC_DIR = File.join('spec', 'controllers')
26
+ REQUEST_SPEC_DIR = File.join('spec', 'requests')
27
+ ROUTING_SPEC_DIR = File.join('spec', 'routing')
28
+
29
+ # Object Daddy http://github.com/flogic/object_daddy/tree/master
30
+ EXEMPLARS_TEST_DIR = File.join('test', 'exemplars')
31
+ EXEMPLARS_SPEC_DIR = File.join('spec', 'exemplars')
32
+
33
+ # Machinist http://github.com/notahat/machinist
34
+ BLUEPRINTS_TEST_DIR = File.join('test', 'blueprints')
35
+ BLUEPRINTS_SPEC_DIR = File.join('spec', 'blueprints')
36
+
37
+ # Factory Bot https://github.com/thoughtbot/factory_bot
38
+ FACTORY_BOT_TEST_DIR = File.join('test', 'factories')
39
+ FACTORY_BOT_SPEC_DIR = File.join('spec', 'factories')
40
+
41
+ # Fabrication https://github.com/paulelliott/fabrication.git
42
+ FABRICATORS_TEST_DIR = File.join('test', 'fabricators')
43
+ FABRICATORS_SPEC_DIR = File.join('spec', 'fabricators')
44
+
45
+ # Serializers https://github.com/rails-api/active_model_serializers
46
+ SERIALIZERS_DIR = File.join('app', 'serializers')
47
+ SERIALIZERS_TEST_DIR = File.join('test', 'serializers')
48
+ SERIALIZERS_SPEC_DIR = File.join('spec', 'serializers')
49
+
50
+ class << self
51
+ def generate(root_directory, pattern_type, options)
52
+ case pattern_type
53
+ when 'test' then test_files(root_directory)
54
+ when 'fixture' then fixture_files(root_directory)
55
+ when 'scaffold' then scaffold_files(root_directory)
56
+ when 'factory' then factory_files(root_directory)
57
+ when 'serializer' then serialize_files(root_directory)
58
+ when 'additional_file_patterns'
59
+ [options[:additional_file_patterns] || []].flatten
60
+ when 'controller'
61
+ [File.join(root_directory, CONTROLLER_DIR, '%PLURALIZED_MODEL_NAME%_controller.rb')]
62
+ when 'admin'
63
+ [
64
+ File.join(root_directory, ACTIVEADMIN_DIR, '%MODEL_NAME%.rb'),
65
+ File.join(root_directory, ACTIVEADMIN_DIR, '%PLURALIZED_MODEL_NAME%.rb')
66
+ ]
67
+ when 'helper'
68
+ [File.join(root_directory, HELPER_DIR, '%PLURALIZED_MODEL_NAME%_helper.rb')]
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def test_files(root_directory)
77
+ [
78
+ File.join(root_directory, UNIT_TEST_DIR, '%MODEL_NAME%_test.rb'),
79
+ File.join(root_directory, MODEL_TEST_DIR, '%MODEL_NAME%_test.rb'),
80
+ File.join(root_directory, SPEC_MODEL_DIR, '%MODEL_NAME%_spec.rb')
81
+ ]
82
+ end
83
+
84
+ def fixture_files(root_directory)
85
+ [
86
+ File.join(root_directory, FIXTURE_TEST_DIR, '%TABLE_NAME%.yml'),
87
+ File.join(root_directory, FIXTURE_SPEC_DIR, '%TABLE_NAME%.yml'),
88
+ File.join(root_directory, FIXTURE_TEST_DIR, '%PLURALIZED_MODEL_NAME%.yml'),
89
+ File.join(root_directory, FIXTURE_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.yml')
90
+ ]
91
+ end
92
+
93
+ def scaffold_files(root_directory)
94
+ [
95
+ File.join(root_directory, CONTROLLER_TEST_DIR, '%PLURALIZED_MODEL_NAME%_controller_test.rb'),
96
+ File.join(root_directory, CONTROLLER_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_controller_spec.rb'),
97
+ File.join(root_directory, REQUEST_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_spec.rb'),
98
+ File.join(root_directory, ROUTING_SPEC_DIR, '%PLURALIZED_MODEL_NAME%_routing_spec.rb')
99
+ ]
100
+ end
101
+
102
+ def factory_files(root_directory)
103
+ [
104
+ File.join(root_directory, EXEMPLARS_TEST_DIR, '%MODEL_NAME%_exemplar.rb'),
105
+ File.join(root_directory, EXEMPLARS_SPEC_DIR, '%MODEL_NAME%_exemplar.rb'),
106
+ File.join(root_directory, BLUEPRINTS_TEST_DIR, '%MODEL_NAME%_blueprint.rb'),
107
+ File.join(root_directory, BLUEPRINTS_SPEC_DIR, '%MODEL_NAME%_blueprint.rb'),
108
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, '%MODEL_NAME%_factory.rb'), # (old style)
109
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%MODEL_NAME%_factory.rb'), # (old style)
110
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, '%TABLE_NAME%.rb'), # (new style)
111
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%TABLE_NAME%.rb'), # (new style)
112
+ File.join(root_directory, FACTORY_BOT_TEST_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style)
113
+ File.join(root_directory, FACTORY_BOT_SPEC_DIR, '%PLURALIZED_MODEL_NAME%.rb'), # (new style)
114
+ File.join(root_directory, FABRICATORS_TEST_DIR, '%MODEL_NAME%_fabricator.rb'),
115
+ File.join(root_directory, FABRICATORS_SPEC_DIR, '%MODEL_NAME%_fabricator.rb')
116
+ ]
117
+ end
118
+
119
+ def serialize_files(root_directory)
120
+ [
121
+ File.join(root_directory, SERIALIZERS_DIR, '%MODEL_NAME%_serializer.rb'),
122
+ File.join(root_directory, SERIALIZERS_TEST_DIR, '%MODEL_NAME%_serializer_test.rb'),
123
+ File.join(root_directory, SERIALIZERS_SPEC_DIR, '%MODEL_NAME%_serializer_spec.rb')
124
+ ]
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module Helper
6
+ MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper).freeze
7
+
8
+ class << self
9
+ def matched_types(options)
10
+ types = MATCHED_TYPES.dup
11
+ types << 'admin' if options[:active_admin] =~ Constants::TRUE_RE && !types.include?('admin')
12
+ types << 'additional_file_patterns' if options[:additional_file_patterns].present?
13
+
14
+ types
15
+ end
16
+
17
+ def magic_comments_as_string(content)
18
+ magic_comments = content.scan(Annotator::MAGIC_COMMENT_MATCHER).flatten.compact
19
+
20
+ if magic_comments.any?
21
+ magic_comments.join
22
+ else
23
+ ''
24
+ end
25
+ end
26
+
27
+ def skip_on_migration?
28
+ Env.read('ANNOTATE_SKIP_ON_DB_MIGRATE') =~ Constants::TRUE_RE || Env.read('skip_on_db_migrate') =~ Constants::TRUE_RE
29
+ end
30
+
31
+ def include_routes?
32
+ Env.read('routes') =~ Constants::TRUE_RE
33
+ end
34
+
35
+ def include_models?
36
+ Env.read('models') =~ Constants::TRUE_RE
37
+ end
38
+
39
+ def true?(val)
40
+ val.present? && Constants::TRUE_RE.match?(val)
41
+ end
42
+
43
+ # TODO: Find another implementation that doesn't depend on ActiveSupport
44
+ def fallback(*args)
45
+ args.compact.detect(&:present?)
46
+ end
47
+
48
+ def reset_options(options)
49
+ options.flatten.each { |key| Env.write(key, nil) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class ModelClassGetter
6
+ class << self
7
+ # Retrieve the classes belonging to the model names we're asked to process
8
+ # Check for namespaced models in subdirectories as well as models
9
+ # in subdirectories without namespacing.
10
+ def call(file, options)
11
+ model_path = file.gsub(/\.rb$/, '')
12
+ options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
13
+
14
+ begin
15
+ get_loaded_model(model_path, file) || raise(BadModelFileError.new)
16
+ rescue LoadError
17
+ # this is for non-rails projects, which don't get Rails auto-require magic
18
+ file_path = File.expand_path(file)
19
+ if File.file?(file_path) && Kernel.require(file_path)
20
+ retry
21
+ elsif model_path =~ /\//
22
+ model_path = model_path.split('/')[1..-1].join('/').to_s
23
+ retry
24
+ else
25
+ raise
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # Retrieve loaded model class
33
+ def get_loaded_model(model_path, file)
34
+ loaded_model_class = get_loaded_model_by_path(model_path)
35
+ return loaded_model_class if loaded_model_class
36
+
37
+ # We cannot get loaded model when `model_path` is loaded by Rails
38
+ # auto_load/eager_load paths. Try all possible model paths one by one.
39
+ absolute_file = File.expand_path(file)
40
+ model_paths =
41
+ $LOAD_PATH.select { |path| absolute_file.include?(path) }
42
+ .map { |path| absolute_file.sub(path, '').sub(/\.rb$/, '').sub(/^\//, '') }
43
+ model_paths
44
+ .map { |path| get_loaded_model_by_path(path) }
45
+ .find { |loaded_model| !loaded_model.nil? }
46
+ end
47
+
48
+ # Retrieve loaded model class by path to the file where it's supposed to be defined.
49
+ def get_loaded_model_by_path(model_path)
50
+ ::ActiveSupport::Inflector.constantize(::ActiveSupport::Inflector.camelize(model_path))
51
+ rescue StandardError, LoadError
52
+ # Revert to the old way but it is not really robust
53
+ ObjectSpace.each_object(::Class)
54
+ .select do |c|
55
+ Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
56
+ c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
57
+ c.ancestors.include?(::ActiveRecord::Base)
58
+ end.detect { |c| ::ActiveSupport::Inflector.underscore(c.to_s) == model_path }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ # Not sure yet what the difference is between this and FileAnnotator
6
+ class ModelFileAnnotator
7
+ class << self
8
+ def call(annotated, file, header, options)
9
+ begin
10
+ return false if /#{Constants::SKIP_ANNOTATION_PREFIX}.*/ =~ (File.exist?(file) ? File.read(file) : '')
11
+ klass = ModelClassGetter.call(file, options)
12
+
13
+ klass_is_a_class = klass.is_a?(Class)
14
+ klass_inherits_active_record_base = klass < ActiveRecord::Base
15
+ klass_is_not_abstract = !klass.abstract_class?
16
+ klass_table_exists = klass.table_exists?
17
+
18
+ not_sure_this_conditional = (!options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
19
+
20
+ annotate_conditions = [
21
+ klass_is_a_class,
22
+ klass_inherits_active_record_base,
23
+ not_sure_this_conditional,
24
+ klass_is_not_abstract,
25
+ klass_table_exists
26
+ ]
27
+
28
+ do_annotate = annotate_conditions.all?
29
+
30
+ if do_annotate
31
+ files_annotated = annotate(klass, file, header, options)
32
+ annotated.concat(files_annotated)
33
+ end
34
+
35
+ rescue BadModelFileError => e
36
+ unless options[:ignore_unknown_models]
37
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
38
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
39
+ end
40
+ rescue StandardError => e
41
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
42
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Given the name of an ActiveRecord class, create a schema
49
+ # info block (basically a comment containing information
50
+ # on the columns and their types) and put it at the front
51
+ # of the model and fixture source files.
52
+ #
53
+ # === Options (opts)
54
+ # :position_in_class<Symbol>:: where to place the annotated section in model file
55
+ # :position_in_test<Symbol>:: where to place the annotated section in test/spec file(s)
56
+ # :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
57
+ # :position_in_factory<Symbol>:: where to place the annotated section in factory file
58
+ # :position_in_serializer<Symbol>:: where to place the annotated section in serializer file
59
+ # :exclude_tests<Symbol>:: whether to skip modification of test/spec files
60
+ # :exclude_fixtures<Symbol>:: whether to skip modification of fixture files
61
+ # :exclude_factories<Symbol>:: whether to skip modification of factory files
62
+ # :exclude_serializers<Symbol>:: whether to skip modification of serializer files
63
+ # :exclude_scaffolds<Symbol>:: whether to skip modification of scaffold files
64
+ # :exclude_controllers<Symbol>:: whether to skip modification of controller files
65
+ # :exclude_helpers<Symbol>:: whether to skip modification of helper files
66
+ # :exclude_sti_subclasses<Symbol>:: whether to skip modification of files for STI subclasses
67
+ #
68
+ # == Returns:
69
+ # an array of file names that were annotated.
70
+ #
71
+ def annotate(klass, file, header, options = {})
72
+ begin
73
+ klass.reset_column_information
74
+ info = SchemaInfo.generate(klass, header, options)
75
+ model_name = klass.name.underscore
76
+ table_name = klass.table_name
77
+ model_file_name = File.join(file)
78
+ annotated = []
79
+
80
+ if FileAnnotator.call(model_file_name, info, :position_in_class, options)
81
+ annotated << model_file_name
82
+ end
83
+
84
+ Helper.matched_types(options).each do |key|
85
+ exclusion_key = "exclude_#{key.pluralize}".to_sym
86
+ position_key = "position_in_#{key}".to_sym
87
+
88
+ # Same options for active_admin models
89
+ if key == 'admin'
90
+ exclusion_key = 'exclude_class'.to_sym
91
+ position_key = 'position_in_class'.to_sym
92
+ end
93
+
94
+ next if options[exclusion_key]
95
+
96
+ patterns = PatternGetter.call(options, key)
97
+
98
+ patterns
99
+ .map { |f| FileNameResolver.call(f, model_name, table_name) }
100
+ .map { |f| Dir.glob(f) }
101
+ .flatten
102
+ .each do |f|
103
+ if FileAnnotator.call(f, info, position_key, options)
104
+ annotated << f
105
+ end
106
+ end
107
+ end
108
+ rescue StandardError => e
109
+ $stderr.puts "Unable to annotate #{file}: #{e.message}"
110
+ $stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
111
+ end
112
+
113
+ annotated
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class ModelFilesGetter
6
+ class << self
7
+ # Return a list of the model files to annotate.
8
+ # If we have command line arguments, they're assumed to the path
9
+ # of model files from root dir. Otherwise we take all the model files
10
+ # in the model_dir directory.
11
+ def call(options)
12
+ model_files = []
13
+
14
+ model_files = list_model_files_from_argument(options) if !options[:is_rake]
15
+
16
+ return model_files if !model_files.empty?
17
+
18
+ options[:model_dir].each do |dir|
19
+ Dir.chdir(dir) do
20
+ list = if options[:ignore_model_sub_dir]
21
+ Dir["*.rb"].map { |f| [dir, f] }
22
+ else
23
+ Dir["**/*.rb"].reject { |f| f["concerns/"] }.map { |f| [dir, f] }
24
+ end
25
+ model_files.concat(list)
26
+ end
27
+ end
28
+
29
+ model_files
30
+ rescue SystemCallError
31
+ $stderr.puts "No models found in directory '#{options[:model_dir].join("', '")}'."
32
+ $stderr.puts "Either specify models on the command line, or use the --model-dir option."
33
+ $stderr.puts "Call 'annotate --help' for more info."
34
+ # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
35
+ end
36
+
37
+ private
38
+
39
+ def list_model_files_from_argument(options)
40
+ return [] if ARGV.empty?
41
+
42
+ specified_files = ARGV.map { |file| File.expand_path(file) }
43
+
44
+ model_files = options[:model_dir].flat_map do |dir|
45
+ absolute_dir_path = File.expand_path(dir)
46
+ specified_files
47
+ .find_all { |file| file.start_with?(absolute_dir_path) }
48
+ .map { |file| [dir, file.sub("#{absolute_dir_path}/", '')] }
49
+ end
50
+
51
+ if model_files.size != specified_files.size
52
+ $stderr.puts "The specified file could not be found in directory '#{options[:model_dir].join("', '")}'."
53
+ $stderr.puts "Call 'annotate --help' for more info."
54
+ # exit 1 # TODO: Return exit code back to caller. Right now it messes up RSpec being able to run
55
+ end
56
+
57
+ model_files
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ class PatternGetter
6
+ class << self
7
+ def call(options, pattern_types = [])
8
+ current_patterns = []
9
+
10
+ options[:root_dir].each do |root_directory|
11
+ Array(pattern_types).each do |pattern_type|
12
+ patterns = FilePatterns.generate(root_directory, pattern_type, options)
13
+
14
+ current_patterns += if pattern_type.to_sym == :additional_file_patterns
15
+ patterns
16
+ else
17
+ patterns.map { |p| p.sub(/^[\/]*/, '') }
18
+ end
19
+ end
20
+ end
21
+
22
+ current_patterns
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end