dry-inflector-0.2.0/0000755000175000017500000000000013617274703014230 5ustar utkarshutkarshdry-inflector-0.2.0/CHANGELOG.md0000644000175000017500000000332713617274703016046 0ustar utkarshutkarsh# dry-inflector Inflector for Ruby ## v0.2.0 - 2019-10-13 ### Added - [Abinoam P. Marques Jr. & Andrii Savchenko] Introduced `Dry::Inflector#upper_camelize` and `Dry::Inflector#lower_camelize`. `Dry::Inflector#camelize` is now an alias for `Dry::Inflector#upper_camelize` and may be deprecated later. ```ruby inflector.camelize_upper("data_mapper") # => "DataMapper" inflector.camelize_lower("data_mapper") # => "dataMapper" ``` ### Fixed - [ecnal] Fixed singularization rules for words like "alias" or "status" [Compare v0.1.2...v0.2.0](https://github.com/dry-rb/dry-inflector/compare/v0.1.2...v0.2.0) ## v0.1.2 - 2018-04-25 ### Added - [Gustavo Caso & Nikita Shilnikov] Added support for acronyms [Compare v0.1.1...v0.1.2](https://github.com/dry-rb/dry-inflector/compare/v0.1.1...v0.1.2) ## v0.1.1 - 2017-11-18 ### Fixed - [Luca Guidi & Abinoam P. Marques Jr.] Ensure `Dry::Inflector#ordinalize` to work for all the numbers from 0 to 100 [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-inflector/compare/v0.1.0...v0.1.1) ## v0.1.0 - 2017-11-17 ### Added - [Luca Guidi] Introduced `Dry::Inflector#pluralize` - [Luca Guidi] Introduced `Dry::Inflector#singularize` - [Luca Guidi] Introduced `Dry::Inflector#camelize` - [Luca Guidi] Introduced `Dry::Inflector#classify` - [Luca Guidi] Introduced `Dry::Inflector#tableize` - [Luca Guidi] Introduced `Dry::Inflector#dasherize` - [Luca Guidi] Introduced `Dry::Inflector#underscore` - [Luca Guidi] Introduced `Dry::Inflector#demodulize` - [Luca Guidi] Introduced `Dry::Inflector#humanize` - [Luca Guidi] Introduced `Dry::Inflector#ordinalize` - [Abinoam P. Marques Jr.] Introduced `Dry::Inflector#foreign_key` - [Abinoam P. Marques Jr.] Introduced `Dry::Inflector#constantize` dry-inflector-0.2.0/dry-inflector.gemspec0000644000175000017500000000300613617274703020355 0ustar utkarshutkarsh # frozen_string_literal: true lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "dry/inflector/version" Gem::Specification.new do |spec| spec.name = "dry-inflector" spec.version = Dry::Inflector::VERSION spec.authors = ["Luca Guidi", "Andrii Savchenko", "Abinoam P. Marques Jr."] spec.email = ["me@lucaguidi.com", "andrey@aejis.eu", "abinoam@gmail.com"] spec.summary = "DRY Inflector" spec.description = "String inflections for dry-rb" spec.homepage = "https://dry-rb.org" spec.license = "MIT" spec.metadata["allowed_push_host"] = "https://rubygems.org" spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-inflector/blob/master/CHANGELOG.md" spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-inflector" spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-inflector/issues" spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md dry-inflector.gemspec`.split($INPUT_RECORD_SEPARATOR) spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.required_ruby_version = '>= 2.4' spec.add_development_dependency "bundler" spec.add_development_dependency "rake", "~> 12.0" spec.add_development_dependency "rspec", "~> 3.7" spec.add_development_dependency "rubocop", "~> 0.50.0" end dry-inflector-0.2.0/README.md0000644000175000017500000000733313617274703015515 0ustar utkarshutkarsh[gem]: https://img.shields.io/gem/v/dry-inflector.svg [travis]: https://travis-ci.org/dry-rb/dry-inflector [codeclimate]: https://codeclimate.com/github/dry-rb/dry-inflector [chat]: https://dry-rb.zulipchat.com [inchpages]: http://inch-ci.org/github/dry-rb/dry-inflector # dry-inflector [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat] [![Gem Version](https://badge.fury.io/rb/dry-inflector.svg)][gem] [![Build Status](https://travis-ci.org/dry-rb/dry-inflector.svg?branch=master)][travis] [![Code Climate](https://codeclimate.com/github/dry-rb/dry-inflector/badges/gpa.svg)][codeclimate] [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-inflector/badges/coverage.svg)][codeclimate] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-inflector.svg?branch=master)][inchpages] dry-inflector is an inflector gem for Ruby. ## Installation Add this line to your application's `Gemfile`: ```ruby gem 'dry-inflector' ``` And then execute: ```shell $ bundle ``` Or install it yourself as: ```shell $ gem install dry-inflector ``` ## Usage ### Basic usage ```ruby require "dry/inflector" inflector = Dry::Inflector.new inflector.pluralize("book") # => "books" inflector.singularize("books") # => "book" inflector.camelize("dry/inflector") # => "Dry::Inflector" inflector.classify("books") # => "Book" inflector.tableize("Book") # => "books" inflector.dasherize("dry_inflector") # => "dry-inflector" inflector.underscore("dry-inflector") # => "dry_inflector" inflector.demodulize("Dry::Inflector") # => "Inflector" inflector.humanize("dry_inflector") # => "Dry inflector" inflector.humanize("author_id") # => "Author" inflector.ordinalize(1) # => "1st" inflector.ordinalize(2) # => "2nd" inflector.ordinalize(3) # => "3rd" inflector.ordinalize(10) # => "10th" inflector.ordinalize(23) # => "23rd" ``` ### Custom inflection rules ```ruby require "dry/inflector" inflector = Dry::Inflector.new do |inflections| inflections.plural "virus", "viruses" # specify a rule for #pluralize inflections.singular "thieves", "thief" # specify a rule for #singularize inflections.uncountable "dry-inflector" # add an exception for an uncountable word end inflector.pluralize("virus") # => "viruses" inflector.singularize("thieves") # => "thief" inflector.pluralize("dry-inflector") # => "dry-inflector" ``` ## Credits This gem is the cumulative effort of the Ruby community. It started with the extlib inflecto originated from [active_support](https://github.com/rails/rails), then dm-core inflector originated from [extlib](https://github.com/datamapper/extlib). Later, [`inflecto`](https://github.com/mbj/inflecto) was extracted from [dm-core](https://github.com/datamapper/dm-core) as a standalone inflector. Now, we resurrect `inflecto` and merged [`flexus`](https://github.com/Ptico/flexus), with some inflection rules from [`hanami-utils`](https://github.com/hanami/utils). This is `dry-inflector`. ## Development After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-inflector. ## Copyright Copyright © The Dry, Rails, Merb, Datamapper, Inflecto, Flexus, and Hanami teams - Released under the MIT License dry-inflector-0.2.0/LICENSE.md0000644000175000017500000000213713617274703015637 0ustar utkarshutkarshCopyright © The Dry, Rails, Merb, Datamapper, Inflecto, Flexus, and Hanami teams MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dry-inflector-0.2.0/lib/0000755000175000017500000000000013617274703014776 5ustar utkarshutkarshdry-inflector-0.2.0/lib/dry-inflector.rb0000644000175000017500000000003013617274703020075 0ustar utkarshutkarshrequire 'dry/inflector' dry-inflector-0.2.0/lib/dry/0000755000175000017500000000000013617274703015574 5ustar utkarshutkarshdry-inflector-0.2.0/lib/dry/inflector/0000755000175000017500000000000013617274703017561 5ustar utkarshutkarshdry-inflector-0.2.0/lib/dry/inflector/version.rb0000644000175000017500000000015713617274703021576 0ustar utkarshutkarsh# frozen_string_literal: true module Dry class Inflector # @since 0.1.0 VERSION = "0.2.0" end end dry-inflector-0.2.0/lib/dry/inflector/inflections.rb0000644000175000017500000001545013617274703022430 0ustar utkarshutkarsh# frozen_string_literal: true require "set" require "dry/inflector/rules" require "dry/inflector/acronyms" module Dry class Inflector # Inflections # # @since 0.1.0 class Inflections require "dry/inflector/inflections/defaults" # Instantiate a set of inflection rules. # It adds the default rules and the optional customizations, passed as a block. # # @param blk [Proc] the optional, custom rules # # @since 0.1.0 # @api private def self.build(&blk) new do |inflect| Defaults.call(inflect) blk.call(inflect) if block_given? end end # Pluralization rules # # @return [Dry::Inflector::Rules] # # @since 0.1.0 # @api private attr_reader :plurals # Singularization rules # # @return [Dry::Inflector::Rules] # # @since 0.1.0 # @api private attr_reader :singulars # Uncountable rules # # @return [Set] # # @since 0.1.0 # @api private attr_reader :uncountables # Human rules # # @return [Dry::Inflector::Rules] # # @since 0.1.0 # @api private attr_reader :humans # Acronyms # # @return [Dry::Inflector::Acronyms] # # @since 0.1.2 # @api private attr_reader :acronyms # Instantiate the rules # # @return [Dry::Inflector::Inflections] # @yieldparam [self] # # @since 0.1.0 # @api private def initialize @plurals = Rules.new @singulars = Rules.new @humans = Rules.new @uncountables = Set[] @acronyms = Acronyms.new yield(self) if block_given? end # Add a custom pluralization rule # # Specifies a new pluralization rule and its replacement. # The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. # # @param rule [String, Regexp] the rule # @param replacement [String] the replacement # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.plural "virus", "viruses" # end def plural(rule, replacement) rule(rule, replacement, plurals) end # Add a custom singularization rule # # Specifies a new singularization rule and its replacement. # The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. # # @param rule [String, Regexp] the rule # @param replacement [String] the replacement # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.singular "thieves", "thief" # end def singular(rule, replacement) rule(rule, replacement, singulars) end # Add a custom pluralization rule # # Specifies a new irregular that applies to both pluralization and singularization at the same time. # This can only be used for strings, not regular expressions. # You simply pass the irregular in singular and plural form. # # @param singular [String] the singular # @param plural [String] the plural # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.singular "octopus", "octopi" # end def irregular(singular, plural) uncountables.delete(singular) uncountables.delete(plural) add_irregular(singular, plural, plurals) add_irregular(plural, singular, singulars) end # Add a custom rule for uncountable words # # Uncountable will not be inflected # # @param [Enumerable] words # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.uncountable "money" # inflections.uncountable "money", "information" # inflections.uncountable %w(money information rice) # end def uncountable(*words) uncountables.merge(words.flatten) end # Add one or more acronyms # # Acronyms affect how basic operations are performed, such # as camelize/underscore. # # @param words [Array] a list of acronyms # # @since 0.1.2 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.acronym "HTML" # end # # inflector.camelize("html") # => "HTML" # inflector.underscore("HTMLIsFun") # => "html_is_fun" def acronym(*words) words.each { |word| @acronyms.add(word.downcase, word) } end # Add a custom humanize rule # # Specifies a humanized form of a string by a regular expression rule or by a string mapping. # When using a regular expression based replacement, the normal humanize formatting is called after the replacement. # When a string is used, the human form should be specified as desired (example: `"The name"`, not `"the_name"`) # # @param rule [String, Regexp] the rule # @param replacement [String] the replacement # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new do |inflections| # inflections.human(/_cnt$/i, '\1_count') # inflections.human("legacy_col_person_name", "Name") # end def human(rule, replacement) humans.insert(0, [rule, replacement]) end private # Add irregular inflection # # @param rule [String] the rule # @param replacement [String] the replacement # # @return [undefined] # # @since 0.1.0 # @api private def add_irregular(rule, replacement, target) head, *tail = rule.chars.to_a rule(/(#{head})#{tail.join}\z/i, '\1' + replacement[1..-1], target) end # Add a new rule # # @param rule [String, Regexp] the rule # @param replacement [String, Regexp] the replacement # @param target [Dry::Inflector::Rules] the target # # @since 0.1.0 # @api private def rule(rule, replacement, target) uncountables.delete(rule) uncountables.delete(replacement) target.insert(0, [rule, replacement]) end end end end dry-inflector-0.2.0/lib/dry/inflector/inflections/0000755000175000017500000000000013617274703022076 5ustar utkarshutkarshdry-inflector-0.2.0/lib/dry/inflector/inflections/defaults.rb0000644000175000017500000001103413617274703024231 0ustar utkarshutkarsh# frozen_string_literal: true module Dry class Inflector class Inflections # Default inflections # # @since 0.1.0 # @api private # # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength module Defaults # @since 0.1.0 # @api private def self.call(inflect) plural(inflect) singular(inflect) irregular(inflect) uncountable(inflect) acronyms(inflect) end # @since 0.1.0 # @api private def self.plural(inflect) inflect.plural(/\z/, "s") inflect.plural(/s\z/i, "s") inflect.plural(/(ax|test)is\z/i, '\1es') inflect.plural(/(.*)us\z/i, '\1uses') inflect.plural(/(octop|vir|cact)us\z/i, '\1i') inflect.plural(/(octop|vir)i\z/i, '\1i') inflect.plural(/(alias|status)\z/i, '\1es') inflect.plural(/(buffal|domin|ech|embarg|her|mosquit|potat|tomat)o\z/i, '\1oes') inflect.plural(/(? "dataMapper" def camelize_lower(input) internal_camelize(input, false) end # Upper camelize a string # # @param input [String,Symbol] the input # @return [String] the upper camelized string # # @since 0.1.3 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.camelize_upper("data_mapper") # => "DataMapper" # inflector.camelize_upper("dry/inflector") # => "Dry::Inflector" def camelize_upper(input) internal_camelize(input, true) end alias :camelize :camelize_upper # Find a constant with the name specified in the argument string # # The name is assumed to be the one of a top-level constant, # constant scope of caller is ignored # # @param input [String,Symbol] the input # @return [Class, Module] the class or module # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.constantize("Module") # => Module # inflector.constantize("Dry::Inflector") # => Dry::Inflector def constantize(input) Object.const_get(input) end # Classify a string # # @param input [String,Symbol] the input # @return [String] the classified string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.classify("books") # => "Book" def classify(input) camelize(singularize(input.to_s.sub(/.*\./, ""))) end # Dasherize a string # # @param input [String,Symbol] the input # @return [String] the dasherized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.dasherize("dry_inflector") # => "dry-inflector" def dasherize(input) input.to_s.tr("_", "-") end # Demodulize a string # # @param input [String,Symbol] the input # @return [String] the demodulized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.demodulize("Dry::Inflector") # => "Inflector" def demodulize(input) input.to_s.split("::").last end # Humanize a string # # @param input [String,Symbol] the input # @return [String] the humanized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.humanize("dry_inflector") # => "Dry inflector" # inflector.humanize("author_id") # => "Author" def humanize(input) input = input.to_s result = inflections.humans.apply_to(input) result.chomp!("_id") result.tr!("_", " ") match = /(?\W)/.match(result) separator = match ? match[:separator] : DEFAULT_SEPARATOR result.split(separator).map.with_index { |word, index| inflections.acronyms.apply_to(word, index.zero?) }.join(separator) end # Creates a foreign key name # # @param input [String, Symbol] the input # @return [String] foreign key # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.foreign_key("Message") => "message_id" def foreign_key(input) "#{underscore(demodulize(input))}_id" end # Ordinalize a number # # @param number [Integer] the input # @return [String] the ordinalized number # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.ordinalize(1) # => "1st" # inflector.ordinalize(2) # => "2nd" # inflector.ordinalize(3) # => "3rd" # inflector.ordinalize(10) # => "10th" # inflector.ordinalize(23) # => "23rd" def ordinalize(number) # rubocop:disable Metrics/MethodLength abs_value = number.abs if ORDINALIZE_TH.key?(abs_value % 100) "#{number}th" else case abs_value % 10 when 1 then "#{number}st" when 2 then "#{number}nd" when 3 then "#{number}rd" else "#{number}th" end end end # Pluralize a string # # @param input [String,Symbol] the input # @return [String] the pluralized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.pluralize("book") # => "books" # inflector.pluralize("money") # => "money" def pluralize(input) input = input.to_s return input if uncountable?(input) inflections.plurals.apply_to(input) end # Singularize a string # # @param input [String] the input # @return [String] the singularized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.singularize("books") # => "book" # inflector.singularize("money") # => "money" def singularize(input) input = input.to_s return input if uncountable?(input) inflections.singulars.apply_to(input) end # Tableize a string # # @param input [String,Symbol] the input # @return [String] the tableized string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.tableize("Book") # => "books" def tableize(input) input = input.to_s.gsub(/::/, "_") pluralize(underscore(input)) end # Underscore a string # # @param input [String,Symbol] the input # @return [String] the underscored string # # @since 0.1.0 # # @example # require "dry/inflector" # # inflector = Dry::Inflector.new # inflector.underscore("dry-inflector") # => "dry_inflector" def underscore(input) input = input.to_s.gsub("::", "/") input.gsub!(inflections.acronyms.regex) do m1 = Regexp.last_match(1) m2 = Regexp.last_match(2) "#{m1 ? '_' : '' }#{m2.downcase}" end input.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') input.gsub!(/([a-z\d])([A-Z])/, '\1_\2') input.tr!("-", "_") input.downcase! input end # Check if the input is an uncountable word # # @param input [String] the input # @return [TrueClass,FalseClass] the result of the check # # @since 0.1.0 # @api private def uncountable?(input) !(input =~ /\A[[:space:]]*\z/).nil? || inflections.uncountables.include?(input.downcase) end # @return [String] # # @since 0.2.0 # @api public def to_s '#' end alias inspect to_s private # @since 0.1.0 # @api private ORDINALIZE_TH = (11..13).each_with_object({}) { |n, ret| ret[n] = true }.freeze # @since 0.1.2 # @api private DEFAULT_SEPARATOR = " " attr_reader :inflections # @since 0.1.3 # @api private def internal_camelize(input, upper) input = input.to_s.dup input.sub!(/^[a-z\d]*/) { |match| inflections.acronyms.apply_to(match, upper) } input.gsub!(%r{(?:_|(/))([a-z\d]*)}i) do m1 = Regexp.last_match(1) m2 = Regexp.last_match(2) "#{m1}#{inflections.acronyms.apply_to(m2)}" end input.gsub!("/", "::") input end end end