tomkersten-annotate-models 1.0.4

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.
@@ -0,0 +1,15 @@
1
+ == 1.0.2 2008-03-22
2
+
3
+ * Add contributions from Michael Bumann (http://github.com/bumi)
4
+ * added an option "position" to choose to put the annotation,
5
+ * spec/fixtures now also get annotated
6
+ * added a task to remove the annotations
7
+ * these options can be specified from command line as -d and -p [before|after]
8
+
9
+ == 1.0.3 2008-05-02
10
+
11
+ * Add misc changes from Dustin Sallings and Henrik N
12
+ * Remove trailing whitespace
13
+ * More intuitive info messages
14
+ * Update README file with update-to-date example
15
+
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008 Dave Thomas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,27 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/annotate_models.rb
9
+ lib/annotate_models/version.rb
10
+ lib/annotate_models/tasks.rb
11
+ lib/tasks/annotate.rake
12
+ log/debug.log
13
+ script/destroy
14
+ script/generate
15
+ script/txt2html
16
+ bin/annotate
17
+ setup.rb
18
+ tasks/deployment.rake
19
+ tasks/environment.rake
20
+ tasks/website.rake
21
+ test/test_annotate_models.rb
22
+ test/test_helper.rb
23
+ website/index.html
24
+ website/index.txt
25
+ website/javascripts/rounded_corners_lite.inc.js
26
+ website/stylesheets/screen.css
27
+ website/template.rhtml
@@ -0,0 +1,50 @@
1
+ == AnnotateModels
2
+
3
+ Add a comment summarizing the current schema to the top of each ActiveRecord model source file.
4
+
5
+ # == Schema Information
6
+ #
7
+ # id :integer(11) not null
8
+ # quantity :integer(11)
9
+ # product_id :integer(11)
10
+ # unit_price :float
11
+ # order_id :integer(11)
12
+ #
13
+
14
+ class LineItem < ActiveRecord::Base
15
+ belongs_to :product
16
+
17
+ end
18
+
19
+ Note that this code will blow away the initial comment block in your models if it looks like it was
20
+ previously added by annotate models, so you don't want to add additional text to an automatically
21
+ created comment block.
22
+
23
+ == Install
24
+
25
+ sudo gem install annotate-models
26
+
27
+ == Usage
28
+
29
+ cd [your project]
30
+ annotate
31
+ annotate -d
32
+ annotate -p [before|after]
33
+ annotate -h
34
+
35
+ == Source
36
+
37
+ http://github.com/ctran/annotate_models
38
+
39
+ == Author
40
+ Dave Thomas
41
+ Pragmatic Programmers, LLC
42
+
43
+ Released under the same license as Ruby. No Support. No Warranty.
44
+
45
+ == Modifications
46
+ - alex@pivotallabs.com
47
+ - Cuong Tran - http://github.com/ctran
48
+ - Jack Danger - http://github.com/JackDanger
49
+ - Michael Bumann - http://github.com/bumi
50
+ - Henrik Nyh - http://github.com/henrik
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'annotate_models/tasks'
5
+
6
+ task = "annotate_models"
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: annotate [options]"
10
+ opts.on('-d', '--delete') { task = "remove_annotation" }
11
+ opts.on('-p', '--position [before|after]', ['before', 'after']) { |p| ENV['position'] = p }
12
+ end.parse!
13
+
14
+ Rake::Task[task].invoke
@@ -0,0 +1,69 @@
1
+ require 'annotate_models/version'
2
+
3
+ AUTHOR = 'Dave Thomas' # can also be an array of Authors
4
+ EMAIL = "ctran@pragmaquest.com"
5
+ DESCRIPTION = "Add a comment summarizing the current schema to the top of each ActiveRecord model source file"
6
+ GEM_NAME = 'annotate-models' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'annotate-models' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = AnnotateModels::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'annotate_models documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.developer(AUTHOR, EMAIL)
52
+ p.description = DESCRIPTION
53
+ p.summary = DESCRIPTION
54
+ p.url = HOMEPATH
55
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
56
+ p.test_globs = ["test/**/test_*.rb"]
57
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
58
+
59
+ # == Optional
60
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
61
+ p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
62
+ p.spec_extras = {} # A hash of extra values to set in the gemspec.
63
+
64
+ end
65
+
66
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
67
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
68
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
69
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'annotate_models'
@@ -0,0 +1,201 @@
1
+ module AnnotateModels
2
+ class << self
3
+ MODEL_DIR = "app/models"
4
+ FIXTURE_DIRS = ["test/fixtures","spec/fixtures"]
5
+ PREFIX = "== Schema Information"
6
+
7
+ # Simple quoting for the default column value
8
+ def quote(value)
9
+ case value
10
+ when NilClass then "NULL"
11
+ when TrueClass then "TRUE"
12
+ when FalseClass then "FALSE"
13
+ when Float, Fixnum, Bignum then value.to_s
14
+ # BigDecimals need to be output in a non-normalized form and quoted.
15
+ when BigDecimal then value.to_s('F')
16
+ else
17
+ value.inspect
18
+ end
19
+ end
20
+
21
+ # Use the column information in an ActiveRecord class
22
+ # to create a comment block containing a line for
23
+ # each column. The line contains the column name,
24
+ # the type (and length), and any optional attributes
25
+ def get_schema_info(klass, header)
26
+ info = "# #{header}\n#\n"
27
+ info << "# Table name: #{klass.table_name}\n#\n"
28
+
29
+ max_size = klass.column_names.collect{|name| name.size}.max + 1
30
+ klass.columns.each do |col|
31
+ attrs = []
32
+ attrs << "default(#{quote(col.default)})" if col.default
33
+ attrs << "not null" unless col.null
34
+ attrs << "primary key" if col.name == klass.primary_key
35
+
36
+ col_type = col.type.to_s
37
+ if col_type == "decimal"
38
+ col_type << "(#{col.precision}, #{col.scale})"
39
+ else
40
+ col_type << "(#{col.limit})" if col.limit
41
+ end
42
+ info << sprintf("# %-#{max_size}.#{max_size}s:%-15.15s %s", col.name, col_type, attrs.join(", ")).rstrip + "\n"
43
+ end
44
+
45
+ info << "#\n\n"
46
+ end
47
+
48
+ # Add a schema block to a file. If the file already contains
49
+ # a schema info block (a comment starting with "== Schema Information"), check if it
50
+ # matches the block that is already there. If so, leave it be. If not, remove the old
51
+ # info block and write a new one.
52
+ # Returns true or false depending on whether the file was modified.
53
+ #
54
+ # === Options (opts)
55
+ # :position<Symbol>:: where to place the annotated section in fixture or model file,
56
+ # "before" or "after". Default is "before".
57
+ # :position_in_class<Symbol>:: where to place the annotated section in model file
58
+ # :position_in_fixture<Symbol>:: where to place the annotated section in fixture file
59
+ #
60
+ def annotate_one_file(file_name, info_block, options={})
61
+ if File.exist?(file_name)
62
+ old_content = File.read(file_name)
63
+
64
+ # Ignore the Schema version line because it changes with each migration
65
+ header = Regexp.new(/(^# Table name:.*?\n(#.*\n)*\n)/)
66
+ old_header = old_content.match(header).to_s
67
+ new_header = info_block.match(header).to_s
68
+
69
+ if old_header == new_header
70
+ false
71
+ else
72
+ # Remove old schema info
73
+ old_content.sub!(/^# #{PREFIX}.*?\n(#.*\n)*\n/, '')
74
+
75
+ # Write it back
76
+ new_content = options[:position] == "after" ? (old_content + "\n" + info_block) : (info_block + old_content)
77
+
78
+ File.open(file_name, "w") { |f| f.puts new_content }
79
+ true
80
+ end
81
+ end
82
+ end
83
+
84
+ def remove_annotation_of_file(file_name)
85
+ if File.exist?(file_name)
86
+ content = File.read(file_name)
87
+
88
+ content.sub!(/^# #{PREFIX}.*?\n(#.*\n)*\n/, '')
89
+
90
+ File.open(file_name, "w") { |f| f.puts content }
91
+ end
92
+ end
93
+
94
+ # Given the name of an ActiveRecord class, create a schema
95
+ # info block (basically a comment containing information
96
+ # on the columns and their types) and put it at the front
97
+ # of the model and fixture source files.
98
+ # Returns true or false depending on whether the source
99
+ # files were modified.
100
+
101
+ def annotate(klass, file, header,options={})
102
+ info = get_schema_info(klass, header)
103
+ annotated = false
104
+
105
+ model_file_name = File.join(MODEL_DIR, file)
106
+ if annotate_one_file(model_file_name, info, options.merge(:position=>(options[:position_in_class] || options[:position])))
107
+ annotated = true
108
+ end
109
+
110
+ FIXTURE_DIRS.each do |dir|
111
+ fixture_file_name = File.join(dir,klass.table_name + ".yml")
112
+ annotate_one_file(fixture_file_name, info, options.merge(:position=>(options[:position_in_fixture] || options[:position]))) if File.exist?(fixture_file_name)
113
+ end
114
+ annotated
115
+ end
116
+
117
+ # Return a list of the model files to annotate. If we have
118
+ # command line arguments, they're assumed to be either
119
+ # the underscore or CamelCase versions of model names.
120
+ # Otherwise we take all the model files in the
121
+ # app/models directory.
122
+ def get_model_files
123
+ models = ARGV.dup
124
+ models.shift
125
+ models.reject!{|m| m.starts_with?("position=")}
126
+ if models.empty?
127
+ Dir.chdir(MODEL_DIR) do
128
+ models = Dir["**/*.rb"]
129
+ end
130
+ end
131
+ models
132
+ end
133
+
134
+ # Retrieve the classes belonging to the model names we're asked to process
135
+ # Check for namespaced models in subdirectories as well as models
136
+ # in subdirectories without namespacing.
137
+ def get_model_class(file)
138
+ model = file.gsub(/\.rb$/, '').camelize
139
+ parts = model.split('::')
140
+ begin
141
+ parts.inject(Object) {|klass, part| klass.const_get(part) }
142
+ rescue LoadError
143
+ Object.const_get(parts.last)
144
+ end
145
+ end
146
+
147
+ # We're passed a name of things that might be
148
+ # ActiveRecord models. If we can find the class, and
149
+ # if its a subclass of ActiveRecord::Base,
150
+ # then pas it to the associated block
151
+ def do_annotations(options={})
152
+ header = PREFIX.dup
153
+ version = ActiveRecord::Migrator.current_version rescue 0
154
+ if version > 0
155
+ header << "\n# Schema version: #{version}"
156
+ end
157
+
158
+ annotated = []
159
+ get_model_files.each do |file|
160
+ begin
161
+ klass = get_model_class(file)
162
+ if klass < ActiveRecord::Base && !klass.abstract_class?
163
+ if annotate(klass, file, header,options)
164
+ annotated << klass
165
+ end
166
+ end
167
+ rescue Exception => e
168
+ puts "Unable to annotate #{file}: #{e.message}"
169
+ end
170
+ end
171
+ if annotated.empty?
172
+ puts "Nothing annotated!"
173
+ else
174
+ puts "Annotated #{annotated.join(', ')}"
175
+ end
176
+ end
177
+
178
+ def remove_annotations
179
+ deannotated = []
180
+ get_model_files.each do |file|
181
+ begin
182
+ klass = get_model_class(file)
183
+ if klass < ActiveRecord::Base && !klass.abstract_class?
184
+ deannotated << klass
185
+
186
+ model_file_name = File.join(MODEL_DIR, file)
187
+ remove_annotation_of_file(model_file_name)
188
+
189
+ FIXTURE_DIRS.each do |dir|
190
+ fixture_file_name = File.join(dir,klass.table_name + ".yml")
191
+ remove_annotation_of_file(fixture_file_name) if File.exist?(fixture_file_name)
192
+ end
193
+ end
194
+ rescue Exception => e
195
+ puts "Unable to annotate #{file}: #{e.message}"
196
+ end
197
+ end
198
+ puts "Removed annotation from: #{deannotated.join(', ')}"
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,2 @@
1
+ load 'Rakefile'
2
+ Dir[File.join(File.dirname(__FILE__), '../tasks', '**/*.rake')].each { |rake| load rake }
@@ -0,0 +1,9 @@
1
+ module AnnotateModels #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 4
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ desc "Add schema information (as comments) to model and fixture files"
2
+ task :annotate_models => :environment do
3
+ require 'annotate_models'
4
+ options={}
5
+ options[:position_in_class] = ENV['position_in_class'] || ENV['position']
6
+ options[:position_in_fixture] = ENV['position_in_fixture'] || ENV['position']
7
+ AnnotateModels.do_annotations(options)
8
+ end
9
+
10
+ desc "Remove schema information from model and fixture files"
11
+ task :remove_annotation => :environment do
12
+ require 'annotate_models'
13
+ AnnotateModels.remove_annotations
14
+ end