strings-inflection 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ require_relative "noun"
6
+
7
+ module Strings
8
+ module Inflection
9
+ class Parser
10
+ # Parse a string by evaluating content inside tags
11
+ #
12
+ # @api private
13
+ def self.parse(str, count)
14
+ parser = new(str, count)
15
+ parser.parse
16
+ end
17
+
18
+ def initialize(str, count)
19
+ @scanner = StringScanner.new(str)
20
+ @count = count
21
+ @value = []
22
+ end
23
+
24
+ def parse
25
+ while !@scanner.eos?
26
+ parse_noun || parse_verb ||
27
+ parse_count || parse_char
28
+ end
29
+
30
+ @value.join
31
+ end
32
+
33
+ private
34
+
35
+ # @api private
36
+ def parse_noun
37
+ if @scanner.scan(/\{\{N([^\}]*?):([^\}]+?)\}\}/)
38
+ option = @scanner[1].to_s.tr(" ", "").downcase
39
+ if option =~ /[^sp]/i
40
+ raise "Unknown option '#{option}' in {{N:...}} tag"
41
+ end
42
+ inflection = if option.empty?
43
+ @count == 1 ? :singular : :plural
44
+ else
45
+ option == "s" ? :singular : :plural
46
+ end
47
+ noun = Noun[@scanner[2].to_s.tr(" ", "")]
48
+ @value << noun.public_send(inflection)
49
+ end
50
+ end
51
+
52
+ # @api private
53
+ def parse_verb
54
+ if @scanner.scan(/\{\{V([^\}]*?):([^\}]+?)\}\}/)
55
+ option = @scanner[1].to_s.tr(" ", "").downcase
56
+ if !option.empty?
57
+ raise "Unknown option '#{option}' in {{V:...}} tag"
58
+ end
59
+ inflection = @count == 1 ? :singular : :plural
60
+ verb = Verb[@scanner[2].to_s.tr(" ", "")]
61
+ @value << verb.public_send(inflection)
62
+ end
63
+ end
64
+
65
+ # @api private
66
+ def parse_count
67
+ if @scanner.scan(/\{\{#([^\}]*?):([^\}]+?)\}\}/)
68
+ option = @scanner[1].to_s.tr(" ", "").downcase
69
+ if option =~ /[^f]/
70
+ raise "Unknown option '#{option}' in {{#:...}} tag"
71
+ end
72
+ amount = case option
73
+ when "f"
74
+ fuzzy_count(@count)
75
+ else
76
+ @count
77
+ end
78
+ @value << amount
79
+ end
80
+ end
81
+
82
+ # @api private
83
+ def parse_char
84
+ @value << @scanner.getch
85
+ end
86
+
87
+ # @api private
88
+ def fuzzy_count(count)
89
+ if count >= 10 then "many"
90
+ elsif count >= 6 then "several"
91
+ elsif count >= 3 then "a few"
92
+ elsif count == 2 then "a couple of"
93
+ elsif count == 1 then "one"
94
+ else "no"
95
+ end
96
+ end
97
+ end # Parser
98
+ end # Inflection
99
+ end # Strings
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ class Term
6
+ # Create a term
7
+ #
8
+ # @api public
9
+ def self.[](word)
10
+ self.new(word)
11
+ end
12
+
13
+ attr_reader :word
14
+
15
+ # Create a new term
16
+ #
17
+ # @param [String] word
18
+ # the word to turn into a term object
19
+ #
20
+ # @api public
21
+ def initialize(word)
22
+ @word = word.dup
23
+ freeze
24
+ end
25
+
26
+ # Find a matching rule and replace
27
+ #
28
+ # @return [nil, String]
29
+ # nil or replaced word
30
+ #
31
+ # @api private
32
+ def find_match(rules)
33
+ regex, replacement = rules.find { |rule| !!(word =~ rule[0]) }
34
+
35
+ return if regex.nil?
36
+
37
+ word.sub(regex, replacement)
38
+ end
39
+
40
+ # Check if noun is in singular form
41
+ #
42
+ # @example
43
+ # Strings::Inflection::Noun.new("error").singular?
44
+ # # => true
45
+ #
46
+ # @return [Boolean]
47
+ #
48
+ # @api public
49
+ def singular?
50
+ return false if word.to_s.empty?
51
+
52
+ word.downcase == singular
53
+ end
54
+
55
+ # Check if noun is in plural form
56
+ #
57
+ # @example
58
+ # Strings::Inflection::Noun.new("errors").plural?
59
+ # # => true
60
+ #
61
+ # @return [Boolean]
62
+ #
63
+ # @api public
64
+ def plural?
65
+ return false if word.to_s.empty?
66
+
67
+ word.downcase == plural
68
+ end
69
+
70
+ # A string representation of this term
71
+ #
72
+ # @api public
73
+ def to_s
74
+ word.to_s
75
+ end
76
+ end # Term
77
+ end # Inflection
78
+ end # Strings
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "term"
4
+ require_relative "verbs"
5
+
6
+ module Strings
7
+ module Inflection
8
+ class Verb < Term
9
+ # Check if word is uninflected
10
+ #
11
+ # @param [String] word
12
+ # the word to check
13
+ #
14
+ # @return [Boolean]
15
+ #
16
+ # @api private
17
+ def uninflected?
18
+ Verbs.uninflected.include?(word)
19
+ end
20
+
21
+ # Inflect a word to its singular form
22
+ #
23
+ # @example
24
+ # Strings::Inflection::Verb.new("go").singular
25
+ # # => "goes"
26
+ #
27
+ # @return [String]
28
+ # the verb inflected to singular form
29
+ #
30
+ # @api public
31
+ def singular
32
+ return word if word.to_s.empty? || uninflected?
33
+
34
+ find_match(Verbs.singulars) || word
35
+ end
36
+
37
+ # Inflect a word to its plural form
38
+ #
39
+ # @example
40
+ # Strings::Inflection::Verb.new("goes").plural
41
+ # # => "go"
42
+ #
43
+ # @return [String]
44
+ # the verb inflected to plural form
45
+ #
46
+ # @api public
47
+ def plural
48
+ return word if word.to_s.empty? || uninflected?
49
+
50
+ find_match(Verbs.plurals) || word
51
+ end
52
+ end # Verb
53
+ end # Inflection
54
+ end # Strings
@@ -0,0 +1,130 @@
1
+ # frozen_string_litearl: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ module Verbs
6
+
7
+ @uninflected = [
8
+ "can",
9
+ "may",
10
+ "must",
11
+ "ought",
12
+ "shall",
13
+ "will"
14
+ ]
15
+
16
+ @singular_irregular = [
17
+ [/\A(is|are)$/, "is"],
18
+ [/\A(was|were)$/, "was"],
19
+ [/\A(ha)ve$/, "\\1s"],
20
+ [/(.*go|do)$/, "\\1es"],
21
+ [/(.*demo)$/, "\\1s"],
22
+ [/\A(gas)$/i, "\\1ses"],
23
+ [/(.*precis)$/i, "\\1es"],
24
+ [/(.*stomach)$/i, "\\1s"],
25
+ ]
26
+
27
+ @singular_rules = [
28
+ [/(.*[y]ch)$/i, "\\1s"],
29
+ [/(.*[cs]h)$/i, "\\1es"],
30
+ [/(.*is)$/i, "\\1ses"],
31
+ [/(.*[ua]s)$/i, "\\1es"],
32
+ [/(.*[x])$/i, "\\1es"],
33
+ [/(.*oe)$/i, "\\1s"],
34
+ [/(.*iz)$/i, "\\1zes"],
35
+ [/(.*zz)$/i, "\\1es"],
36
+ [/(.*[il]tz)$/i, "\\1es"],
37
+ [/(.*ss)$/i, "\\1es"],
38
+ [/(.*[aeiou])y$/i, "\\1ys"],
39
+ [/(.*)y$/i, "\\1ies"],
40
+ [/(.*[io]o)$/i, "\\1s"],
41
+ [/(.*o)$/i, "\\1es"],
42
+ [/(.*[^s])$/i, "\\1s"],
43
+ [/(.*)s$/i, "\\1s"],
44
+ [/(.*)$/i, "\\1s"]
45
+ ]
46
+
47
+ @singulars = @singular_irregular + @singular_rules
48
+
49
+ @plural_irregular = [
50
+ [/\A(am|is)$/, "are"],
51
+ [/\Awas$/, "were"],
52
+ [/\Ahas$/, "have"],
53
+ [/(.*go|do)es$/, "\\1"],
54
+ [/\A(bellyache|ache)s$/, "\\1"],
55
+ [/\A(axe)s$/, "\\1"],
56
+ [/\A(bus)es$/, "\\1"],
57
+ [/(.*caucus)es$/i, "\\1"],
58
+ [/(.*finesse)s$/i, "\\1"],
59
+ [/(.*bias)es$/i, "\\1"],
60
+ [/(.*cache)s$/i, "\\1"],
61
+ [/(.*caddie)s$/i, "\\1"],
62
+ [/(.*caucus)es$/i, "\\1"],
63
+ [/(.*chorus)es$/i, "\\1"],
64
+ [/\A(die|hie|vie)s$/i, "\\1"],
65
+ [/\A(lie|belie|overlie|underlie)s$/i, "\\1"],
66
+ [/\A(tie|untie)s$/i, "\\1"],
67
+ [/\A(dis|gas)ses$/i, "\\1"],
68
+ [/(.*echo|veto)es$/i, "\\1"],
69
+ [/(.*douche)s$/i, "\\1"],
70
+ [/(.*focus)es$/i, "\\1"],
71
+ [/(.*precis)es$/i, "\\1"],
72
+ [/(.*quiz)zes$/i, "\\1"],
73
+ [/(.*stymie)s$/i, "\\1"],
74
+ [/(.*sk)ies$/i, "\\1y"],
75
+ [/(.*whiz)zes$/i, "\\1"]
76
+ ]
77
+
78
+ @plural_rules = [
79
+ [/(.*[aeiou]y)s$/i, "\\1"],
80
+ [/(.*[aiurywnopl]se)s$/i, "\\1"],
81
+ [/(.*[cs]h)es$/i, "\\1"],
82
+ [/(.*[sx])es$/i, "\\1"],
83
+ # [/(.*oe)s$/i, "\\1"],
84
+ [/(.*zz)es$/i, "\\1"],
85
+ [/(.*[il]tz)es$/i, "\\1"],
86
+ [/(.*ue)s$/i, "\\1"],
87
+ [/(.*[gk]ie)s$/i, "\\1"],
88
+ [/(.*all)ies$/i, "\\1y"],
89
+ [/(.*)ies$/i, "\\1y"],
90
+ [/(.*y)s$/i, "\\1"],
91
+ [/(.*[thn]oe)s$/i, "\\1"],
92
+ [/(.*o)es$/i, "\\1"],
93
+ [/(.+[^s])s$/i, "\\1"],
94
+ [/(.+)s$/i, "\\1"]
95
+ ]
96
+
97
+ @plurals = @plural_irregular + @plural_rules
98
+
99
+ # A set of uninflected verbs
100
+ #
101
+ # @return [Array[String]]
102
+ #
103
+ # @api private
104
+ def uninflected
105
+ @uninflected
106
+ end
107
+ module_function :uninflected
108
+
109
+ # A list of singular rules
110
+ #
111
+ # @return [Array[String]]
112
+ #
113
+ # @api private
114
+ def singulars
115
+ @singulars
116
+ end
117
+ module_function :singulars
118
+
119
+ # A list of plural rules
120
+ #
121
+ # @return [Array[String]]
122
+ #
123
+ # @api private
124
+ def plurals
125
+ @plurals
126
+ end
127
+ module_function :plurals
128
+ end # Verbs
129
+ end # Inflection
130
+ end # Strings
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "strings/inflection/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "strings-inflection"
7
+ spec.version = Strings::Inflection::VERSION
8
+ spec.authors = ["Piotr Murach"]
9
+ spec.email = ["me@piotrmurach.com"]
10
+ spec.summary = %q{Inflect English nouns and verbs.}
11
+ spec.description = %q{Inflect English nouns and verbs. The algorithms are based on the analysis of 7,000 most frequently used nouns and 6,000 most used verbs in English language.}
12
+ spec.homepage = "https://github.com/piotrmurach/strings-inflection"
13
+ spec.license = "MIT"
14
+
15
+ if spec.respond_to?(:metadata)
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+ spec.metadata["changelog_uri"] = "https://github.com/piotrmurach/strings-inflection/blob/master/CHANGELOG.md"
18
+ spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/strings-inflection"
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/piotrmurach/strings-inflection"
21
+ end
22
+
23
+ spec.files = Dir["lib/**/*.rb"]
24
+ spec.files += Dir["tasks/*", "strings-inflection.gemspec"]
25
+ spec.files += Dir["README.md", "CHANGELOG.md", "LICENSE.txt", "Rakefile"]
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_development_dependency "bundler", ">= 1.5"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Load gem inside irb console"
4
+ task :console do
5
+ require "irb"
6
+ require "irb/completion"
7
+ require File.join(__FILE__, "../../lib/strings-inflection")
8
+ ARGV.clear
9
+ IRB.start
10
+ end
11
+ task c: %w[ console ]
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Measure code coverage"
4
+ task :coverage do
5
+ begin
6
+ original, ENV["COVERAGE"] = ENV["COVERAGE"], "true"
7
+ Rake::Task["spec"].invoke
8
+ ensure
9
+ ENV["COVERAGE"] = original
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "rspec/core/rake_task"
5
+
6
+ desc "Run all specs"
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.pattern = "spec/{unit,integration}{,/*/**}/*_spec.rb"
9
+ end
10
+
11
+ namespace :spec do
12
+ desc "Run unit specs"
13
+ RSpec::Core::RakeTask.new(:unit) do |task|
14
+ task.pattern = "spec/unit{,/*/**}/*_spec.rb"
15
+ end
16
+
17
+ desc "Run integration specs"
18
+ RSpec::Core::RakeTask.new(:integration) do |task|
19
+ task.pattern = "spec/integration{,/*/**}/*_spec.rb"
20
+ end
21
+
22
+ desc "Run performance specs"
23
+ RSpec::Core::RakeTask.new(:perf) do |task|
24
+ task.pattern = "spec/perf{,/*/**}/*_spec.rb"
25
+ end
26
+ end
27
+
28
+ rescue LoadError
29
+ %w[spec spec:unit spec:integration].each do |name|
30
+ task name do
31
+ $stderr.puts "In order to run #{name}, do `gem install rspec`"
32
+ end
33
+ end
34
+ end