awesome_annotate 0.2.0 → 0.3.1

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: d120e2f6dba99ee06506353a609f7b68e4ad08213141762b77ca6dacacc854ce
4
- data.tar.gz: 6bb8098703ec777bfc7dcc9351ca88ed4c2f9655cb122c71f25b398165aad187
3
+ metadata.gz: 4c4c2fe792fc935930fcc07c2a9662f7032cd590bcad80f06551e5dae567f279
4
+ data.tar.gz: 62db756fb9535984c36eda116f23aa2130ba18b2077c8102423d8b559e075e78
5
5
  SHA512:
6
- metadata.gz: d04a22f78762b7fd4de71460b710b512b3c23e126ce136afa326a7aa1a32f7bc9be2c72ed7fa3a740da266ae2cea8883d0a156296089a1ed3dc8fa0b88ae02f7
7
- data.tar.gz: d64c97ff823e7eb03e2f16a9e511343ef6dc9856c33e78b2e0003e8ccaf390ce7adc87dedb62a1299aa320b0293d9a148677fbc86b7d9e6886d8e3c210ee9a1a
6
+ metadata.gz: 33795981d662f1eeae3be75bb11befd25aaed09d22f50c26647d131c1916ea54eb9f2a105272a8e42e4cb6c80a5df3c474cda12c4a9c566c0e9452512d40ac33
7
+ data.tar.gz: ae8e56afa0844d88bbb2d1cae87e12538846ccf507394943259c8856e548d7e70e9c1e49cdc8d42ce3c7e8ada99900df46f8b251396cdb8161f976c4ad25ae2f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-05-31
4
+
5
+ - Fixed a bug
6
+
7
+ ## [0.1.0] - 2024-04-29
8
+
9
+ - Initial release
10
+
11
+ ## [0.3.0] - 2026-05-31
12
+
13
+ - Added `awesome_annotate init` to create
14
+ `config/initializers/awesome_annotate.yml`.
15
+ - Added configuration loading for annotation and remove commands.
16
+ - Added path configuration:
17
+ - `env_file_path`
18
+ - `model_dir`
19
+ - `route_file_path`
20
+ - Added `annotation_position` to choose whether new annotation blocks are
21
+ inserted at the top or bottom of files.
22
+ - Added model annotation filters and output controls:
23
+ - `exclude_model_files`
24
+ - `include_indexes`
25
+ - `exclude_columns`
26
+ - `include_column_defaults`
27
+ - Added `exclude_routes` to omit matching route lines from route annotations.
28
+ - Added validation for supported configuration value types.
29
+
3
30
  ## [0.2.0] - 2026-05-10
4
31
 
5
32
  - Added model schema annotations with column type, nullability, primary key,
data/README.md CHANGED
@@ -51,6 +51,7 @@ bundle exec awesome_annotate models
51
51
  bundle exec awesome_annotate models user article admin/user
52
52
  bundle exec awesome_annotate routes
53
53
  bundle exec awesome_annotate all
54
+ bundle exec awesome_annotate init
54
55
  bundle exec awesome_annotate remove model user
55
56
  bundle exec awesome_annotate remove models
56
57
  bundle exec awesome_annotate remove routes
@@ -90,6 +91,40 @@ bundle exec awesome_annotate remove routes
90
91
  bundle exec awesome_annotate remove all
91
92
  ```
92
93
 
94
+ Create a configuration file:
95
+
96
+ ```sh
97
+ bundle exec awesome_annotate init
98
+ ```
99
+
100
+ This creates `config/initializers/awesome_annotate.yml`:
101
+
102
+ ```yaml
103
+ # AwesomeAnnotate configuration
104
+ #
105
+ # Change these paths when your Rails app uses non-standard locations.
106
+ env_file_path: config/environment.rb
107
+ model_dir: app/models
108
+ route_file_path: config/routes.rb
109
+ annotation_position: top
110
+ exclude_model_files: []
111
+ include_indexes: true
112
+ exclude_columns: []
113
+ include_column_defaults: true
114
+ exclude_routes: []
115
+ ```
116
+
117
+ When this file exists, annotation and removal commands use these values. This
118
+ allows applications with non-standard model, route, or environment file paths to
119
+ change command behavior without passing Ruby options directly. Set
120
+ `annotation_position` to `top` or `bottom` to choose where new annotation blocks
121
+ are inserted. Set `exclude_model_files` to relative file paths or glob patterns
122
+ under `model_dir` to skip them during model discovery. Set `include_indexes` to
123
+ `false` to omit the Indexes section from model schema annotations. Set
124
+ `exclude_columns` to column names to omit them from model schema annotations. Set
125
+ `include_column_defaults` to `false` to omit `default(...)` details. Set
126
+ `exclude_routes` to route line patterns to omit them from route annotations.
127
+
93
128
  This loads `config/environment.rb`, resolves `User`, reads its Active Record
94
129
  columns, and writes a schema block before the class definition in
95
130
  `app/models/user.rb`:
@@ -4,12 +4,12 @@ module AwesomeAnnotate
4
4
  module AnnotationBlock
5
5
  private
6
6
 
7
- def replace_or_insert_annotation(file_path:, marker:, content:, before:)
7
+ def replace_or_insert_annotation(file_path:, marker:, content:, before:, position: 'top')
8
8
  path = file_path.to_s
9
9
  file_content = File.read(path)
10
10
  annotation = annotation_block(marker, content)
11
11
 
12
- File.write(path, replace_annotation(file_content, marker, annotation, before))
12
+ File.write(path, replace_annotation(file_content, marker, annotation, before, position))
13
13
  end
14
14
 
15
15
  def remove_annotation(file_path:, marker:)
@@ -36,11 +36,13 @@ module AwesomeAnnotate
36
36
  %r{^# == AwesomeAnnotate: #{escaped_marker}\n.*?^# == /AwesomeAnnotate: #{escaped_marker}\n(?:\n)*}m
37
37
  end
38
38
 
39
- def replace_annotation(file_content, marker, annotation, before)
39
+ def replace_annotation(file_content, marker, annotation, before, position)
40
40
  pattern = annotation_block_pattern(marker)
41
41
 
42
42
  return file_content.sub(pattern, annotation) if file_content.match?(pattern)
43
43
 
44
+ return "#{file_content.chomp}\n#{annotation}" if position.to_s == 'bottom'
45
+
44
46
  file_content.sub(before, "#{annotation}\\0")
45
47
  end
46
48
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'awesome_annotate'
4
+ require 'awesome_annotate/configuration'
4
5
  require 'awesome_annotate/model'
5
6
  require 'awesome_annotate/route'
6
7
  require 'awesome_annotate/version'
@@ -10,23 +11,37 @@ module AwesomeAnnotate
10
11
  class Remove < Thor
11
12
  desc 'model [model_name]', 'remove annotation from a model'
12
13
  def model(model_name)
13
- AwesomeAnnotate::Model.new.remove(model_name)
14
+ model_annotator.remove(model_name)
14
15
  end
15
16
 
16
17
  desc 'models [model_names...]', 'remove annotations from all models or specified models'
17
18
  def models(*model_names)
18
- AwesomeAnnotate::Model.new.remove_all(model_names)
19
+ model_annotator.remove_all(model_names)
19
20
  end
20
21
 
21
22
  desc 'routes', 'remove annotation from `config/routes.rb`'
22
23
  def routes
23
- AwesomeAnnotate::Route.new.remove
24
+ route_annotator.remove
24
25
  end
25
26
 
26
27
  desc 'all', 'remove annotations from all models and routes'
27
28
  def all
28
- AwesomeAnnotate::Model.new.remove_all
29
- AwesomeAnnotate::Route.new.remove
29
+ model_annotator.remove_all
30
+ route_annotator.remove
31
+ end
32
+
33
+ private
34
+
35
+ def model_annotator
36
+ AwesomeAnnotate::Model.new(configuration.options)
37
+ end
38
+
39
+ def route_annotator
40
+ AwesomeAnnotate::Route.new(configuration.options)
41
+ end
42
+
43
+ def configuration
44
+ AwesomeAnnotate::Configuration.load
30
45
  end
31
46
  end
32
47
 
@@ -42,24 +57,35 @@ module AwesomeAnnotate
42
57
  map %w[model -m] => :model
43
58
  desc 'model [model_name]', 'annotate your model'
44
59
  def model(model_name)
45
- AwesomeAnnotate::Model.new.annotate(model_name)
60
+ model_annotator.annotate(model_name)
46
61
  end
47
62
 
48
63
  desc 'models [model_names...]', 'annotate all models or specified models'
49
64
  def models(*model_names)
50
- AwesomeAnnotate::Model.new.annotate_all(model_names)
65
+ model_annotator.annotate_all(model_names)
51
66
  end
52
67
 
53
68
  map %w[routes -r] => :routes
54
69
  desc 'routes', 'Writes application route information to `config/routes.rb`.'
55
70
  def routes
56
- AwesomeAnnotate::Route.new.annotate
71
+ route_annotator.annotate
57
72
  end
58
73
 
59
74
  desc 'all', 'annotate all models and routes'
60
75
  def all
61
- AwesomeAnnotate::Model.new.annotate_all
62
- AwesomeAnnotate::Route.new.annotate
76
+ model_annotator.annotate_all
77
+ route_annotator.annotate
78
+ end
79
+
80
+ desc 'init', "create #{AwesomeAnnotate::Configuration::DEFAULT_PATH}"
81
+ def init
82
+ path = AwesomeAnnotate::Configuration::DEFAULT_PATH
83
+ if File.exist?(path)
84
+ say "Config file already exists: #{path}"
85
+ else
86
+ AwesomeAnnotate::Configuration.create(path)
87
+ say "create #{path}"
88
+ end
63
89
  end
64
90
 
65
91
  desc 'remove SUBCOMMAND', 'remove generated annotations'
@@ -68,5 +94,19 @@ module AwesomeAnnotate
68
94
  def self.exit_on_failure?
69
95
  true
70
96
  end
97
+
98
+ private
99
+
100
+ def model_annotator
101
+ AwesomeAnnotate::Model.new(configuration.options)
102
+ end
103
+
104
+ def route_annotator
105
+ AwesomeAnnotate::Route.new(configuration.options)
106
+ end
107
+
108
+ def configuration
109
+ AwesomeAnnotate::Configuration.load
110
+ end
71
111
  end
72
112
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module AwesomeAnnotate
7
+ class Configuration
8
+ DEFAULT_PATH = 'config/initializers/awesome_annotate.yml'
9
+ DEFAULT_OPTIONS = {
10
+ env_file_path: 'config/environment.rb',
11
+ model_dir: 'app/models',
12
+ route_file_path: 'config/routes.rb',
13
+ annotation_position: 'top',
14
+ exclude_model_files: [],
15
+ include_indexes: true,
16
+ exclude_columns: [],
17
+ include_column_defaults: true,
18
+ exclude_routes: []
19
+ }.freeze
20
+ ANNOTATION_POSITIONS = %w[top bottom].freeze
21
+
22
+ TEMPLATE = <<~YAML
23
+ # AwesomeAnnotate configuration
24
+ #
25
+ # Change these paths when your Rails app uses non-standard locations.
26
+ env_file_path: config/environment.rb
27
+ model_dir: app/models
28
+ route_file_path: config/routes.rb
29
+ annotation_position: top
30
+ exclude_model_files: []
31
+ include_indexes: true
32
+ exclude_columns: []
33
+ include_column_defaults: true
34
+ exclude_routes: []
35
+ YAML
36
+
37
+ class << self
38
+ def load(path = DEFAULT_PATH)
39
+ return new unless File.exist?(path)
40
+
41
+ loaded = YAML.safe_load_file(path, aliases: false) || {}
42
+ raise ArgumentError, "Configuration file must contain a YAML mapping: #{path}" unless loaded.is_a?(Hash)
43
+
44
+ new(symbolize_options(loaded))
45
+ end
46
+
47
+ def create(path = DEFAULT_PATH)
48
+ FileUtils.mkdir_p(File.dirname(path))
49
+ File.write(path, TEMPLATE)
50
+ end
51
+
52
+ private
53
+
54
+ def symbolize_options(options)
55
+ options.each_with_object({}) do |(key, value), result|
56
+ symbol_key = key.to_sym
57
+ result[symbol_key] = value if DEFAULT_OPTIONS.key?(symbol_key)
58
+ end
59
+ end
60
+ end
61
+
62
+ attr_reader :options
63
+
64
+ def initialize(options = {})
65
+ @options = DEFAULT_OPTIONS.merge(options)
66
+ validate_annotation_position
67
+ validate_exclude_model_files
68
+ validate_include_indexes
69
+ validate_exclude_columns
70
+ validate_include_column_defaults
71
+ validate_exclude_routes
72
+ end
73
+
74
+ private
75
+
76
+ def validate_annotation_position
77
+ return if ANNOTATION_POSITIONS.include?(@options[:annotation_position])
78
+
79
+ raise ArgumentError, "annotation_position must be one of: #{ANNOTATION_POSITIONS.join(', ')}"
80
+ end
81
+
82
+ def validate_exclude_model_files
83
+ return if @options[:exclude_model_files].is_a?(Array)
84
+
85
+ raise ArgumentError, 'exclude_model_files must be an array'
86
+ end
87
+
88
+ def validate_include_indexes
89
+ return if [true, false].include?(@options[:include_indexes])
90
+
91
+ raise ArgumentError, 'include_indexes must be true or false'
92
+ end
93
+
94
+ def validate_exclude_columns
95
+ return if @options[:exclude_columns].is_a?(Array)
96
+
97
+ raise ArgumentError, 'exclude_columns must be an array'
98
+ end
99
+
100
+ def validate_include_column_defaults
101
+ return if [true, false].include?(@options[:include_column_defaults])
102
+
103
+ raise ArgumentError, 'include_column_defaults must be true or false'
104
+ end
105
+
106
+ def validate_exclude_routes
107
+ return if @options[:exclude_routes].is_a?(Array)
108
+
109
+ raise ArgumentError, 'exclude_routes must be an array'
110
+ end
111
+ end
112
+ end
@@ -18,6 +18,11 @@ module AwesomeAnnotate
18
18
  super()
19
19
  @env_file_path = Pathname.new(params[:env_file_path] || 'config/environment.rb')
20
20
  @model_dir = Pathname.new(params[:model_dir] || 'app/models')
21
+ @annotation_position = params[:annotation_position] || 'top'
22
+ @exclude_model_files = params[:exclude_model_files] || []
23
+ @include_indexes = params.fetch(:include_indexes, true)
24
+ @exclude_columns = params[:exclude_columns] || []
25
+ @include_column_defaults = params.fetch(:include_column_defaults, true)
21
26
  end
22
27
 
23
28
  desc 'model [model name]', 'annotate your model'
@@ -75,9 +80,7 @@ module AwesomeAnnotate
75
80
  discovered_model_file_paths.map { |file_path| model_name_from_file_path(file_path) }
76
81
  end
77
82
 
78
- def discovered_model_file_paths
79
- Dir.glob(@model_dir.join('**/*.rb')).reject { |file_path| excluded_model_file?(file_path) }
80
- end
83
+ def discovered_model_file_paths = Dir.glob(@model_dir.join('**/*.rb')).reject { |path| excluded_model_file?(path) }
81
84
 
82
85
  def model_name_from_file_path(file_path)
83
86
  Pathname.new(file_path).relative_path_from(@model_dir).sub_ext('').to_s
@@ -86,7 +89,12 @@ module AwesomeAnnotate
86
89
  def excluded_model_file?(file_path)
87
90
  relative_path = Pathname.new(file_path).relative_path_from(@model_dir).to_s
88
91
 
89
- relative_path == 'application_record.rb' || relative_path.start_with?('concerns/')
92
+ relative_path == 'application_record.rb' || relative_path.start_with?('concerns/') ||
93
+ excluded_by_config?(relative_path)
94
+ end
95
+
96
+ def excluded_by_config?(relative_path)
97
+ @exclude_model_files.any? { |pattern| File.fnmatch?(pattern, relative_path, File::FNM_PATHNAME) }
90
98
  end
91
99
 
92
100
  def annotate_discovered_model(model_name)
@@ -104,12 +112,8 @@ module AwesomeAnnotate
104
112
  end
105
113
 
106
114
  def insert_file_before_class(file_path, message)
107
- replace_or_insert_annotation(
108
- file_path: file_path,
109
- marker: 'columns',
110
- content: message,
111
- before: /^class\s+\w+\s+<\s+\w+/
112
- )
115
+ replace_or_insert_annotation(file_path: file_path, marker: 'columns', content: message,
116
+ before: /^class\s+\w+\s+<\s+\w+/, position: @annotation_position)
113
117
  end
114
118
 
115
119
  def model_file_path(model_name)
@@ -131,9 +135,8 @@ module AwesomeAnnotate
131
135
  raise AwesomeAnnotate::NotFoundError
132
136
  end
133
137
 
134
- def self.source_root
135
- Dir.pwd
136
- end
138
+ def self.source_root = Dir.pwd
139
+
137
140
  private_class_method :source_root
138
141
  end
139
142
  end
@@ -15,6 +15,8 @@ module AwesomeAnnotate
15
15
  super()
16
16
  @env_file_path = Pathname.new(params[:env_file_path] || 'config/environment.rb')
17
17
  @route_file_path = Pathname.new(params[:route_file_path] || 'config/routes.rb')
18
+ @annotation_position = params[:annotation_position] || 'top'
19
+ @exclude_routes = params[:exclude_routes] || []
18
20
  end
19
21
 
20
22
  desc 'annotate all routes', 'annotate your routes'
@@ -50,7 +52,7 @@ module AwesomeAnnotate
50
52
  private
51
53
 
52
54
  def parse_routes(routes)
53
- split_routes = routes.split(/\r\n|\r|\n/)
55
+ split_routes = routes.split(/\r\n|\r|\n/).reject { |route| excluded_route?(route) }
54
56
  parse_routes = split_routes.map do |route|
55
57
  "# #{route}\n"
56
58
  end
@@ -59,12 +61,17 @@ module AwesomeAnnotate
59
61
  parse_routes.join
60
62
  end
61
63
 
64
+ def excluded_route?(route)
65
+ @exclude_routes.any? { |pattern| File.fnmatch?(pattern, route.strip) }
66
+ end
67
+
62
68
  def insert_file_before_class(file_path, message)
63
69
  replace_or_insert_annotation(
64
70
  file_path: file_path,
65
71
  marker: 'routes',
66
72
  content: message,
67
- before: "Rails.application.routes.draw do\n"
73
+ before: "Rails.application.routes.draw do\n",
74
+ position: @annotation_position
68
75
  )
69
76
  end
70
77
 
@@ -5,18 +5,28 @@ module AwesomeAnnotate
5
5
  private
6
6
 
7
7
  def schema_annotation(klass)
8
- columns = klass.columns
9
- column_name_width = columns.map { |column| column.name.length }.max || 0
10
- column_type_width = columns.map { |column| column_type(column).length }.max || 0
8
+ columns = schema_columns(klass)
11
9
 
12
10
  [
13
11
  schema_header(klass),
14
- columns.map { |column| column_annotation(klass, column, column_name_width, column_type_width) }.join,
15
- index_annotations(klass),
12
+ column_annotations(klass, columns),
13
+ include_indexes? ? index_annotations(klass) : '',
16
14
  "#\n"
17
15
  ].join
18
16
  end
19
17
 
18
+ def include_indexes?
19
+ @include_indexes != false
20
+ end
21
+
22
+ def include_column_defaults?
23
+ @include_column_defaults != false
24
+ end
25
+
26
+ def schema_columns(klass)
27
+ klass.columns.reject { |column| @exclude_columns.include?(column.name) }
28
+ end
29
+
20
30
  def schema_header(klass)
21
31
  [
22
32
  "# == Schema Information\n",
@@ -26,6 +36,13 @@ module AwesomeAnnotate
26
36
  ].join
27
37
  end
28
38
 
39
+ def column_annotations(klass, columns)
40
+ column_name_width = columns.map { |column| column.name.length }.max || 0
41
+ column_type_width = columns.map { |column| column_type(column).length }.max || 0
42
+
43
+ columns.map { |column| column_annotation(klass, column, column_name_width, column_type_width) }.join
44
+ end
45
+
29
46
  def column_annotation(klass, column, column_name_width, column_type_width)
30
47
  column_name = column.name.ljust(column_name_width)
31
48
  type = column_type(column).ljust(column_type_width)
@@ -44,7 +61,7 @@ module AwesomeAnnotate
44
61
  details = []
45
62
  details << 'not null' if column.null == false
46
63
  details << 'primary key' if column.name == klass.primary_key
47
- details << "default(#{column.default.inspect})" unless column.default.nil?
64
+ details << "default(#{column.default.inspect})" if include_column_defaults? && !column.default.nil?
48
65
  details
49
66
  end
50
67
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AwesomeAnnotate
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -2,4 +2,5 @@
2
2
 
3
3
  require_relative 'awesome_annotate/version'
4
4
  require_relative 'awesome_annotate/error'
5
+ require_relative 'awesome_annotate/configuration'
5
6
  require_relative 'awesome_annotate/cli'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awesome_annotate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - wisdom-plus
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-10 00:00:00.000000000 Z
11
+ date: 2026-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -63,6 +63,7 @@ files:
63
63
  - lib/awesome_annotate.rb
64
64
  - lib/awesome_annotate/annotation_block.rb
65
65
  - lib/awesome_annotate/cli.rb
66
+ - lib/awesome_annotate/configuration.rb
66
67
  - lib/awesome_annotate/error.rb
67
68
  - lib/awesome_annotate/model.rb
68
69
  - lib/awesome_annotate/rails_environment.rb