spoonerize 0.1.3 → 1.0.0

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: bb11ac34b545af8dd6a68950c95de9617c0b54995e5fb9b0ae55f3613d18be22
4
- data.tar.gz: 5b60db2501c97458406205884f985e2b07a93a5e7a78b891daedcda45c687d33
3
+ metadata.gz: 1b4c993b75b971449d8795c0666e4645bee48ff903afaea84463025da44ffdbf
4
+ data.tar.gz: b57070d3e1dc670ef4cdf17f9657c8759828e86a039571d305a6d6716aaa5b65
5
5
  SHA512:
6
- metadata.gz: 1b9c899c14a35ae01d4aaa857f174427acaf91dbdad28a913c57b8e542e7fa33756f5e991c76cf5c978334280b2cd3217c32ef4749e52b2acfc5ed5f9de65cf6
7
- data.tar.gz: 0e6c20e84abfb81cd4098f42ee9e5f1907e11c5e62827340885106cce38717dc770acdf21df087da9fcc81235e78bee60f4da05cffbdb3ee07a0b5395f4b7e9a
6
+ metadata.gz: ccf19838d1a10609dddbd2da4cfe8286c548b9bc91e2b957ee592120225305c93a166da7ae10c34ead80a402f8cb767fbb192162fade18afc046298ff3d5dd1c
7
+ data.tar.gz: 3420c7dc81ea682e3388b9f9097a6c538199cbb38e2f5179e3a5f68f8200b278674da04397df05e14a259ad6ba5878daf3b63390f244a3988c3678f08e8b85a3
data/Gemfile.lock CHANGED
@@ -1,28 +1,74 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spoonerize (0.1.3)
4
+ spoonerize (1.0.0)
5
5
  csv
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ ast (2.4.3)
10
11
  csv (3.3.5)
11
12
  date (3.5.1)
12
13
  erb (6.0.4)
14
+ json (2.20.0)
15
+ language_server-protocol (3.17.0.5)
16
+ lint_roller (1.1.0)
17
+ parallel (1.28.0)
18
+ parser (3.3.11.1)
19
+ ast (~> 2.4.1)
20
+ racc
13
21
  power_assert (3.0.1)
22
+ prism (1.9.0)
14
23
  psych (5.4.0)
15
24
  date
16
25
  stringio
26
+ racc (1.8.1)
27
+ rainbow (3.1.1)
17
28
  rake (13.4.2)
18
29
  rdoc (7.2.0)
19
30
  erb
20
31
  psych (>= 4.0.0)
21
32
  tsort
33
+ regexp_parser (2.12.0)
34
+ rubocop (1.84.2)
35
+ json (~> 2.3)
36
+ language_server-protocol (~> 3.17.0.2)
37
+ lint_roller (~> 1.1.0)
38
+ parallel (~> 1.10)
39
+ parser (>= 3.3.0.2)
40
+ rainbow (>= 2.2.2, < 4.0)
41
+ regexp_parser (>= 2.9.3, < 3.0)
42
+ rubocop-ast (>= 1.49.0, < 2.0)
43
+ ruby-progressbar (~> 1.7)
44
+ unicode-display_width (>= 2.4.0, < 4.0)
45
+ rubocop-ast (1.49.1)
46
+ parser (>= 3.3.7.2)
47
+ prism (~> 1.7)
48
+ rubocop-performance (1.26.1)
49
+ lint_roller (~> 1.1)
50
+ rubocop (>= 1.75.0, < 2.0)
51
+ rubocop-ast (>= 1.47.1, < 2.0)
52
+ ruby-progressbar (1.13.0)
53
+ standard (1.54.0)
54
+ language_server-protocol (~> 3.17.0.2)
55
+ lint_roller (~> 1.0)
56
+ rubocop (~> 1.84.0)
57
+ standard-custom (~> 1.0.0)
58
+ standard-performance (~> 1.8)
59
+ standard-custom (1.0.2)
60
+ lint_roller (~> 1.0)
61
+ rubocop (~> 1.50)
62
+ standard-performance (1.9.0)
63
+ lint_roller (~> 1.1)
64
+ rubocop-performance (~> 1.26.0)
22
65
  stringio (3.2.0)
23
66
  test-unit (3.7.8)
24
67
  power_assert
25
68
  tsort (0.2.0)
69
+ unicode-display_width (3.2.0)
70
+ unicode-emoji (~> 4.1)
71
+ unicode-emoji (4.2.0)
26
72
 
27
73
  PLATFORMS
28
74
  arm64-darwin-25
@@ -33,6 +79,7 @@ DEPENDENCIES
33
79
  rake (~> 13.0, >= 13.0.1)
34
80
  rdoc
35
81
  spoonerize!
82
+ standard (= 1.54.0)
36
83
  test-unit (~> 3.3, >= 3.3.5)
37
84
 
38
85
  BUNDLED WITH
data/README.md CHANGED
@@ -26,6 +26,8 @@ consonants, but will still lose its own if it has any.
26
26
  - If the word to pull from is excluded, that word is skipped, and you pull the
27
27
  leading consonants from the next non-excluded word.
28
28
  - "Q" and "U" should stay together (like "queen").
29
+ - "Y" is treated like a leading consonant by itself or before a vowel sound
30
+ (like "yellow"), but like a leading vowel before a consonant (like "yttrium").
29
31
  - A lot of the time, the words won't look how they're supposed to sound, as you
30
32
  go by how the word *used* to sound, not how it's spelled. For instance,
31
33
  `$ spoonerize two new cuties` becomes "no cew twuties", but it would be
@@ -40,7 +42,7 @@ Just install the gem!
40
42
  gem install spoonerize
41
43
  ```
42
44
 
43
- If you don't have permission on your system to install ruby or gems, I recommend
45
+ If you don't have permission on your system to install Ruby or gems, I recommend
44
46
  using
45
47
  [rbenv](http://www.rubyinside.com/rbenv-a-simple-new-ruby-version-management-tool-5302.html),
46
48
  or you can try the manual methods below.
@@ -82,10 +84,12 @@ get the results. For example:
82
84
 
83
85
  ```
84
86
  $ spoonerize -s not too shabby
85
- Saving [tot shoo nabby] to ~/.cache/spoonerize/spoonerize.csv
87
+ tot shoo nabby
88
+ Saving...
86
89
 
87
90
  $ spoonerize -rs not too shabby
88
- Saving [shot noo tabby] to ~/.cache/spoonerize/spoonerize.csv
91
+ shot noo tabby
92
+ Saving...
89
93
 
90
94
  $ spoonerize -p
91
95
  not too shabby | tot shoo nabby | No Options
@@ -97,72 +101,90 @@ Here is a list of all available options:
97
101
  ```
98
102
  -r, --[no-]reverse Reverse flipping
99
103
  -l, --[no-]lazy Skip small words
104
+ -c, --[no-]consonants-only Only flip consonant-starting words
100
105
  -m, --[no-]map Print words mapping
101
- -p, --[no-]print Print all entries in the log
106
+ -p, --[no-]print-log Print all entries in the log
102
107
  -s, --[no-]save Save results in log
103
- --exclude=WORDS Words to skip
108
+ --exclude=WORD Words to skip
104
109
  ```
105
110
 
106
111
  ### Config File
107
- You can create a config file called `~/.spoonerize.yml`. In this file, you can
108
- change default options at runtime. Available settings are:
109
-
110
- ```yaml
111
- # Setting Default
112
- excluded_words: []
113
- lazy: false
114
- reverse: false
115
- logfile_name: '~/.cache/spoonerize/spoonerize.csv'
112
+ You can create a Ruby config file called `~/.spoonerizerc`. The CLI loads this
113
+ file automatically before it parses command-line options, so options set in the
114
+ file can still be overridden at runtime by executable flags.
115
+
116
+ ```ruby
117
+ Spoonerize.configure do |config|
118
+ config.excluded_words = []
119
+ config.lazy = false
120
+ config.consonants_only = false
121
+ config.reverse = false
122
+ config.logfile_name = File.expand_path("~/.cache/spoonerize/spoonerize.csv")
123
+ end
116
124
  ```
117
125
 
118
- Options set by this file can be overridden at runtime by the use of the
119
- executable's flags.
126
+ Because the file is Ruby, you can set only the values you want to change.
120
127
 
121
128
  ## API
122
129
  The API is [fully
123
130
  documented](https://evanthegrayt.github.io/spoonerize/), but below
124
- are some quick examples of how you could use this in your ruby code.
131
+ are some quick examples of how you could use this in your Ruby code.
125
132
 
126
133
  ```ruby
127
134
  require 'spoonerize'
128
135
 
129
- spoonerism = Spoonerize::Spoonerism.new(%w[not too shabby]) do |s|
130
- s.reverse = true
131
- end
136
+ spoonerism = Spoonerize::Spoonerism.new("not", "too", "shabby")
137
+
138
+ spoonerism.to_s
139
+ # => tot shoo nabby
132
140
 
133
- spoonerism.spoonerize
141
+ reversed = Spoonerize::Spoonerism.new("not", "too", "shabby", reverse: true)
142
+ reversed.to_s
134
143
  # => shot noo tabby
135
144
 
136
- spoonerism.reverse = false
137
- spoonerism.spoonerize
138
- # => tot shoo nabby
145
+ Spoonerize.configure do |config|
146
+ config.logfile_name = File.expand_path("~/.cache/spoonerize/spoonerize.csv")
147
+ end
148
+ Spoonerize::Spoonerism.new("not", "too", "shabby").save
149
+ ```
139
150
 
140
- spoonerism.logfile_name = '~/.cache/spoonerize/spoonerize.csv'
141
- spoonerism.save
151
+ To leave vowel-starting words alone, enable consonants-only mode:
152
+
153
+ ```ruby
154
+ Spoonerize::Spoonerism.new("turn", "up", "son", consonants_only: true).to_s
155
+ # => surn up ton
142
156
  ```
143
157
 
144
- You can also use the [config file](#config-file), either by passing it at
145
- initialization, or via the setter. The config file will be automatically loaded
146
- if passed at initialization, before the instance is yielded so you can still
147
- change the values via the block. If set via the setter, you must call
148
- `#load_config_file`.
158
+ You can also configure global defaults before creating a spoonerism:
149
159
 
150
160
  ```ruby
151
- # Config file would be automatically loaded before block is executed.
152
- s = Spoonerise::Spoonerism.new(%w[not too shabby], '~/.spoonerize.yml') do |sp|
153
- sp.reverse = true
161
+ Spoonerize.configure do |config|
162
+ config.reverse = true
154
163
  end
155
164
 
156
- # Config file would need to be manually loaded.
157
- s = Spoonerise::Spoonerism.new(%w[not too shabby]) do |sp|
158
- sp.config_file = '~/.spoonerize.yml'
159
- end
165
+ s = Spoonerize::Spoonerism.new("not", "too", "shabby")
166
+ s.spoonerize
167
+ # => shot noo tabby
168
+ ```
169
+
170
+ Options passed directly to `Spoonerize::Spoonerism.new` only apply to that
171
+ instance.
172
+
173
+ Passing words as an array is deprecated and will be removed in Spoonerize 1.0:
160
174
 
161
- s.load_config_file
175
+ ```ruby
176
+ Spoonerize::Spoonerism.new(%w[not too shabby])
177
+ ```
178
+
179
+ Or load a config file manually:
180
+
181
+ ```ruby
182
+ Spoonerize.load_config_file("~/.spoonerizerc")
183
+ s = Spoonerize::Spoonerism.new("not", "too", "shabby")
162
184
  ```
163
185
 
164
186
  ## Self Promotion
165
187
  I do these projects for fun, and I enjoy knowing that they're helpful to people.
166
188
  Consider starring [the repository](https://github.com/evanthegrayt/spoonerize)
167
189
  if you like it! If you love it, follow me [on
168
- github](https://github.com/evanthegrayt)!
190
+ GitHub](https://github.com/evanthegrayt)!
data/Rakefile CHANGED
@@ -4,6 +4,7 @@ require_relative "lib/spoonerize"
4
4
  require "bundler/gem_tasks"
5
5
  require "rdoc/task"
6
6
  require "rake/testtask"
7
+ require "standard/rake"
7
8
 
8
9
  Rake::TestTask.new do |t|
9
10
  t.libs = ["lib"]
@@ -64,4 +65,3 @@ namespace :version do
64
65
  end
65
66
  end
66
67
  end
67
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spoonerize
2
4
  ##
3
5
  # Class that handles bumping an index.
@@ -1,16 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "optparse"
2
- require "yaml"
3
4
 
4
5
  module Spoonerize
5
6
  ##
6
7
  # The class for handling the command-line interface.
7
8
  class Cli
8
9
  ##
9
- # The preferences file the user can create to change runtime options.
10
+ # The config file the user can create to change default runtime options.
10
11
  #
11
12
  # @return [String]
12
- PREFERENCE_FILE =
13
- File.expand_path(File.join(ENV["HOME"], ".spoonerize.yml")).freeze
13
+ CONFIG_FILE = File.expand_path(File.join(ENV["HOME"], ".spoonerizerc"))
14
14
 
15
15
  ##
16
16
  # Creates an instance of +Spoonerism+ and runs what the user requested.
@@ -19,7 +19,7 @@ module Spoonerize
19
19
  def self.execute(options = [])
20
20
  exe = new(options)
21
21
 
22
- if exe.print?
22
+ if exe.print_log?
23
23
  exe.print_log
24
24
  return
25
25
  end
@@ -40,10 +40,10 @@ module Spoonerize
40
40
  attr_reader :options
41
41
 
42
42
  ##
43
- # Preferences after reading config file and parsing ARGV.
43
+ # Overrides after reading config file and parsing ARGV.
44
44
  #
45
- # @return [Array]
46
- attr_reader :preferences
45
+ # @return [Hash]
46
+ attr_reader :overrides
47
47
 
48
48
  ##
49
49
  # Create instance of +Cli+
@@ -52,23 +52,20 @@ module Spoonerize
52
52
  #
53
53
  # @return [self]
54
54
  def initialize(options)
55
+ Spoonerize.load_config_file(CONFIG_FILE) if File.file?(CONFIG_FILE)
55
56
  @map = false
56
57
  @save = false
57
- @print = false
58
+ @print_log = false
58
59
  @options = options
59
- @preferences = get_preferences
60
+ @overrides = get_overrides
60
61
  end
61
62
 
62
63
  ##
63
- # Sets up an instance of +Spoonerize::Spoonerism+ and passes all user
64
- # preferences.
64
+ # Sets up an instance of +Spoonerize::Spoonerism+
65
65
  #
66
66
  # @return [Spoonerize::Spoonerism]
67
67
  def spoonerism
68
- pf = File.file?(PREFERENCE_FILE) ? PREFERENCE_FILE : nil
69
- @spoonerism ||= Spoonerism.new(options, pf) do |s|
70
- preferences.each { |k, v| s.send(:"#{k}=", v) }
71
- end
68
+ @spoonerism ||= Spoonerism.new(*options, **overrides)
72
69
  end
73
70
 
74
71
  ##
@@ -91,8 +88,8 @@ module Spoonerize
91
88
  # Should we print to the command line?
92
89
  #
93
90
  # @return [Boolean]
94
- def print?
95
- @print
91
+ def print_log?
92
+ @print_log
96
93
  end
97
94
 
98
95
  ##
@@ -100,8 +97,7 @@ module Spoonerize
100
97
  #
101
98
  # @return [Integer]
102
99
  def longest_word_length
103
- @longest_word_length ||=
104
- spoonerism.spoonerize.group_by(&:size).max.first.size
100
+ @longest_word_length ||= spoonerism.spoonerize.max_by(&:size).size
105
101
  end
106
102
 
107
103
  ##
@@ -109,8 +105,9 @@ module Spoonerize
109
105
  #
110
106
  # @return [nil]
111
107
  def print_log
112
- s = Spoonerize::Log.new(spoonerism.logfile_name)
113
- s.each { |row| print row.join(" | ") + "\n" }
108
+ Spoonerize::Log.new(Spoonerize.config.logfile_name).each do |row|
109
+ puts row.join(" | ")
110
+ end
114
111
  end
115
112
 
116
113
  ##
@@ -119,7 +116,7 @@ module Spoonerize
119
116
  # @return [nil]
120
117
  def print_mappings
121
118
  spoonerism.to_h.each do |k, v|
122
- printf("%-#{longest_word_length}s => %s\n", k, v)
119
+ printf("%-#{longest_word_length + 1}s => %s\n", k, v)
123
120
  end
124
121
  end
125
122
 
@@ -127,27 +124,30 @@ module Spoonerize
127
124
 
128
125
  ##
129
126
  # Read in args and set options
130
- def get_preferences # :nodoc:
127
+ def get_overrides # :nodoc:
131
128
  {}.tap do |prefs|
132
129
  OptionParser.new do |o|
133
130
  o.version = ::Spoonerize::Version.to_s
134
131
  o.on("-r", "--[no-]reverse", "Reverse flipping") do |v|
135
- prefs["reverse"] = v
132
+ prefs[:reverse] = v
136
133
  end
137
134
  o.on("-l", "--[no-]lazy", "Skip small words") do |v|
138
- prefs["lazy"] = v
135
+ prefs[:lazy] = v
136
+ end
137
+ o.on("-c", "--[no-]consonants-only", "Only flip consonant-starting words") do |v|
138
+ prefs[:consonants_only] = v
139
139
  end
140
140
  o.on("-m", "--[no-]map", "Print words mapping") do |v|
141
141
  @map = v
142
142
  end
143
- o.on("-p", "--[no-]print", "Print all entries in the log") do |v|
144
- @print = v
143
+ o.on("-p", "--[no-]print-log", "Print all entries in the log") do |v|
144
+ @print_log = v
145
145
  end
146
146
  o.on("-s", "--[no-]save", "Save results in log") do |v|
147
147
  @save = v
148
148
  end
149
149
  o.on("--exclude=WORD", Array, "Words to skip") do |v|
150
- prefs["exclude"] = v
150
+ prefs[:excluded_words] = v
151
151
  end
152
152
  end.parse!(options)
153
153
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spoonerize
4
+ class Config
5
+ ##
6
+ # Lazy mode. If true, words in +lazy_words+ will not be altered.
7
+ #
8
+ # @return [Boolean]
9
+ attr_accessor :lazy
10
+
11
+ ##
12
+ # Words to skip when +lazy+ is true.
13
+ #
14
+ # @return [Array]
15
+ attr_accessor :lazy_words
16
+
17
+ ##
18
+ # Words that should not be altered.
19
+ #
20
+ # @return [Array]
21
+ attr_accessor :excluded_words
22
+
23
+ ##
24
+ # When true, only consonant-starting words are eligible to be flipped.
25
+ #
26
+ # @return [Boolean]
27
+ attr_accessor :consonants_only
28
+
29
+ ##
30
+ # When true, reverse the order of the flipping. Only makes a difference
31
+ # when there are more than two flip-able words.
32
+ #
33
+ # @return [Boolean]
34
+ attr_accessor :reverse
35
+
36
+ ##
37
+ # Name of the log file. Should be a fully-qualified file path.
38
+ #
39
+ # @return [String]
40
+ attr_accessor :logfile_name
41
+
42
+ ##
43
+ # Create instance of Config.
44
+ #
45
+ # @return [Spoonerize::Config]
46
+ def initialize
47
+ @lazy = false
48
+ @lazy_words = %w[i a an and in of the my your his her him hers to is]
49
+ @excluded_words = []
50
+ @consonants_only = false
51
+ @reverse = false
52
+ @logfile_name = File.expand_path(
53
+ File.join(ENV["HOME"], ".cache", "spoonerize", "spoonerize.csv")
54
+ )
55
+ end
56
+
57
+ ##
58
+ # Create a copy of the current config with optional overrides.
59
+ #
60
+ # @return [Spoonerize::Config]
61
+ def with(**overrides)
62
+ self.class.new.tap do |config|
63
+ config.lazy = lazy
64
+ config.lazy_words = lazy_words.dup
65
+ config.excluded_words = excluded_words.dup
66
+ config.consonants_only = consonants_only
67
+ config.reverse = reverse
68
+ config.logfile_name = logfile_name
69
+
70
+ overrides.each do |key, value|
71
+ config.public_send(:"#{key}=", copy_value(value))
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def copy_value(value)
79
+ value.is_a?(Array) ? value.dup : value
80
+ end
81
+ end
82
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "csv"
2
4
  require "fileutils"
3
5
 
4
6
  module Spoonerize
5
7
  ##
6
- # Class that handles reading/writing logs.
8
+ # Class that handles reading/writing logs. Log file is stored as a simple CSV.
7
9
  class Log
8
10
  ##
9
11
  # The file name to use.
@@ -51,7 +53,7 @@ module Spoonerize
51
53
  ##
52
54
  # Iterate through each line of the file.
53
55
  #
54
- # @return [Array]
56
+ # @return [Enumerable]
55
57
  def each
56
58
  contents.each { |row| yield row }
57
59
  end
@@ -1,7 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spoonerize
2
4
  ##
3
5
  # The main word-flipper.
4
6
  class Spoonerism
7
+ VOWEL_LETTERS = "aeio"
8
+ CONSONANT_LETTERS = "bcdfghjklmnprstvwxz"
9
+ Y_FOLLOWING_CONSONANTS = "bcdfghjklmnpqrstvwxz"
10
+
5
11
  ##
6
12
  # The words originally passed at initialization.
7
13
  #
@@ -9,224 +15,235 @@ module Spoonerize
9
15
  attr_reader :words
10
16
 
11
17
  ##
12
- # This boolean determines if flipping should be performed lazily.
13
- #
14
- # @param [Boolean] true if should be lazy.
15
- #
16
- # @return [Boolean]
17
- attr_writer :lazy
18
-
19
- ##
20
- # This boolean determines if flipping should be reversed.
21
- #
22
- # @param [Boolean] true if should be reversed.
23
- #
24
- # @return [Boolean]
25
- attr_writer :reverse
26
-
27
- ##
28
- # The full path to the log file.
29
- #
30
- # @param [String] file
31
- #
32
- # @return [String]
33
- attr_accessor :logfile_name
34
-
35
- ##
36
- # The words that are to be excluded.
37
- #
38
- # @param [Array] words
39
- #
40
- # @return [Array]
41
- attr_accessor :excluded_words
42
-
43
- ##
44
- # The configuration file. Default is +nil+. If set to a string, and the file
45
- # exists, it is used to set options.
18
+ # Configuration values for this spoonerism.
46
19
  #
47
- # @return [String] file path
48
- attr_reader :config_file
49
-
50
- ##
51
- # The options from +config_file+ as a hash.
52
- #
53
- # @return [Hash] Options from +config_file+
20
+ # @return [Spoonerize::Config]
54
21
  attr_reader :config
55
22
 
56
23
  ##
57
- # Initialize instance. You can also use the +config_file+ either by passing
58
- # it at initialization, or via the setter. The config file will be
59
- # automatically loaded if passed at initialization, before the instance is
60
- # yielded so you can still change the values via the block. If set via the
61
- # setter, you must call `#load_config_file`.
62
- #
63
- # @param [Array] words
64
- #
65
- # @param [String] config_file
66
- #
67
- # @example
68
- # # Config file would be automatically loaded before block is executed.
69
- # s = Spoonerise::Spoonerism.new(%w[not too shabby], '~/.spoonerize.yml') do |sp|
70
- # sp.reverse = true # Would override setting from config file
71
- # end
72
- # # Config file would need to be manually loaded.
73
- # s = Spoonerise::Spoonerism.new(%w[not too shabby]) do |sp|
74
- # sp.config_file = '~/.spoonerize.yml'
75
- # sp.reverse = true
76
- # end
77
- # s.load_config_file # Would override setting from initialization
78
- def initialize(words, config_file = nil)
79
- @config = {}
80
- @excluded_words = []
81
- @words = words.map(&:downcase)
82
- @lazy = false
83
- @reverse = false
84
- @config_file = config_file && File.expand_path(config_file)
85
- @config_file_loaded = false
86
- @logfile_name = File.expand_path(
87
- File.join(ENV["HOME"], ".cache", "spoonerize", "spoonerize.csv")
88
- )
89
-
90
- load_config_file if config_file
91
-
92
- yield self if block_given?
24
+ # Initialize instance.
25
+ #
26
+ # @param [Array<String>] words Words to spoonerize. Passing a single array
27
+ # is deprecated and will be removed in Spoonerize 1.0.
28
+ # @param [Spoonerize::Config] config Base config to copy.
29
+ # @param [Boolean, nil] lazy Override lazy mode for this instance.
30
+ # @param [Array<String>, nil] lazy_words Override lazy words for this instance.
31
+ # @param [Array<String>, nil] excluded_words Override excluded words for this instance.
32
+ # @param [Boolean, nil] consonants_only Override consonants-only mode for this instance.
33
+ # @param [Boolean, nil] reverse Override reverse mode for this instance.
34
+ # @param [String, nil] logfile_name Override the log file path for this instance.
35
+ #
36
+ # @return [Spoonerize::Spoonerism]
37
+ def initialize(
38
+ *words,
39
+ config: Spoonerize.config,
40
+ lazy: nil,
41
+ lazy_words: nil,
42
+ excluded_words: nil,
43
+ consonants_only: nil,
44
+ reverse: nil,
45
+ logfile_name: nil
46
+ )
47
+ @words = normalize_words(words).map(&:downcase)
48
+ @config = config.with(**{
49
+ lazy: lazy,
50
+ lazy_words: lazy_words,
51
+ excluded_words: excluded_words,
52
+ consonants_only: consonants_only,
53
+ reverse: reverse,
54
+ logfile_name: logfile_name
55
+ }.reject { |_, value| value.nil? })
93
56
  end
94
57
 
95
58
  ##
96
59
  # Iterates through words array, and maps its elements to the output of
97
- # flip_words. Returns results as an array.
60
+ # flip_words.
61
+ #
62
+ # @return [Array]
98
63
  def spoonerize
99
- raise JakPibError, "Not enough words to flip" unless enough_flippable_words?
64
+ raise "Not enough words to flip" unless enough_flippable_words?
100
65
 
101
66
  words.map.with_index { |word, idx| flip_words(word, idx) }
102
67
  end
103
68
 
104
69
  ##
105
- # Returns spoonerize array as a joined string.
70
+ # Spoonerized results as a joined string.
71
+ #
72
+ # @return [String]
106
73
  def to_s
107
74
  spoonerize.join(" ")
108
75
  end
109
76
 
110
77
  ##
111
- # Returns hash of the original words mapped to their flipped counterparts.
78
+ # Spoonerized results as a joined hash.
79
+ #
80
+ # @return [Hash]
112
81
  def to_h
113
82
  words.zip(spoonerize).to_h
114
83
  end
115
84
 
116
85
  ##
117
86
  # Same as to_h, but as json.
87
+ #
88
+ # @return [String]
118
89
  def to_json
119
90
  to_h.to_json
120
91
  end
121
92
 
122
93
  ##
123
- # Has a config file been loaded?
94
+ # True if there are more than one non-excluded word to flip
124
95
  #
125
96
  # @return [Boolean]
126
- def config_file_loaded?
127
- @config_file_loaded
97
+ def enough_flippable_words?
98
+ words.each_index.count { |index| flippable?(index) } > 1
128
99
  end
129
100
 
130
101
  ##
131
- # Returns true if there are more than one non-excluded word to flip
132
- def enough_flippable_words?
133
- (words - all_excluded_words).size > 1
102
+ # Saves the flipped words to the log file, along with the options
103
+ #
104
+ # @return [Array]
105
+ def save
106
+ log.write([words.join(" "), to_s, options.join(", ")])
134
107
  end
135
108
 
136
109
  ##
137
- # Should the lazy words be excluded?
138
- def lazy?
139
- @lazy
110
+ # Array of words to exclude by combining two arrays:
111
+ # * Any user-passed words, stored in +config.excluded_words+
112
+ # * Any lazy words, if lazy mode is true
113
+ #
114
+ # @return [Array]
115
+ def all_excluded_words
116
+ (config.excluded_words + (
117
+ config.lazy ? config.lazy_words : []
118
+ )).map(&:downcase)
119
+ end
120
+
121
+ private
122
+
123
+ def normalize_words(words)
124
+ if words.size == 1 && words.first.is_a?(Array)
125
+ warn(
126
+ "Passing words as an array is deprecated and will be removed in Spoonerize 1.0. " \
127
+ "Pass words as positional arguments instead."
128
+ )
129
+ words = words.first
130
+ end
131
+
132
+ unless words.all? { |word| word.is_a?(String) }
133
+ raise ArgumentError, "Words must be strings"
134
+ end
135
+
136
+ words
140
137
  end
141
138
 
142
139
  ##
143
- # Should the words flip the other direction?
144
- def reverse?
145
- @reverse
140
+ # Main flipping method. Creates the replacement word from the next
141
+ # non-excluded word's leading consonants, and the current word's first
142
+ # vowel sound through the end of the word.
143
+ def flip_words(word, idx) # :nodoc:
144
+ return word unless flippable?(idx)
145
+
146
+ bumper = Bumper.new(idx, words.size, config.reverse)
147
+ bumper.bump until flippable?(bumper.value)
148
+ leading_consonants(words[bumper.value]) + retained_suffix(word)
146
149
  end
147
150
 
148
151
  ##
149
- # Saves the flipped words to the log file, along with the options
150
- def save
151
- log.write([words.join(" "), to_s, options.join(", ")])
152
+ # Returns true if word[index] is in the excluded_words array
153
+ def excluded?(index) # :nodoc:
154
+ all_excluded_words.include?(words[index])
152
155
  end
153
156
 
154
157
  ##
155
- # Returns an array of words to exclude by combining three arrays:
156
- # * Any word in the passed arguments that's only one character
157
- # * Any user-passed words, stored in +excluded_words+
158
- # * If lazy-mode, the LAZY_WORDS from yaml file are added
159
- def all_excluded_words
160
- (excluded_words + (lazy? ? LAZY_WORDS : [])).map(&:downcase)
158
+ # Returns true if word[index] can participate in a spoonerism.
159
+ def flippable?(index) # :nodoc:
160
+ return false if excluded?(index)
161
+
162
+ !config.consonants_only || consonant_sound_start?(words[index])
161
163
  end
162
164
 
163
165
  ##
164
- # Setter for +config_file+. Must be expanded in case the user uses `~` for
165
- # home.
166
- #
167
- # @param [String] file
168
- #
169
- # @return [String]
170
- def config_file=(config_file)
171
- @config_file = File.expand_path(config_file)
166
+ # Returns true when a word starts with a consonant sound.
167
+ def consonant_sound_start?(word) # :nodoc:
168
+ return false if word.empty?
169
+
170
+ !vowel_sound_at?(word, 0)
172
171
  end
173
172
 
174
173
  ##
175
- # Loads the config file
176
- #
177
- # @return [Hash] The config options
178
- def load_config_file
179
- raise "No config file set" if config_file.nil?
180
- raise "File #{config_file} does not exist" unless File.file?(config_file)
181
- @config = YAML.load_file(config_file)
182
- @config_file_loaded = true
183
- @config.each { |k, v| send(:"#{k}=", v) }
174
+ # Returns the consonant group a word contributes to another word.
175
+ def leading_consonants(word) # :nodoc:
176
+ return "qu" if word.start_with?("qu")
177
+ return "y" if initial_y_consonant?(word)
178
+
179
+ index = word.length.times.find do |letter_index|
180
+ !CONSONANT_LETTERS.include?(word[letter_index])
181
+ end
182
+
183
+ index ? word[0...index] : word
184
184
  end
185
185
 
186
- private
186
+ ##
187
+ # Returns the part of a word kept after dropping its leading consonants.
188
+ def retained_suffix(word) # :nodoc:
189
+ index = first_vowel_sound_index(word)
190
+
191
+ index ? word[index..] : ""
192
+ end
187
193
 
188
194
  ##
189
- # Main flipping method. Creates the replacement word from the next
190
- # non-excluded word's leading syllables, and the current word's first vowels
191
- # through the end of the word.
192
- def flip_words(word, idx) # :nodoc:
193
- return word if excluded?(idx)
194
- bumper = Bumper.new(idx, words.size, reverse?)
195
- bumper.bump while excluded?(bumper.value)
196
- words[bumper.value].match(consonants).to_s + word.match(vowels).to_s
195
+ # Returns the first index where a word starts sounding vowel-like.
196
+ def first_vowel_sound_index(word) # :nodoc:
197
+ word.length.times.find { |index| vowel_sound_at?(word, index) }
197
198
  end
198
199
 
199
200
  ##
200
- # Returns true if word[index] is in the excluded_words array
201
- def excluded?(index) # :nodoc:
202
- all_excluded_words.include?(words[index])
201
+ # Returns true when the letter at index starts a vowel sound.
202
+ def vowel_sound_at?(word, index) # :nodoc:
203
+ letter = word[index]
204
+
205
+ VOWEL_LETTERS.include?(letter) ||
206
+ (letter == "u" && word[index - 1] != "q") ||
207
+ y_vowel_sound_at?(word, index)
203
208
  end
204
209
 
205
210
  ##
206
- # Returns regex to match first vowels through the rest of the word
207
- def vowels # :nodoc:
208
- /((?<!q)u|[aeio]|(?<=[bcdfghjklmnprstvwxz])y).*$/
211
+ # Initial y is vowel-like before a consonant; later y is vowel-like after a
212
+ # consonant.
213
+ def y_vowel_sound_at?(word, index) # :nodoc:
214
+ return false unless word[index] == "y"
215
+
216
+ if index.zero?
217
+ next_letter = word[index + 1]
218
+
219
+ next_letter && Y_FOLLOWING_CONSONANTS.include?(next_letter)
220
+ else
221
+ CONSONANT_LETTERS.include?(word[index - 1])
222
+ end
209
223
  end
210
224
 
211
225
  ##
212
- # Returns regex to match leading consonants
213
- def consonants # :nodoc:
214
- /^(y|[bcdfghjklmnprstvwxz]+|qu)/
226
+ # Initial y is consonant-like by itself or before a vowel sound.
227
+ def initial_y_consonant?(word) # :nodoc:
228
+ word == "y" || (word.start_with?("y") && vowel_sound_at?(word, 1))
215
229
  end
216
230
 
217
231
  ##
218
232
  # Creates and memoizes instance of the log file.
219
233
  def log # :nodoc:
220
- @log ||= Spoonerize::Log.new(logfile_name)
234
+ @log ||= Spoonerize::Log.new(config.logfile_name)
221
235
  end
222
236
 
223
237
  ##
224
- # the options as a string
238
+ # The options that were passed at runtime as a string
225
239
  def options # :nodoc:
226
240
  [].tap do |o|
227
- o << "Lazy" if lazy?
228
- o << "Reverse" if reverse?
229
- o << "Exclude [#{all_excluded_words.join(", ")}]" if excluded_words.any?
241
+ o << "Lazy" if config.lazy
242
+ o << "Consonants Only" if config.consonants_only
243
+ o << "Reverse" if config.reverse
244
+ if config.excluded_words.any?
245
+ o << "Exclude [#{config.excluded_words.join(", ")}]"
246
+ end
230
247
  o << "No Options" if o.empty?
231
248
  end
232
249
  end
@@ -9,19 +9,19 @@ module Spoonerize
9
9
  # Major version.
10
10
  #
11
11
  # @return [Integer]
12
- MAJOR = 0
12
+ MAJOR = 1
13
13
 
14
14
  ##
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 1
18
+ MINOR = 0
19
19
 
20
20
  ##
21
21
  # Patch version.
22
22
  #
23
23
  # @return [Integer]
24
- PATCH = 3
24
+ PATCH = 0
25
25
 
26
26
  module_function
27
27
 
data/lib/spoonerize.rb CHANGED
@@ -1,4 +1,6 @@
1
- require "yaml"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spoonerize/config"
2
4
  require_relative "spoonerize/spoonerism"
3
5
  require_relative "spoonerize/bumper"
4
6
  require_relative "spoonerize/version"
@@ -9,12 +11,57 @@ require_relative "spoonerize/cli"
9
11
  # The main namespace for the gem.
10
12
  module Spoonerize
11
13
  ##
12
- # The error exception raised when there are not enough flippable words.
13
- JakPibError = Class.new(StandardError)
14
+ # Has the config file been loaded?
15
+ @config_file_loaded = false
16
+
17
+ module_function
18
+
19
+ ##
20
+ # Method for accessing the configuration.
21
+ #
22
+ # @return [Spoonerize::Config]
23
+ def config
24
+ @config || reset_config
25
+ end
26
+
27
+ ##
28
+ # Reset all configuration values to their defaults.
29
+ #
30
+ # @return [Spoonerize::Config]
31
+ def reset_config
32
+ @config_file_loaded = false
33
+ @config = Spoonerize::Config.new
34
+ end
14
35
 
15
36
  ##
16
- # Excluded words from config files.
17
- LAZY_WORDS = YAML.load_file(
18
- File.join(File.dirname(__FILE__), "config", "lazy_words.yml")
19
- ).freeze
37
+ # Allows for configuration via a block. Useful when making config files.
38
+ #
39
+ # @example
40
+ # Spoonerize.configure { |s| s.lazy = true }
41
+ def configure
42
+ yield config
43
+ end
44
+
45
+ ##
46
+ # Has a config file been loaded?
47
+ #
48
+ # @return [Boolean]
49
+ def config_file_loaded?
50
+ @config_file_loaded
51
+ end
52
+
53
+ ##
54
+ # Loads a config file.
55
+ #
56
+ # @param [String] config_file
57
+ #
58
+ # @return [String] file
59
+ def load_config_file(config_file)
60
+ ::File.expand_path(config_file).tap do |file|
61
+ raise "File #{file} does not exist." unless ::File.file?(file)
62
+
63
+ @config_file_loaded = true
64
+ load file
65
+ end
66
+ end
20
67
  end
data/spoonerize.gemspec CHANGED
@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "csv"
32
32
  spec.add_development_dependency "rdoc"
33
33
  spec.add_development_dependency "rake", "~> 13.0", ">= 13.0.1"
34
+ spec.add_development_dependency "standard", "= 1.54.0"
34
35
  spec.add_development_dependency "test-unit", "~> 3.3", ">= 3.3.5"
35
36
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spoonerize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -57,6 +57,20 @@ dependencies:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
59
  version: 13.0.1
60
+ - !ruby/object:Gem::Dependency
61
+ name: standard
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.54.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '='
72
+ - !ruby/object:Gem::Version
73
+ version: 1.54.0
60
74
  - !ruby/object:Gem::Dependency
61
75
  name: test-unit
62
76
  requirement: !ruby/object:Gem::Requirement
@@ -93,10 +107,10 @@ files:
93
107
  - Rakefile
94
108
  - _config.yml
95
109
  - bin/spoonerize
96
- - lib/config/lazy_words.yml
97
110
  - lib/spoonerize.rb
98
111
  - lib/spoonerize/bumper.rb
99
112
  - lib/spoonerize/cli.rb
113
+ - lib/spoonerize/config.rb
100
114
  - lib/spoonerize/log.rb
101
115
  - lib/spoonerize/spoonerism.rb
102
116
  - lib/spoonerize/version.rb
@@ -1,22 +0,0 @@
1
- #=================================================================#
2
- # File: lazy_words.yml #
3
- # Description: Words to be excluded when "lazy" mode is enabled #
4
- # #
5
- # Author: Evan Gray #
6
- #=================================================================#
7
-
8
- - i
9
- - a
10
- - an
11
- - and
12
- - in
13
- - of
14
- - the
15
- - my
16
- - your
17
- - his
18
- - her
19
- - him
20
- - hers
21
- - to
22
- - is