seedling 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/AUTHORS +3 -0
  2. data/CHANGELOG +76 -0
  3. data/MANIFEST +42 -0
  4. data/README +28 -0
  5. data/Rakefile +68 -0
  6. data/bin/seedling +15 -0
  7. data/lib/seedling.rb +5 -0
  8. data/lib/seedling/bin.rb +196 -0
  9. data/lib/seedling/extensions/inflector.rb +283 -0
  10. data/lib/seedling/project_creator.rb +147 -0
  11. data/lib/seedling/version.rb +3 -0
  12. data/lib/templates/core/Rakefile.seed +68 -0
  13. data/lib/templates/core/lib/library.rb.seed +5 -0
  14. data/lib/templates/core/lib/library/version.rb.seed +3 -0
  15. data/lib/templates/core/spec/helper.rb.seed +28 -0
  16. data/lib/templates/core/tasks/authors.rake +30 -0
  17. data/lib/templates/core/tasks/bacon.rake +70 -0
  18. data/lib/templates/core/tasks/changelog.rake +18 -0
  19. data/lib/templates/core/tasks/copyright.rake +21 -0
  20. data/lib/templates/core/tasks/gem.rake +23 -0
  21. data/lib/templates/core/tasks/gem_installer.rake +76 -0
  22. data/lib/templates/core/tasks/install_dependencies.rake +6 -0
  23. data/lib/templates/core/tasks/manifest.rake +4 -0
  24. data/lib/templates/core/tasks/rcov.rake +23 -0
  25. data/lib/templates/core/tasks/release.rake +52 -0
  26. data/lib/templates/core/tasks/reversion.rake +8 -0
  27. data/lib/templates/core/tasks/setup.rake.seed +15 -0
  28. data/seedling.gemspec +38 -0
  29. data/spec/helper.rb +28 -0
  30. data/tasks/authors.rake +30 -0
  31. data/tasks/bacon.rake +70 -0
  32. data/tasks/changelog.rake +18 -0
  33. data/tasks/copyright.rake +21 -0
  34. data/tasks/gem.rake +23 -0
  35. data/tasks/gem_installer.rake +76 -0
  36. data/tasks/install_dependencies.rake +6 -0
  37. data/tasks/manifest.rake +4 -0
  38. data/tasks/rcov.rake +23 -0
  39. data/tasks/release.rake +52 -0
  40. data/tasks/reversion.rake +8 -0
  41. data/tasks/setup.rake +15 -0
  42. data/tasks/yard.rake +4 -0
  43. metadata +101 -0
data/AUTHORS ADDED
@@ -0,0 +1,3 @@
1
+ Following persons have contributed to seedling.
2
+ (Sorted by number of submitted patches, then alphabetically)
3
+
@@ -0,0 +1,76 @@
1
+ [db34e4f | Thu May 14 17:30:32 UTC 2009] Kevin Berry <kevin@opensourcealchemist.com>
2
+
3
+ * Correct Rakefile for project name, update manifest, authors.
4
+
5
+ [27deb65 | Thu May 14 15:30:14 UTC 2009] Kevin Berry <kevin@opensourcealchemist.com>
6
+
7
+ * Version 0.0.1
8
+
9
+ [4017044 | Thu May 14 14:50:26 UTC 2009] Kevin Berry <kevinberry@nrs.us>
10
+
11
+ * Remove old library dir, and correct erb output for library.rb.seed
12
+
13
+ [04d16e0 | Thu May 14 14:45:02 UTC 2009] Kevin Berry <kevinberry@nrs.us>
14
+
15
+ * Get the library module and children in the right place. TODO: Remove library directory from plant.
16
+
17
+ [25e0453 | Thu May 14 14:09:29 UTC 2009] Kevin Berry <kevinberry@nrs.us>
18
+
19
+ * Fix erb calls (Railsism doesn't work!)
20
+
21
+ [f8eeba5 | Wed May 13 18:55:39 UTC 2009] Kevin Berry <kevinberry@nrs.us>
22
+
23
+ * Add params for doc_generator, test_suite, correct email parameter.
24
+
25
+ [ead7fbf | Wed May 13 18:22:26 UTC 2009] Kevin Berry <kevinberry@nrs.us>
26
+
27
+ * Add rubyforge_project commandline option.
28
+
29
+ [fb7663b | Wed May 13 17:34:18 UTC 2009] Kevin Berry <kevinberry@nrs.us>
30
+
31
+ * Fix a couple of templates, and add more planting options.
32
+
33
+ [eb1d0d0 | Wed May 13 15:40:01 UTC 2009] Kevin Berry <kevinberry@nrs.us>
34
+
35
+ * Convert setup into a seed (erb) so that bacon/rspec and rdoc/yard choices can be made.
36
+
37
+ [771f9d3 | Tue May 12 23:12:42 UTC 2009] Kevin Berry <KevinBerry@nrs.us>
38
+
39
+ * rest of erb to seed conversion.
40
+
41
+ [72aa1b4 | Tue May 12 23:06:40 UTC 2009] Kevin Berry <KevinBerry@nrs.us>
42
+
43
+ * Adjust library.rb.erb to library.rb.seed, and adjust template data to match new standards.
44
+
45
+ [2f71f19 | Tue May 12 22:50:37 UTC 2009] TJ Vanderpoel <bougy.man@gmail.com>
46
+
47
+ * changed extension for templates to be parsed with erb to .seed to remove conflicts with actual .erb files. added most options to Rakefile.seed. TODO: finish out Rakefile.seed and move on to more .seed files where dynamic values are needed
48
+
49
+ [aff3223 | Mon May 11 19:41:52 UTC 2009] TJ Vanderpoel <bougyman@zero.(none)>
50
+
51
+ * added bin/seedling and began forming bin.rb to seedling usage
52
+
53
+ [6765184 | Sun May 10 22:32:16 UTC 2009] Kevin Berry <Kevin Berry>
54
+
55
+ * Modify Rakefile.erb to get some project info, need to get ProjectCreator to feed the right info.
56
+
57
+ [5917d53 | Sun May 10 03:42:26 UTC 2009] Kevin Berry <Kevin Berry>
58
+
59
+ * Make plant work.
60
+
61
+ [f02eaf1 | Sun May 10 03:06:22 UTC 2009] Kevin Berry <Kevin Berry>
62
+
63
+ * Bring in initial generator and ProjectCreator kit (stripped down bougyman's autumn library.)
64
+
65
+ [22fb2a5 | Fri May 08 03:19:21 UTC 2009] TJ Vanderpoel <bougy.man@gmail.com>
66
+
67
+ * changed env variable for versioning to VERSION
68
+
69
+ [6329710 | Fri May 08 03:07:50 UTC 2009] TJ Vanderpoel <bougy.man@gmail.com>
70
+
71
+ * added yard task and a README, plus a .gitignore
72
+
73
+ [41fd3c5 | Thu May 07 19:21:22 UTC 2009] TJ Vanderpoel <bougy.man@gmail.com>
74
+
75
+ * new tree to begin seedling work
76
+
@@ -0,0 +1,42 @@
1
+ AUTHORS
2
+ CHANGELOG
3
+ MANIFEST
4
+ README
5
+ Rakefile
6
+ bin/seedling
7
+ lib/seedling.rb
8
+ lib/seedling/bin.rb
9
+ lib/seedling/extensions/inflector.rb
10
+ lib/seedling/project_creator.rb
11
+ lib/seedling/version.rb
12
+ lib/templates/core/Rakefile.seed
13
+ lib/templates/core/lib/library.rb.seed
14
+ lib/templates/core/lib/library/version.rb.seed
15
+ lib/templates/core/spec/helper.rb.seed
16
+ lib/templates/core/tasks/authors.rake
17
+ lib/templates/core/tasks/bacon.rake
18
+ lib/templates/core/tasks/changelog.rake
19
+ lib/templates/core/tasks/copyright.rake
20
+ lib/templates/core/tasks/gem.rake
21
+ lib/templates/core/tasks/gem_installer.rake
22
+ lib/templates/core/tasks/install_dependencies.rake
23
+ lib/templates/core/tasks/manifest.rake
24
+ lib/templates/core/tasks/rcov.rake
25
+ lib/templates/core/tasks/release.rake
26
+ lib/templates/core/tasks/reversion.rake
27
+ lib/templates/core/tasks/setup.rake.seed
28
+ seedling.gemspec
29
+ spec/helper.rb
30
+ tasks/authors.rake
31
+ tasks/bacon.rake
32
+ tasks/changelog.rake
33
+ tasks/copyright.rake
34
+ tasks/gem.rake
35
+ tasks/gem_installer.rake
36
+ tasks/install_dependencies.rake
37
+ tasks/manifest.rake
38
+ tasks/rcov.rake
39
+ tasks/release.rake
40
+ tasks/reversion.rake
41
+ tasks/setup.rake
42
+ tasks/yard.rake
data/README ADDED
@@ -0,0 +1,28 @@
1
+ ==Seedling
2
+ ----------
3
+
4
+ Seedling is a tool for creating ruby projects. It aims to add
5
+ tasks (via a Rakefile and some tasks/*.rake) which allow for quick
6
+ and easy publishing of your new ruby library, via github and or rubyforge.
7
+ It automates some of the monotony such as
8
+
9
+ * Re-Versioning
10
+ * AUTHORS updating
11
+ * .gemspec updates
12
+ * building a gem, tarball, zip, source export tree
13
+ * Test (spec) running, via a bacon task and bacon spec helper (we love bacon)
14
+ * Documentation via yard
15
+ * CHANGELOG updating
16
+
17
+ == Download
18
+
19
+ using git
20
+ git clone git://rubyists.com/gits/seedling
21
+
22
+ ==Usage
23
+ -------
24
+
25
+ We're getting there. Will be
26
+ seedling plant /path/to/tree
27
+
28
+ Copyright(c) 2009 The Rubyists
@@ -0,0 +1,68 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/gempackagetask'
6
+ require 'time'
7
+ require 'date'
8
+ require "lib/seedling"
9
+
10
+ PROJECT_SPECS = FileList[
11
+ 'spec/**/*.rb'
12
+ ]
13
+
14
+ PROJECT_MODULE = 'Seedling'
15
+ PROJECT_README = 'README'
16
+ PROJECT_RUBYFORGE_GROUP_ID = 4525
17
+ PROJECT_COPYRIGHT = [
18
+ "# Copyright (c) #{Time.now.year} The Rubyists rubyists@rubyists.com",
19
+ "# Distributed under the terms of the MIT license."
20
+ ]
21
+
22
+ # To release the monthly version do:
23
+ # $ PROJECT_VERSION=2009.03 rake release
24
+ IGNORE_FILES = [/\.gitignore/]
25
+
26
+ GEMSPEC = Gem::Specification.new{|s|
27
+ s.name = 'seedling'
28
+ s.author = "Kevin Berry"
29
+ s.summary = "A lightweight tool to create new ruby library trees, with helpers to make gemming and maintaining a breeze."
30
+ s.description = s.summary
31
+ s.email = 'deathsyn@gmail.com'
32
+ s.homepage = 'http://github.com/deathsyn/seedling'
33
+ s.platform = Gem::Platform::RUBY
34
+ s.version = (ENV['VERSION'] || (begin;Object.const_get(PROJECT_MODULE)::VERSION;rescue;Date.today.strftime("%Y.%m.%d");end))
35
+ s.files = `git ls-files`.split("\n").sort.reject { |f| IGNORE_FILES.detect { |exp| f.match(exp) } }
36
+ s.has_rdoc = true
37
+ s.require_path = 'lib'
38
+ s.bindir = "bin"
39
+ s.executables = ["seedling"]
40
+ s.rubyforge_project = "seedling"
41
+
42
+ s.post_install_message = <<MESSAGE.strip
43
+ ============================================================
44
+
45
+ Thank you for installing Seedling!
46
+ Begin by planting your ruby project with
47
+ # seedling plant /path/to/new/project
48
+
49
+ ============================================================
50
+ MESSAGE
51
+ }
52
+
53
+ Dir['tasks/*.rake'].each{|f| import(f) }
54
+
55
+ task :default => [:bacon]
56
+
57
+ CLEAN.include %w[
58
+ **/.*.sw?
59
+ *.gem
60
+ .config
61
+ **/*~
62
+ **/{data.db,cache.yaml}
63
+ *.yaml
64
+ pkg
65
+ rdoc
66
+ ydoc
67
+ *coverage*
68
+ ]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require "pathname"
4
+ local_seedling = Pathname.new(__FILE__).expand_path.dirname.join("..", "lib", "seedling.rb")
5
+ if local_seedling.file?
6
+ require local_seedling
7
+ else
8
+ require "seedling"
9
+ end
10
+ require "seedling/bin"
11
+ rescue LoadError
12
+ require "rubygems"
13
+ require "seedling/bin"
14
+ end
15
+ Seedling::Bin::Cmd.run(ARGV)
@@ -0,0 +1,5 @@
1
+ require "pathname"
2
+ $LOAD_PATH.unshift(Pathname.new(__FILE__).dirname.expand_path.to_s)
3
+ module Seedling
4
+ end
5
+ require "seedling/version"
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require "seedling/extensions/inflector"
4
+ ### This module offers the functionality to create.
5
+ module Seedling
6
+ module Bin
7
+ class Cmd # This class contains the command methods {{{
8
+ #include Helpers
9
+ attr_accessor :command
10
+
11
+ def initialize(args = nil)
12
+ args ||= ARGV
13
+ raise "arguments must be an array!" unless args.respond_to?(:detect)
14
+ @ourargs = args.dup
15
+ @command = args.detect { |arg| arg.match(/^(?:--?)?(?:plant|create|h(?:elp)?|v(?:ersion)?|console)/) }
16
+ if command.nil?
17
+ @command = ""
18
+ else
19
+ args.delete(@command)
20
+ end
21
+ ARGV.replace(args)
22
+ end
23
+
24
+ # {{{ #run is called when we're interactive ($0 == __FILE__)
25
+ def self.run(args = nil)
26
+ cmd = new(args)
27
+ case cmd.command
28
+ when /^(?:--?)?(?:plant|create)$/
29
+ require "seedling/project_creator"
30
+ cmd.plant(cmd.command)
31
+ when /^(?:--?)?console$/
32
+ require "irb"
33
+ require "irb/completion"
34
+ cmd.include_seedling
35
+ IRB.start
36
+ puts "Seedling session has ended."
37
+ when /^(?:--?)?h(elp)?$/
38
+ puts cmd.usage
39
+ when /^(?:--?)?v(ersion)?$/
40
+ cmd.include_seedling
41
+ puts Seedling::VERSION
42
+ exit
43
+ when /^$/
44
+ puts "Must supply a valid command"
45
+ puts cmd.usage
46
+ exit 1
47
+ else
48
+ puts "#{command} not implemented"
49
+ puts cmd.usage
50
+ exit 1
51
+ end
52
+ end # }}}
53
+
54
+ def include_seedling # {{{
55
+ begin
56
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '/../lib')
57
+ require 'seedling'
58
+ rescue LoadError
59
+ $LOAD_PATH.shift
60
+
61
+ begin
62
+ require 'rubygems'
63
+ rescue LoadError
64
+ end
65
+ require 'seedling'
66
+ end
67
+ end # }}}
68
+
69
+ def usage # {{{
70
+ txt = [
71
+ "\n Usage:",
72
+ "seedling <plant|create|console> PROJECT [options]\n",
73
+ "Commands:\n",
74
+ " plant - Creates a new prototype Seedling application in a directory named PROJECT in",
75
+ " the current directory. seedling create foo would make ./foo containing a",
76
+ " seedling prototype.\n",
77
+ " create - Synonymous with plant.\n",
78
+ " console - Starts an irb console with seedling (and irb completion) loaded.",
79
+ " ARGV is passed on to IRB.\n\n"
80
+ ].join("\n\t")
81
+
82
+ txt << "* All commands take PROJECT as the directory the seedling lives in.\n\n"
83
+ txt << plant_options.to_s << "\n"
84
+ #if is_windows?
85
+ #txt << %x{ruby #{rackup_path} --help}.split("\n").reject { |line| line.match(/^Usage:/) }.join("\n\t")
86
+ #else
87
+ #txt << %x{#{rackup_path} --help}.split("\n").reject { |line| line.match(/^Usage:/) }.join("\n\t")
88
+ #end
89
+ end # }}}
90
+
91
+ def al_root
92
+ require "pathname"
93
+ dir = nil
94
+ if ARGV.size == 1
95
+ dir = Pathname.new(ARGV.shift)
96
+ elsif ARGV.size > 1
97
+ $stderr.puts "Unknown options given #{ARGV.join(" ")}"
98
+ puts usage
99
+ exit 1
100
+ end
101
+ if dir.nil? or not dir.directory?
102
+ dir = Pathname.new(ENV["PWD"]).expand_path
103
+ $stderr.puts "Path to seedling tree not given or invalid, using #{dir}"
104
+ end
105
+ Object.const_set("AL_ROOT", dir.expand_path.to_s)
106
+ Dir.chdir(AL_ROOT)
107
+ end
108
+
109
+ ### Methods for commands {{{
110
+ def plant_options(opts = {})
111
+ @plant_opts ||= OptionParser.new do |o|
112
+ o.banner = "Planting Options"
113
+ o.on("-nSUMMARY", "--summary SUMMARY", "Short description of this project") { |yn| opts[:summary] = yn }
114
+ o.on("-dDESCRIPTION", "--description DESCRIPTION", "Longer description (Default: summary)") { |des| opts[:description] = des }
115
+ o.on("-lLIBNAME", "--libname LIBNAME", "Library name (Default: path specifcation)") { |libname| opts[:lib_name] = libname }
116
+ o.on("-gDOCGENERATOR", "--doc-generator DOCGENERATOR", "Preferred documentation generator (Default: yard)") { |docgenerator| opts[:doc_generator] = docgenerator }
117
+ o.on("-tTESTSUITE", "--test-suite TESTSUITE", "Preferred test suite (Default: bacon)") { |testsuite| opts[:test_suite] = testsuite }
118
+ o.on("-vVER", "--version VER", "Initial version number (Default: 0.0.1)") { |ver| opts[:version] = ver }
119
+ o.on("-rRUBYFORGE", "--rubyforge RUBYFORGE", "Rubyforge project name") { |rubyforge| opts[:rubyforge_project] = rubyforge }
120
+
121
+ o.separator ""
122
+ o.separator "Author Options"
123
+ o.on("-sAUTHOR", "--summary AUTHOR", "Author's Name") { |yn| opts[:author_name] = yn }
124
+ o.on("-eEMAIL", "--email EMAIL", "Author's Email") { |yn| opts[:author_email] = yn }
125
+ o.on("-uURL", "--url URL", "Project URL/homepage") { |url| opts[:project_url] = url }
126
+
127
+ o.separator ""
128
+ o.separator "Directory Creation Options"
129
+ o.on("-f", "--force", "Force creation if dir already exists") { |yn| opts[:force] = true }
130
+ o.on("-a", "--amend", "Update a tree") { |yn| opts[:amend] = true }
131
+ end
132
+ end
133
+
134
+ def plant(command) # {{{
135
+ plant_options(o = {}).parse!(ARGV)
136
+ unless ARGV.size == 1
137
+ $stderr.puts "Invalid options given: #{ARGV.join(" ")}"
138
+ exit 1
139
+ end
140
+ project_root = ARGV.shift
141
+ if project_root.nil?
142
+ $stderr.puts "Must supply a valid directory to install your project, you gave none."
143
+ puts usage
144
+ exit 1
145
+ end
146
+ o[:lib_name] ||= Pathname.new(project_root).basename.to_s.classify
147
+ o[:lib_name_u] ||= o[:lib_name].underscore
148
+ opts = plant_defaults(o)
149
+ # need to titleize this
150
+ include_seedling
151
+ Seedling::ProjectCreator.new(project_root, opts).create
152
+ end # }}}
153
+
154
+ private
155
+
156
+ # Sets all of our default settings to make a sane rakefile, pulling from everywhere that makes sense
157
+ def plant_defaults(o = {:lib_name => "Seedling"})
158
+ # this shouldn't happen, but if so let's be descriptive
159
+ raise "plant_defaults requires a :lib_name in the calling argument" unless o[:lib_name]
160
+ o[:lib_name_u] = o[:lib_name].underscore
161
+ [:author_name, :author_email].each do |opt|
162
+ o[opt] = self.send(opt)
163
+ end
164
+ o[:summary] ||= "The #{o[:lib_name].classify.titleize} library, by #{o[:author_name]}"
165
+ o[:description] ||= o[:summary]
166
+ o[:version] ||= "0.0.1"
167
+ o[:test_suite] ||= "bacon"
168
+ o[:doc_generator] ||= "yard"
169
+ o
170
+ end
171
+
172
+ def author_email
173
+ gitted = %x{git config --global --get user.email}
174
+ return gitted.to_s.strip if gitted.to_s.match(/\w/)
175
+ return ENV["EMAIL"] if ENV["EMAIL"]
176
+ return [ENV["LOGUSER"], ENV["HOSTNAME"]].join("@") if ENV["LOGUSER"] and ENV["HOSTNAME"]
177
+ raise "Cannot find author email, please use --email \"you@your.domain.com\" or set the EMAIL environment variable"
178
+ end
179
+
180
+ def author_name
181
+ gitted = %x{git config --global --get user.name}
182
+ return gitted.to_s.strip if gitted.to_s.match(/\w/)
183
+ return ENV["NAME"] if ENV["NAME"]
184
+ return ENV["LOGUSER"] if ENV["LOGUSER"]
185
+ return Pathname.new(ENV["HOME"]).expand_path.basename.to_s if ENV["HOME"]
186
+ raise "Cannot find author name, please use --author \"Your Name\" or set the NAME or LOGUSER environment variables"
187
+ end
188
+
189
+ ### End of command methods }}}
190
+ end # }}}
191
+ end
192
+ end
193
+
194
+ if $0 == __FILE__
195
+ Seedling::Bin::Cmd.run(ARGV)
196
+ end
@@ -0,0 +1,283 @@
1
+ # Add inflection methods to String, which allows the easy transformation of
2
+ # words from singular to plural,class names to table names, modularized class
3
+ # names to ones without, and class names to foreign keys.
4
+
5
+ class String
6
+ # This module acts as a singleton returned/yielded by String.inflections,
7
+ # which is used to override or specify additional inflection rules. Examples:
8
+ #
9
+ # String.inflections do |inflect|
10
+ # inflect.plural /^(ox)$/i, '\1\2en'
11
+ # inflect.singular /^(ox)en/i, '\1'
12
+ #
13
+ # inflect.irregular 'octopus', 'octopi'
14
+ #
15
+ # inflect.uncountable "equipment"
16
+ # end
17
+ #
18
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
19
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
20
+ # already have been loaded.
21
+ module Inflections
22
+ @plurals, @singulars, @uncountables = [], [], []
23
+
24
+ class << self
25
+ attr_reader :plurals, :singulars, :uncountables
26
+ end
27
+
28
+ # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
29
+ # the options are: :plurals, :singulars, :uncountables
30
+ #
31
+ # Examples:
32
+ # clear :all
33
+ # clear :plurals
34
+ def self.clear(scope = :all)
35
+ case scope
36
+ when :all
37
+ @plurals, @singulars, @uncountables = [], [], []
38
+ else
39
+ instance_variable_set("@#{scope}", [])
40
+ end
41
+ end
42
+
43
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
44
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
45
+ #
46
+ # Examples:
47
+ # irregular 'octopus', 'octopi'
48
+ # irregular 'person', 'people'
49
+ def self.irregular(singular, plural)
50
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
51
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
52
+ end
53
+
54
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
55
+ # The replacement should always be a string that may include references to the matched data from the rule.
56
+ #
57
+ # Example:
58
+ # plural(/(x|ch|ss|sh)$/i, '\1es')
59
+ def self.plural(rule, replacement)
60
+ @plurals.insert(0, [rule, replacement])
61
+ end
62
+
63
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
64
+ # The replacement should always be a string that may include references to the matched data from the rule.
65
+ #
66
+ # Example:
67
+ # singular(/([^aeiouy]|qu)ies$/i, '\1y')
68
+ def self.singular(rule, replacement)
69
+ @singulars.insert(0, [rule, replacement])
70
+ end
71
+
72
+ # Add uncountable words that shouldn't be attempted inflected.
73
+ #
74
+ # Examples:
75
+ # uncountable "money"
76
+ # uncountable "money", "information"
77
+ # uncountable %w( money information rice )
78
+ def self.uncountable(*words)
79
+ (@uncountables << words).flatten!
80
+ end
81
+
82
+ # Setup the default inflections
83
+ plural(/$/, 's')
84
+ plural(/s$/i, 's')
85
+ plural(/(ax|test)is$/i, '\1es')
86
+ plural(/(octop|vir)us$/i, '\1i')
87
+ plural(/(alias|status)$/i, '\1es')
88
+ plural(/(bu)s$/i, '\1ses')
89
+ plural(/(buffal|tomat)o$/i, '\1oes')
90
+ plural(/([ti])um$/i, '\1a')
91
+ plural(/sis$/i, 'ses')
92
+ plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
93
+ plural(/(hive)$/i, '\1s')
94
+ plural(/([^aeiouy]|qu)y$/i, '\1ies')
95
+ plural(/(x|ch|ss|sh)$/i, '\1es')
96
+ plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
97
+ plural(/([m|l])ouse$/i, '\1ice')
98
+ plural(/^(ox)$/i, '\1en')
99
+ plural(/(quiz)$/i, '\1zes')
100
+
101
+ singular(/s$/i, '')
102
+ singular(/(n)ews$/i, '\1ews')
103
+ singular(/([ti])a$/i, '\1um')
104
+ singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
105
+ singular(/(^analy)ses$/i, '\1sis')
106
+ singular(/([^f])ves$/i, '\1fe')
107
+ singular(/(hive)s$/i, '\1')
108
+ singular(/(tive)s$/i, '\1')
109
+ singular(/([lr])ves$/i, '\1f')
110
+ singular(/([^aeiouy]|qu)ies$/i, '\1y')
111
+ singular(/(s)eries$/i, '\1eries')
112
+ singular(/(m)ovies$/i, '\1ovie')
113
+ singular(/(x|ch|ss|sh)es$/i, '\1')
114
+ singular(/([m|l])ice$/i, '\1ouse')
115
+ singular(/(bus)es$/i, '\1')
116
+ singular(/(o)es$/i, '\1')
117
+ singular(/(shoe)s$/i, '\1')
118
+ singular(/(cris|ax|test)es$/i, '\1is')
119
+ singular(/(octop|vir)i$/i, '\1us')
120
+ singular(/(alias|status)es$/i, '\1')
121
+ singular(/^(ox)en/i, '\1')
122
+ singular(/(vert|ind)ices$/i, '\1ex')
123
+ singular(/(matr)ices$/i, '\1ix')
124
+ singular(/(quiz)zes$/i, '\1')
125
+
126
+ irregular('person', 'people')
127
+ irregular('man', 'men')
128
+ irregular('child', 'children')
129
+ irregular('sex', 'sexes')
130
+ irregular('move', 'moves')
131
+
132
+ uncountable(%w(equipment information rice money species series fish sheep))
133
+ end
134
+
135
+ # Yield the Inflections module if a block is given, and return
136
+ # the Inflections module.
137
+ def self.inflections
138
+ yield Inflections if block_given?
139
+ Inflections
140
+ end
141
+
142
+ # By default, camelize converts the string to UpperCamelCase. If the argument to camelize
143
+ # is set to :lower then camelize produces lowerCamelCase.
144
+ #
145
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
146
+ #
147
+ # Examples
148
+ # "active_record".camelize #=> "ActiveRecord"
149
+ # "active_record".camelize(:lower) #=> "activeRecord"
150
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
151
+ # "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
152
+ def camelize(first_letter_in_uppercase = :upper)
153
+ s = gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
154
+ s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
155
+ s
156
+ end
157
+ alias_method :camelcase, :camelize
158
+
159
+ # Singularizes and camelizes the string. Also strips out all characters preceding
160
+ # and including a period (".").
161
+ #
162
+ # Examples
163
+ # "egg_and_hams".classify #=> "EggAndHam"
164
+ # "post".classify #=> "Post"
165
+ # "schema.post".classify #=> "Post"
166
+ def classify
167
+ sub(/.*\./, '').singularize.camelize
168
+ end
169
+
170
+ # Constantize tries to find a declared constant with the name specified
171
+ # in the string. It raises a NameError when the name is not in CamelCase
172
+ # or is not initialized.
173
+ #
174
+ # Examples
175
+ # "Module".constantize #=> Module
176
+ # "Class".constantize #=> Class
177
+ def constantize
178
+ raise(NameError, "#{inspect} is not a valid constant name!") unless m = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.match(self)
179
+ Object.module_eval("::#{m[1]}", __FILE__, __LINE__)
180
+ end
181
+
182
+ # Replaces underscores with dashes in the string.
183
+ #
184
+ # Example
185
+ # "puni_puni".dasherize #=> "puni-puni"
186
+ def dasherize
187
+ gsub(/_/, '-')
188
+ end
189
+
190
+ # Removes the module part from the expression in the string
191
+ #
192
+ # Examples
193
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
194
+ # "Inflections".demodulize #=> "Inflections"
195
+ def demodulize
196
+ gsub(/^.*::/, '')
197
+ end
198
+
199
+ # Creates a foreign key name from a class name.
200
+ # +use_underscore+ sets whether the method should put '_' between the name and 'id'.
201
+ #
202
+ # Examples
203
+ # "Message".foreign_key #=> "message_id"
204
+ # "Message".foreign_key(false) #=> "messageid"
205
+ # "Admin::Post".foreign_key #=> "post_id"
206
+ def foreign_key(use_underscore = true)
207
+ "#{demodulize.underscore}#{'_' if use_underscore}id"
208
+ end
209
+
210
+ # Capitalizes the first word and turns underscores into spaces and strips _id.
211
+ # Like titleize, this is meant for creating pretty output.
212
+ #
213
+ # Examples
214
+ # "employee_salary" #=> "Employee salary"
215
+ # "author_id" #=> "Author"
216
+ def humanize
217
+ gsub(/_id$/, "").gsub(/_/, " ").capitalize
218
+ end
219
+
220
+ # Returns the plural form of the word in the string.
221
+ #
222
+ # Examples
223
+ # "post".pluralize #=> "posts"
224
+ # "octopus".pluralize #=> "octopi"
225
+ # "sheep".pluralize #=> "sheep"
226
+ # "words".pluralize #=> "words"
227
+ # "the blue mailman".pluralize #=> "the blue mailmen"
228
+ # "CamelOctopus".pluralize #=> "CamelOctopi"
229
+ def pluralize
230
+ result = dup
231
+ Inflections.plurals.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(downcase)
232
+ result
233
+ end
234
+
235
+ # The reverse of pluralize, returns the singular form of a word in a string.
236
+ #
237
+ # Examples
238
+ # "posts".singularize #=> "post"
239
+ # "octopi".singularize #=> "octopus"
240
+ # "sheep".singluarize #=> "sheep"
241
+ # "word".singluarize #=> "word"
242
+ # "the blue mailmen".singularize #=> "the blue mailman"
243
+ # "CamelOctopi".singularize #=> "CamelOctopus"
244
+ def singularize
245
+ result = dup
246
+ Inflections.singulars.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(downcase)
247
+ result
248
+ end
249
+
250
+ # Underscores and pluralizes the string.
251
+ #
252
+ # Examples
253
+ # "RawScaledScorer".tableize #=> "raw_scaled_scorers"
254
+ # "egg_and_ham".tableize #=> "egg_and_hams"
255
+ # "fancyCategory".tableize #=> "fancy_categories"
256
+ def tableize
257
+ underscore.pluralize
258
+ end
259
+
260
+ # Capitalizes all the words and replaces some characters in the string to create
261
+ # a nicer looking title. Titleize is meant for creating pretty output.
262
+ #
263
+ # titleize is also aliased as as titlecase
264
+ #
265
+ # Examples
266
+ # "man from the boondocks".titleize #=> "Man From The Boondocks"
267
+ # "x-men: the last stand".titleize #=> "X Men: The Last Stand"
268
+ def titleize
269
+ underscore.humanize.gsub(/\b([a-z])/){|x| x[-1..-1].upcase}
270
+ end
271
+ alias_method :titlecase, :titleize
272
+
273
+ # The reverse of camelize. Makes an underscored form from the expression in the string.
274
+ # Also changes '::' to '/' to convert namespaces to paths.
275
+ #
276
+ # Examples
277
+ # "ActiveRecord".underscore #=> "active_record"
278
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
279
+ def underscore
280
+ gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
281
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
282
+ end
283
+ end