spoonerize 0.2.0 → 2.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 +4 -4
- data/Gemfile.lock +75 -1
- data/README.md +62 -19
- data/Rakefile +1 -1
- data/bin/spoonerize-web +5 -0
- data/lib/spoonerize/cli.rb +15 -12
- data/lib/spoonerize/config.rb +34 -0
- data/lib/spoonerize/spoonerism.rb +153 -24
- data/lib/spoonerize/version.rb +2 -2
- data/lib/spoonerize/web/cli.rb +72 -0
- data/lib/spoonerize/web/public/styles.css +178 -0
- data/lib/spoonerize/web/views/index.erb +61 -0
- data/lib/spoonerize/web/views/layout.erb +20 -0
- data/lib/spoonerize/web.rb +121 -0
- data/lib/spoonerize.rb +11 -1
- data/spoonerize.gemspec +5 -0
- metadata +65 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ad82a22fd996a0e5af1653a6ed66f27863da252331b4fa58865980d6a27b063
|
|
4
|
+
data.tar.gz: d206494ce8cc6e9dabbda3ab86389d080e57d1baf0a003800ba6edd3fe3c3b13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8b89f4ea9ca38fb0ee9773ca55e6fb654e94d4135f4b4748abc91c90fe014e3c1bd90e6f0c9c4f9d74f2efcf64b7bfd8b488ebcf3cf92dc40d9c2616089d30e
|
|
7
|
+
data.tar.gz: 1c81200ffd8922cc1384c11b9a8a8766eae3d59f3301c544dbf9a6928617c85d231f931cec768d4ad4c7c8fcbc697ca94d666c182340974c7d406411c11dab34
|
data/Gemfile.lock
CHANGED
|
@@ -1,28 +1,101 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
spoonerize (
|
|
4
|
+
spoonerize (2.0.0)
|
|
5
5
|
csv
|
|
6
|
+
puma (~> 8.0)
|
|
7
|
+
rackup (~> 2.3)
|
|
8
|
+
sinatra (~> 4.2)
|
|
6
9
|
|
|
7
10
|
GEM
|
|
8
11
|
remote: https://rubygems.org/
|
|
9
12
|
specs:
|
|
13
|
+
ast (2.4.3)
|
|
14
|
+
base64 (0.3.0)
|
|
10
15
|
csv (3.3.5)
|
|
11
16
|
date (3.5.1)
|
|
12
17
|
erb (6.0.4)
|
|
18
|
+
json (2.20.0)
|
|
19
|
+
language_server-protocol (3.17.0.5)
|
|
20
|
+
lint_roller (1.1.0)
|
|
21
|
+
logger (1.7.0)
|
|
22
|
+
mustermann (3.1.1)
|
|
23
|
+
nio4r (2.7.5)
|
|
24
|
+
parallel (1.28.0)
|
|
25
|
+
parser (3.3.11.1)
|
|
26
|
+
ast (~> 2.4.1)
|
|
27
|
+
racc
|
|
13
28
|
power_assert (3.0.1)
|
|
29
|
+
prism (1.9.0)
|
|
14
30
|
psych (5.4.0)
|
|
15
31
|
date
|
|
16
32
|
stringio
|
|
33
|
+
puma (8.0.2)
|
|
34
|
+
nio4r (~> 2.0)
|
|
35
|
+
racc (1.8.1)
|
|
36
|
+
rack (3.2.6)
|
|
37
|
+
rack-protection (4.2.1)
|
|
38
|
+
base64 (>= 0.1.0)
|
|
39
|
+
logger (>= 1.6.0)
|
|
40
|
+
rack (>= 3.0.0, < 4)
|
|
41
|
+
rack-session (2.1.2)
|
|
42
|
+
base64 (>= 0.1.0)
|
|
43
|
+
rack (>= 3.0.0)
|
|
44
|
+
rackup (2.3.1)
|
|
45
|
+
rack (>= 3)
|
|
46
|
+
rainbow (3.1.1)
|
|
17
47
|
rake (13.4.2)
|
|
18
48
|
rdoc (7.2.0)
|
|
19
49
|
erb
|
|
20
50
|
psych (>= 4.0.0)
|
|
21
51
|
tsort
|
|
52
|
+
regexp_parser (2.12.0)
|
|
53
|
+
rubocop (1.84.2)
|
|
54
|
+
json (~> 2.3)
|
|
55
|
+
language_server-protocol (~> 3.17.0.2)
|
|
56
|
+
lint_roller (~> 1.1.0)
|
|
57
|
+
parallel (~> 1.10)
|
|
58
|
+
parser (>= 3.3.0.2)
|
|
59
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
60
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
61
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
62
|
+
ruby-progressbar (~> 1.7)
|
|
63
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
64
|
+
rubocop-ast (1.49.1)
|
|
65
|
+
parser (>= 3.3.7.2)
|
|
66
|
+
prism (~> 1.7)
|
|
67
|
+
rubocop-performance (1.26.1)
|
|
68
|
+
lint_roller (~> 1.1)
|
|
69
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
70
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
71
|
+
ruby-progressbar (1.13.0)
|
|
72
|
+
sinatra (4.2.1)
|
|
73
|
+
logger (>= 1.6.0)
|
|
74
|
+
mustermann (~> 3.0)
|
|
75
|
+
rack (>= 3.0.0, < 4)
|
|
76
|
+
rack-protection (= 4.2.1)
|
|
77
|
+
rack-session (>= 2.0.0, < 3)
|
|
78
|
+
tilt (~> 2.0)
|
|
79
|
+
standard (1.54.0)
|
|
80
|
+
language_server-protocol (~> 3.17.0.2)
|
|
81
|
+
lint_roller (~> 1.0)
|
|
82
|
+
rubocop (~> 1.84.0)
|
|
83
|
+
standard-custom (~> 1.0.0)
|
|
84
|
+
standard-performance (~> 1.8)
|
|
85
|
+
standard-custom (1.0.2)
|
|
86
|
+
lint_roller (~> 1.0)
|
|
87
|
+
rubocop (~> 1.50)
|
|
88
|
+
standard-performance (1.9.0)
|
|
89
|
+
lint_roller (~> 1.1)
|
|
90
|
+
rubocop-performance (~> 1.26.0)
|
|
22
91
|
stringio (3.2.0)
|
|
23
92
|
test-unit (3.7.8)
|
|
24
93
|
power_assert
|
|
94
|
+
tilt (2.7.0)
|
|
25
95
|
tsort (0.2.0)
|
|
96
|
+
unicode-display_width (3.2.0)
|
|
97
|
+
unicode-emoji (~> 4.1)
|
|
98
|
+
unicode-emoji (4.2.0)
|
|
26
99
|
|
|
27
100
|
PLATFORMS
|
|
28
101
|
arm64-darwin-25
|
|
@@ -33,6 +106,7 @@ DEPENDENCIES
|
|
|
33
106
|
rake (~> 13.0, >= 13.0.1)
|
|
34
107
|
rdoc
|
|
35
108
|
spoonerize!
|
|
109
|
+
standard (= 1.54.0)
|
|
36
110
|
test-unit (~> 3.3, >= 3.3.5)
|
|
37
111
|
|
|
38
112
|
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
|
|
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
|
-
|
|
87
|
+
tot shoo nabby
|
|
88
|
+
Saving...
|
|
86
89
|
|
|
87
90
|
$ spoonerize -rs not too shabby
|
|
88
|
-
|
|
91
|
+
shot noo tabby
|
|
92
|
+
Saving...
|
|
89
93
|
|
|
90
94
|
$ spoonerize -p
|
|
91
95
|
not too shabby | tot shoo nabby | No Options
|
|
@@ -96,22 +100,48 @@ Here is a list of all available options:
|
|
|
96
100
|
|
|
97
101
|
```
|
|
98
102
|
-r, --[no-]reverse Reverse flipping
|
|
99
|
-
-l, --[no-]lazy Skip
|
|
103
|
+
-l, --[no-]lazy Skip common words
|
|
104
|
+
-c, --[no-]consonants-only Only flip consonant-starting words
|
|
100
105
|
-m, --[no-]map Print words mapping
|
|
101
|
-
-p, --[no-]print
|
|
106
|
+
-p, --[no-]print-log Print all entries in the log
|
|
102
107
|
-s, --[no-]save Save results in log
|
|
103
|
-
--exclude=
|
|
108
|
+
--exclude=WORD Words to skip
|
|
104
109
|
```
|
|
105
110
|
|
|
111
|
+
## Web Usage
|
|
112
|
+
The gem also installs a small Sinatra app:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
spoonerize-web
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
By default, Sinatra starts on its normal local development address. You can
|
|
119
|
+
choose a host or port when you need to:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
spoonerize-web --host 127.0.0.1 --port 9292
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Open the printed local URL in your browser, enter a phrase, choose any options,
|
|
126
|
+
and submit the form. The page reloads with the spoonerized result and keeps
|
|
127
|
+
your phrase and options in the form. Check "Save result" to write a successful
|
|
128
|
+
result to the configured log file.
|
|
129
|
+
|
|
130
|
+
The web app ships with the main gem for now, so `gem install spoonerize`
|
|
131
|
+
installs both `spoonerize` and `spoonerize-web`.
|
|
132
|
+
|
|
106
133
|
### Config File
|
|
107
134
|
You can create a Ruby config file called `~/.spoonerizerc`. The CLI loads this
|
|
108
135
|
file automatically before it parses command-line options, so options set in the
|
|
109
|
-
file can still be overridden at runtime by executable flags.
|
|
136
|
+
file can still be overridden at runtime by executable flags. The web app loads
|
|
137
|
+
the same file when it starts, and uses those values for the initial form
|
|
138
|
+
defaults.
|
|
110
139
|
|
|
111
140
|
```ruby
|
|
112
141
|
Spoonerize.configure do |config|
|
|
113
142
|
config.excluded_words = []
|
|
114
143
|
config.lazy = false
|
|
144
|
+
config.consonants_only = false
|
|
115
145
|
config.reverse = false
|
|
116
146
|
config.logfile_name = File.expand_path("~/.cache/spoonerize/spoonerize.csv")
|
|
117
147
|
end
|
|
@@ -122,50 +152,63 @@ Because the file is Ruby, you can set only the values you want to change.
|
|
|
122
152
|
## API
|
|
123
153
|
The API is [fully
|
|
124
154
|
documented](https://evanthegrayt.github.io/spoonerize/), but below
|
|
125
|
-
are some quick examples of how you could use this in your
|
|
155
|
+
are some quick examples of how you could use this in your Ruby code.
|
|
126
156
|
|
|
127
157
|
```ruby
|
|
128
158
|
require 'spoonerize'
|
|
129
159
|
|
|
130
|
-
spoonerism = Spoonerize::Spoonerism.new(
|
|
160
|
+
spoonerism = Spoonerize::Spoonerism.new("not", "too", "shabby")
|
|
131
161
|
|
|
132
162
|
spoonerism.to_s
|
|
133
163
|
# => tot shoo nabby
|
|
134
164
|
|
|
135
|
-
Spoonerize.
|
|
136
|
-
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
spoonerism.to_s
|
|
165
|
+
reversed = Spoonerize::Spoonerism.new("not", "too", "shabby", reverse: true)
|
|
166
|
+
reversed.to_s
|
|
140
167
|
# => shot noo tabby
|
|
141
168
|
|
|
142
169
|
Spoonerize.configure do |config|
|
|
143
170
|
config.logfile_name = File.expand_path("~/.cache/spoonerize/spoonerize.csv")
|
|
144
171
|
end
|
|
145
|
-
|
|
172
|
+
Spoonerize::Spoonerism.new("not", "too", "shabby").save
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
To leave vowel-starting words alone, enable consonants-only mode:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
Spoonerize::Spoonerism.new("turn", "up", "son", consonants_only: true).to_s
|
|
179
|
+
# => surn up ton
|
|
146
180
|
```
|
|
147
181
|
|
|
148
|
-
You can also configure
|
|
182
|
+
You can also configure global defaults before creating a spoonerism:
|
|
149
183
|
|
|
150
184
|
```ruby
|
|
151
185
|
Spoonerize.configure do |config|
|
|
152
186
|
config.reverse = true
|
|
153
187
|
end
|
|
154
188
|
|
|
155
|
-
s = Spoonerize::Spoonerism.new(
|
|
189
|
+
s = Spoonerize::Spoonerism.new("not", "too", "shabby")
|
|
156
190
|
s.spoonerize
|
|
157
191
|
# => shot noo tabby
|
|
158
192
|
```
|
|
159
193
|
|
|
194
|
+
Options passed directly to `Spoonerize::Spoonerism.new` only apply to that
|
|
195
|
+
instance.
|
|
196
|
+
|
|
197
|
+
Passing words as an array is deprecated and will be removed in Spoonerize 1.0:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
Spoonerize::Spoonerism.new(%w[not too shabby])
|
|
201
|
+
```
|
|
202
|
+
|
|
160
203
|
Or load a config file manually:
|
|
161
204
|
|
|
162
205
|
```ruby
|
|
163
206
|
Spoonerize.load_config_file("~/.spoonerizerc")
|
|
164
|
-
s = Spoonerize::Spoonerism.new(
|
|
207
|
+
s = Spoonerize::Spoonerism.new("not", "too", "shabby")
|
|
165
208
|
```
|
|
166
209
|
|
|
167
210
|
## Self Promotion
|
|
168
211
|
I do these projects for fun, and I enjoy knowing that they're helpful to people.
|
|
169
212
|
Consider starring [the repository](https://github.com/evanthegrayt/spoonerize)
|
|
170
213
|
if you like it! If you love it, follow me [on
|
|
171
|
-
|
|
214
|
+
GitHub](https://github.com/evanthegrayt)!
|
data/Rakefile
CHANGED
data/bin/spoonerize-web
ADDED
data/lib/spoonerize/cli.rb
CHANGED
|
@@ -7,10 +7,10 @@ module Spoonerize
|
|
|
7
7
|
# The class for handling the command-line interface.
|
|
8
8
|
class Cli
|
|
9
9
|
##
|
|
10
|
-
# The config file the
|
|
10
|
+
# The config file the CLI loads before parsing runtime options.
|
|
11
11
|
#
|
|
12
12
|
# @return [String]
|
|
13
|
-
CONFIG_FILE =
|
|
13
|
+
CONFIG_FILE = Spoonerize::CONFIG_FILE
|
|
14
14
|
|
|
15
15
|
##
|
|
16
16
|
# Creates an instance of +Spoonerism+ and runs what the user requested.
|
|
@@ -40,10 +40,10 @@ module Spoonerize
|
|
|
40
40
|
attr_reader :options
|
|
41
41
|
|
|
42
42
|
##
|
|
43
|
-
#
|
|
43
|
+
# Overrides after reading config file and parsing ARGV.
|
|
44
44
|
#
|
|
45
|
-
# @return [
|
|
46
|
-
attr_reader :
|
|
45
|
+
# @return [Hash]
|
|
46
|
+
attr_reader :overrides
|
|
47
47
|
|
|
48
48
|
##
|
|
49
49
|
# Create instance of +Cli+
|
|
@@ -57,7 +57,7 @@ module Spoonerize
|
|
|
57
57
|
@save = false
|
|
58
58
|
@print_log = false
|
|
59
59
|
@options = options
|
|
60
|
-
@
|
|
60
|
+
@overrides = get_overrides
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
##
|
|
@@ -65,7 +65,7 @@ module Spoonerize
|
|
|
65
65
|
#
|
|
66
66
|
# @return [Spoonerize::Spoonerism]
|
|
67
67
|
def spoonerism
|
|
68
|
-
@spoonerism ||= Spoonerism.new(options)
|
|
68
|
+
@spoonerism ||= Spoonerism.new(*options, **overrides)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
##
|
|
@@ -124,15 +124,18 @@ module Spoonerize
|
|
|
124
124
|
|
|
125
125
|
##
|
|
126
126
|
# Read in args and set options
|
|
127
|
-
def
|
|
127
|
+
def get_overrides # :nodoc:
|
|
128
128
|
{}.tap do |prefs|
|
|
129
129
|
OptionParser.new do |o|
|
|
130
130
|
o.version = ::Spoonerize::Version.to_s
|
|
131
131
|
o.on("-r", "--[no-]reverse", "Reverse flipping") do |v|
|
|
132
|
-
|
|
132
|
+
prefs[:reverse] = v
|
|
133
133
|
end
|
|
134
|
-
o.on("-l", "--[no-]lazy", "Skip
|
|
135
|
-
|
|
134
|
+
o.on("-l", "--[no-]lazy", "Skip common words") do |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
|
|
136
139
|
end
|
|
137
140
|
o.on("-m", "--[no-]map", "Print words mapping") do |v|
|
|
138
141
|
@map = v
|
|
@@ -144,7 +147,7 @@ module Spoonerize
|
|
|
144
147
|
@save = v
|
|
145
148
|
end
|
|
146
149
|
o.on("--exclude=WORD", Array, "Words to skip") do |v|
|
|
147
|
-
|
|
150
|
+
prefs[:excluded_words] = v
|
|
148
151
|
end
|
|
149
152
|
end.parse!(options)
|
|
150
153
|
end
|
data/lib/spoonerize/config.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Spoonerize
|
|
4
|
+
##
|
|
5
|
+
# Runtime options used by the CLI and Spoonerism instances.
|
|
4
6
|
class Config
|
|
5
7
|
##
|
|
6
8
|
# Lazy mode. If true, words in +lazy_words+ will not be altered.
|
|
@@ -20,6 +22,12 @@ module Spoonerize
|
|
|
20
22
|
# @return [Array]
|
|
21
23
|
attr_accessor :excluded_words
|
|
22
24
|
|
|
25
|
+
##
|
|
26
|
+
# When true, only consonant-starting words are eligible to be flipped.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
attr_accessor :consonants_only
|
|
30
|
+
|
|
23
31
|
##
|
|
24
32
|
# When true, reverse the order of the flipping. Only makes a difference
|
|
25
33
|
# when there are more than two flip-able words.
|
|
@@ -41,10 +49,36 @@ module Spoonerize
|
|
|
41
49
|
@lazy = false
|
|
42
50
|
@lazy_words = %w[i a an and in of the my your his her him hers to is]
|
|
43
51
|
@excluded_words = []
|
|
52
|
+
@consonants_only = false
|
|
44
53
|
@reverse = false
|
|
45
54
|
@logfile_name = File.expand_path(
|
|
46
55
|
File.join(ENV["HOME"], ".cache", "spoonerize", "spoonerize.csv")
|
|
47
56
|
)
|
|
48
57
|
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Create a copy of the current config with optional overrides.
|
|
61
|
+
#
|
|
62
|
+
# @return [Spoonerize::Config]
|
|
63
|
+
def with(**overrides)
|
|
64
|
+
self.class.new.tap do |config|
|
|
65
|
+
config.lazy = lazy
|
|
66
|
+
config.lazy_words = lazy_words.dup
|
|
67
|
+
config.excluded_words = excluded_words.dup
|
|
68
|
+
config.consonants_only = consonants_only
|
|
69
|
+
config.reverse = reverse
|
|
70
|
+
config.logfile_name = logfile_name
|
|
71
|
+
|
|
72
|
+
overrides.each do |key, value|
|
|
73
|
+
config.public_send(:"#{key}=", copy_value(value))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def copy_value(value)
|
|
81
|
+
value.is_a?(Array) ? value.dup : value
|
|
82
|
+
end
|
|
49
83
|
end
|
|
50
84
|
end
|
|
@@ -4,20 +4,69 @@ module Spoonerize
|
|
|
4
4
|
##
|
|
5
5
|
# The main word-flipper.
|
|
6
6
|
class Spoonerism
|
|
7
|
+
##
|
|
8
|
+
# Letters that always represent vowel sounds.
|
|
9
|
+
#
|
|
10
|
+
# @return [String]
|
|
11
|
+
VOWEL_LETTERS = "aeio"
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Letters that always represent consonant sounds.
|
|
15
|
+
#
|
|
16
|
+
# @return [String]
|
|
17
|
+
CONSONANT_LETTERS = "bcdfghjklmnprstvwxz"
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Letters that make an initial "y" act like a vowel sound.
|
|
21
|
+
#
|
|
22
|
+
# @return [String]
|
|
23
|
+
Y_FOLLOWING_CONSONANTS = "bcdfghjklmnpqrstvwxz"
|
|
24
|
+
|
|
7
25
|
##
|
|
8
26
|
# The words originally passed at initialization.
|
|
9
27
|
#
|
|
10
28
|
# @return [Array]
|
|
11
29
|
attr_reader :words
|
|
12
30
|
|
|
31
|
+
##
|
|
32
|
+
# Configuration values for this spoonerism.
|
|
33
|
+
#
|
|
34
|
+
# @return [Spoonerize::Config]
|
|
35
|
+
attr_reader :config
|
|
36
|
+
|
|
13
37
|
##
|
|
14
38
|
# Initialize instance.
|
|
15
39
|
#
|
|
16
|
-
# @param [Array] words
|
|
40
|
+
# @param [Array<String>] words Words to spoonerize. Passing a single array
|
|
41
|
+
# is deprecated and will be removed in Spoonerize 1.0.
|
|
42
|
+
# @param [Spoonerize::Config] config Base config to copy.
|
|
43
|
+
# @param [Boolean, nil] lazy Override lazy mode for this instance.
|
|
44
|
+
# @param [Array<String>, nil] lazy_words Override lazy words for this instance.
|
|
45
|
+
# @param [Array<String>, nil] excluded_words Override excluded words for this instance.
|
|
46
|
+
# @param [Boolean, nil] consonants_only Override consonants-only mode for this instance.
|
|
47
|
+
# @param [Boolean, nil] reverse Override reverse mode for this instance.
|
|
48
|
+
# @param [String, nil] logfile_name Override the log file path for this instance.
|
|
17
49
|
#
|
|
18
50
|
# @return [Spoonerize::Spoonerism]
|
|
19
|
-
def initialize(
|
|
20
|
-
|
|
51
|
+
def initialize(
|
|
52
|
+
*words,
|
|
53
|
+
config: Spoonerize.config,
|
|
54
|
+
lazy: nil,
|
|
55
|
+
lazy_words: nil,
|
|
56
|
+
excluded_words: nil,
|
|
57
|
+
consonants_only: nil,
|
|
58
|
+
reverse: nil,
|
|
59
|
+
logfile_name: nil
|
|
60
|
+
)
|
|
61
|
+
@words = normalize_words(words).map(&:downcase)
|
|
62
|
+
@config = config.with(**{
|
|
63
|
+
lazy: lazy,
|
|
64
|
+
lazy_words: lazy_words,
|
|
65
|
+
excluded_words: excluded_words,
|
|
66
|
+
consonants_only: consonants_only,
|
|
67
|
+
reverse: reverse,
|
|
68
|
+
logfile_name: logfile_name
|
|
69
|
+
}.reject { |_, value| value.nil? })
|
|
21
70
|
end
|
|
22
71
|
|
|
23
72
|
##
|
|
@@ -60,7 +109,7 @@ module Spoonerize
|
|
|
60
109
|
#
|
|
61
110
|
# @return [Boolean]
|
|
62
111
|
def enough_flippable_words?
|
|
63
|
-
|
|
112
|
+
words.each_index.count { |index| flippable?(index) } > 1
|
|
64
113
|
end
|
|
65
114
|
|
|
66
115
|
##
|
|
@@ -73,27 +122,44 @@ module Spoonerize
|
|
|
73
122
|
|
|
74
123
|
##
|
|
75
124
|
# Array of words to exclude by combining two arrays:
|
|
76
|
-
# * Any user-passed words, stored in +
|
|
125
|
+
# * Any user-passed words, stored in +config.excluded_words+
|
|
77
126
|
# * Any lazy words, if lazy mode is true
|
|
78
127
|
#
|
|
79
128
|
# @return [Array]
|
|
80
129
|
def all_excluded_words
|
|
81
|
-
(
|
|
82
|
-
|
|
130
|
+
(config.excluded_words + (
|
|
131
|
+
config.lazy ? config.lazy_words : []
|
|
83
132
|
)).map(&:downcase)
|
|
84
133
|
end
|
|
85
134
|
|
|
86
135
|
private
|
|
87
136
|
|
|
137
|
+
def normalize_words(words)
|
|
138
|
+
if words.size == 1 && words.first.is_a?(Array)
|
|
139
|
+
warn(
|
|
140
|
+
"Passing words as an array is deprecated and will be removed in Spoonerize 1.0. " \
|
|
141
|
+
"Pass words as positional arguments instead."
|
|
142
|
+
)
|
|
143
|
+
words = words.first
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
unless words.all? { |word| word.is_a?(String) }
|
|
147
|
+
raise ArgumentError, "Words must be strings"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
words
|
|
151
|
+
end
|
|
152
|
+
|
|
88
153
|
##
|
|
89
154
|
# Main flipping method. Creates the replacement word from the next
|
|
90
|
-
# non-excluded word's leading
|
|
91
|
-
# through the end of the word.
|
|
155
|
+
# non-excluded word's leading consonants, and the current word's first
|
|
156
|
+
# vowel sound through the end of the word.
|
|
92
157
|
def flip_words(word, idx) # :nodoc:
|
|
93
|
-
return word
|
|
94
|
-
|
|
95
|
-
bumper
|
|
96
|
-
|
|
158
|
+
return word unless flippable?(idx)
|
|
159
|
+
|
|
160
|
+
bumper = Bumper.new(idx, words.size, config.reverse)
|
|
161
|
+
bumper.bump until flippable?(bumper.value)
|
|
162
|
+
leading_consonants(words[bumper.value]) + retained_suffix(word)
|
|
97
163
|
end
|
|
98
164
|
|
|
99
165
|
##
|
|
@@ -103,31 +169,94 @@ module Spoonerize
|
|
|
103
169
|
end
|
|
104
170
|
|
|
105
171
|
##
|
|
106
|
-
# Returns
|
|
107
|
-
def
|
|
108
|
-
|
|
172
|
+
# Returns true if word[index] can participate in a spoonerism.
|
|
173
|
+
def flippable?(index) # :nodoc:
|
|
174
|
+
return false if excluded?(index)
|
|
175
|
+
|
|
176
|
+
!config.consonants_only || consonant_sound_start?(words[index])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
##
|
|
180
|
+
# Returns true when a word starts with a consonant sound.
|
|
181
|
+
def consonant_sound_start?(word) # :nodoc:
|
|
182
|
+
return false if word.empty?
|
|
183
|
+
|
|
184
|
+
!vowel_sound_at?(word, 0)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
##
|
|
188
|
+
# Returns the consonant group a word contributes to another word.
|
|
189
|
+
def leading_consonants(word) # :nodoc:
|
|
190
|
+
return "qu" if word.start_with?("qu")
|
|
191
|
+
return "y" if initial_y_consonant?(word)
|
|
192
|
+
|
|
193
|
+
index = word.length.times.find do |letter_index|
|
|
194
|
+
!CONSONANT_LETTERS.include?(word[letter_index])
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
index ? word[0...index] : word
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
##
|
|
201
|
+
# Returns the part of a word kept after dropping its leading consonants.
|
|
202
|
+
def retained_suffix(word) # :nodoc:
|
|
203
|
+
index = first_vowel_sound_index(word)
|
|
204
|
+
|
|
205
|
+
index ? word[index..] : ""
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
##
|
|
209
|
+
# Returns the first index where a word starts sounding vowel-like.
|
|
210
|
+
def first_vowel_sound_index(word) # :nodoc:
|
|
211
|
+
word.length.times.find { |index| vowel_sound_at?(word, index) }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
##
|
|
215
|
+
# Returns true when the letter at index starts a vowel sound.
|
|
216
|
+
def vowel_sound_at?(word, index) # :nodoc:
|
|
217
|
+
letter = word[index]
|
|
218
|
+
|
|
219
|
+
VOWEL_LETTERS.include?(letter) ||
|
|
220
|
+
(letter == "u" && word[index - 1] != "q") ||
|
|
221
|
+
y_vowel_sound_at?(word, index)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
##
|
|
225
|
+
# Initial y is vowel-like before a consonant; later y is vowel-like after a
|
|
226
|
+
# consonant.
|
|
227
|
+
def y_vowel_sound_at?(word, index) # :nodoc:
|
|
228
|
+
return false unless word[index] == "y"
|
|
229
|
+
|
|
230
|
+
if index.zero?
|
|
231
|
+
next_letter = word[index + 1]
|
|
232
|
+
|
|
233
|
+
next_letter && Y_FOLLOWING_CONSONANTS.include?(next_letter)
|
|
234
|
+
else
|
|
235
|
+
CONSONANT_LETTERS.include?(word[index - 1])
|
|
236
|
+
end
|
|
109
237
|
end
|
|
110
238
|
|
|
111
239
|
##
|
|
112
|
-
#
|
|
113
|
-
def
|
|
114
|
-
|
|
240
|
+
# Initial y is consonant-like by itself or before a vowel sound.
|
|
241
|
+
def initial_y_consonant?(word) # :nodoc:
|
|
242
|
+
word == "y" || (word.start_with?("y") && vowel_sound_at?(word, 1))
|
|
115
243
|
end
|
|
116
244
|
|
|
117
245
|
##
|
|
118
246
|
# Creates and memoizes instance of the log file.
|
|
119
247
|
def log # :nodoc:
|
|
120
|
-
@log ||= Spoonerize::Log.new(
|
|
248
|
+
@log ||= Spoonerize::Log.new(config.logfile_name)
|
|
121
249
|
end
|
|
122
250
|
|
|
123
251
|
##
|
|
124
252
|
# The options that were passed at runtime as a string
|
|
125
253
|
def options # :nodoc:
|
|
126
254
|
[].tap do |o|
|
|
127
|
-
o << "Lazy" if
|
|
128
|
-
o << "
|
|
129
|
-
if
|
|
130
|
-
|
|
255
|
+
o << "Lazy" if config.lazy
|
|
256
|
+
o << "Consonants Only" if config.consonants_only
|
|
257
|
+
o << "Reverse" if config.reverse
|
|
258
|
+
if config.excluded_words.any?
|
|
259
|
+
o << "Exclude [#{config.excluded_words.join(", ")}]"
|
|
131
260
|
end
|
|
132
261
|
o << "No Options" if o.empty?
|
|
133
262
|
end
|
data/lib/spoonerize/version.rb
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
require_relative "../web"
|
|
6
|
+
|
|
7
|
+
module Spoonerize
|
|
8
|
+
class Web
|
|
9
|
+
##
|
|
10
|
+
# Command-line launcher for the web app.
|
|
11
|
+
class Cli
|
|
12
|
+
##
|
|
13
|
+
# Server options used when no command-line overrides are passed.
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash]
|
|
16
|
+
DEFAULT_OPTIONS = {
|
|
17
|
+
host: Web.bind,
|
|
18
|
+
port: Web.port
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Starts the web app from command-line arguments.
|
|
23
|
+
#
|
|
24
|
+
# @param [Array] options
|
|
25
|
+
#
|
|
26
|
+
# @return [nil]
|
|
27
|
+
def self.execute(options = [])
|
|
28
|
+
new(options).execute
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Parsed server options.
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash]
|
|
35
|
+
attr_reader :options
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Create a web CLI launcher.
|
|
39
|
+
#
|
|
40
|
+
# @param [Array] options Command-line arguments.
|
|
41
|
+
#
|
|
42
|
+
# @return [self]
|
|
43
|
+
def initialize(options)
|
|
44
|
+
@options = DEFAULT_OPTIONS.merge(parse(options))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Starts the Sinatra web app.
|
|
49
|
+
#
|
|
50
|
+
# @return [nil]
|
|
51
|
+
def execute
|
|
52
|
+
Web.run!(bind: options[:host], port: options[:port])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def parse(options)
|
|
58
|
+
{}.tap do |prefs|
|
|
59
|
+
OptionParser.new do |o|
|
|
60
|
+
o.version = ::Spoonerize::Version.to_s
|
|
61
|
+
o.on("--host=HOST", "Host to bind") do |value|
|
|
62
|
+
prefs[:host] = value
|
|
63
|
+
end
|
|
64
|
+
o.on("--port=PORT", Integer, "Port to bind") do |value|
|
|
65
|
+
prefs[:port] = value
|
|
66
|
+
end
|
|
67
|
+
end.parse!(options)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f7f7f4;
|
|
4
|
+
--ink: #24211f;
|
|
5
|
+
--muted: #6c655f;
|
|
6
|
+
--line: #d7d2c9;
|
|
7
|
+
--panel: #fffdfa;
|
|
8
|
+
--accent: #1d6f78;
|
|
9
|
+
--accent-dark: #145059;
|
|
10
|
+
--notice-bg: #fff0dc;
|
|
11
|
+
--notice-line: #e1a853;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
min-height: 100vh;
|
|
21
|
+
background: var(--bg);
|
|
22
|
+
color: var(--ink);
|
|
23
|
+
font-family: ui-serif, Georgia, "Times New Roman", serif;
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.page {
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
display: grid;
|
|
30
|
+
place-items: start center;
|
|
31
|
+
padding: 4rem 1rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.shell {
|
|
35
|
+
width: min(100%, 46rem);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.masthead {
|
|
39
|
+
margin-bottom: 2rem;
|
|
40
|
+
border-bottom: 1px solid var(--line);
|
|
41
|
+
padding-bottom: 1.25rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
h1 {
|
|
45
|
+
margin: 0;
|
|
46
|
+
font-size: clamp(2.2rem, 7vw, 4rem);
|
|
47
|
+
line-height: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.result {
|
|
51
|
+
margin: 0 0 1.75rem;
|
|
52
|
+
font-size: clamp(2rem, 6vw, 3.6rem);
|
|
53
|
+
font-weight: 800;
|
|
54
|
+
line-height: 1.05;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.notice {
|
|
58
|
+
margin: 0 0 1.5rem;
|
|
59
|
+
border: 1px solid var(--notice-line);
|
|
60
|
+
background: var(--notice-bg);
|
|
61
|
+
padding: 0.8rem 1rem;
|
|
62
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
63
|
+
font-weight: 650;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.spoonerize-form {
|
|
67
|
+
display: grid;
|
|
68
|
+
gap: 1rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.field,
|
|
72
|
+
fieldset {
|
|
73
|
+
display: grid;
|
|
74
|
+
gap: 0.45rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.field span,
|
|
78
|
+
legend {
|
|
79
|
+
color: var(--muted);
|
|
80
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
81
|
+
font-size: 0.85rem;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
input[type="text"] {
|
|
86
|
+
width: 100%;
|
|
87
|
+
border: 1px solid var(--line);
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
background: var(--panel);
|
|
90
|
+
color: var(--ink);
|
|
91
|
+
font: inherit;
|
|
92
|
+
font-size: 1.1rem;
|
|
93
|
+
padding: 0.75rem 0.85rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
input[type="text"]:focus {
|
|
97
|
+
border-color: var(--accent);
|
|
98
|
+
outline: 3px solid color-mix(in srgb, var(--accent) 22%, transparent);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fieldset {
|
|
102
|
+
margin: 0;
|
|
103
|
+
border: 1px solid var(--line);
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
background: var(--panel);
|
|
106
|
+
padding: 0.9rem 1rem 1rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
legend {
|
|
110
|
+
padding: 0 0.35rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.check {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.55rem;
|
|
117
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.check input {
|
|
121
|
+
width: 1rem;
|
|
122
|
+
height: 1rem;
|
|
123
|
+
accent-color: var(--accent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
button {
|
|
127
|
+
width: fit-content;
|
|
128
|
+
border: 0;
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
background: var(--accent);
|
|
131
|
+
color: #fff;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
134
|
+
font-size: 1rem;
|
|
135
|
+
font-weight: 750;
|
|
136
|
+
padding: 0.72rem 1rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
button:hover,
|
|
140
|
+
button:focus {
|
|
141
|
+
background: var(--accent-dark);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.footer {
|
|
145
|
+
width: min(100%, 46rem);
|
|
146
|
+
margin-top: 2.5rem;
|
|
147
|
+
border-top: 1px solid var(--line);
|
|
148
|
+
padding-top: 1rem;
|
|
149
|
+
color: var(--muted);
|
|
150
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
151
|
+
font-size: 0.82rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.footer a {
|
|
155
|
+
color: inherit;
|
|
156
|
+
text-decoration-color: color-mix(in srgb, var(--muted) 45%, transparent);
|
|
157
|
+
text-underline-offset: 0.16em;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.footer a:hover,
|
|
161
|
+
.footer a:focus {
|
|
162
|
+
color: var(--accent-dark);
|
|
163
|
+
text-decoration-color: currentcolor;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.footer span {
|
|
167
|
+
margin: 0 0.35rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@media (max-width: 40rem) {
|
|
171
|
+
.page {
|
|
172
|
+
padding-top: 2rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
button {
|
|
176
|
+
width: 100%;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<section class="shell">
|
|
2
|
+
<header class="masthead">
|
|
3
|
+
<h1>Flip a phrase</h1>
|
|
4
|
+
</header>
|
|
5
|
+
|
|
6
|
+
<% if @result %>
|
|
7
|
+
<p class="result"><%= h @result %></p>
|
|
8
|
+
<% if @saved %>
|
|
9
|
+
<p class="notice">Saved.</p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% elsif @error %>
|
|
12
|
+
<p class="notice"><%= h @error %></p>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<form action="/" method="post" class="spoonerize-form">
|
|
16
|
+
<label class="field">
|
|
17
|
+
<span>Phrase</span>
|
|
18
|
+
<input
|
|
19
|
+
type="text"
|
|
20
|
+
name="phrase"
|
|
21
|
+
value="<%= h @phrase %>"
|
|
22
|
+
placeholder="Enter phrase to spoonerize..."
|
|
23
|
+
autofocus>
|
|
24
|
+
</label>
|
|
25
|
+
|
|
26
|
+
<fieldset>
|
|
27
|
+
<legend>Options</legend>
|
|
28
|
+
|
|
29
|
+
<label class="check">
|
|
30
|
+
<input type="checkbox" name="reverse" value="1" <%= checked?("reverse") %>>
|
|
31
|
+
<span>Reverse flipping</span>
|
|
32
|
+
</label>
|
|
33
|
+
|
|
34
|
+
<label class="check">
|
|
35
|
+
<input type="checkbox" name="lazy" value="1" <%= checked?("lazy") %>>
|
|
36
|
+
<span>Skip common words</span>
|
|
37
|
+
</label>
|
|
38
|
+
|
|
39
|
+
<label class="check">
|
|
40
|
+
<input type="checkbox" name="consonants_only" value="1" <%= checked?("consonants_only") %>>
|
|
41
|
+
<span>Only flip consonant-starting words</span>
|
|
42
|
+
</label>
|
|
43
|
+
|
|
44
|
+
<label class="check">
|
|
45
|
+
<input type="checkbox" name="save" value="1" <%= @save ? "checked" : nil %>>
|
|
46
|
+
<span>Save result</span>
|
|
47
|
+
</label>
|
|
48
|
+
</fieldset>
|
|
49
|
+
|
|
50
|
+
<label class="field">
|
|
51
|
+
<span>Excluded words</span>
|
|
52
|
+
<input
|
|
53
|
+
type="text"
|
|
54
|
+
name="excluded_words"
|
|
55
|
+
value="<%= h @excluded_words_value %>"
|
|
56
|
+
placeholder="word another">
|
|
57
|
+
</label>
|
|
58
|
+
|
|
59
|
+
<button type="submit">Spoonerize</button>
|
|
60
|
+
</form>
|
|
61
|
+
</section>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Spoonerize</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="page">
|
|
11
|
+
<%= yield %>
|
|
12
|
+
|
|
13
|
+
<footer class="footer">
|
|
14
|
+
<a href="https://github.com/evanthegrayt">evanthegrayt</a>
|
|
15
|
+
<span>/</span>
|
|
16
|
+
<a href="https://github.com/evanthegrayt/spoonerize">spoonerize</a>
|
|
17
|
+
</footer>
|
|
18
|
+
</main>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
|
|
5
|
+
require_relative "../spoonerize"
|
|
6
|
+
|
|
7
|
+
module Spoonerize
|
|
8
|
+
##
|
|
9
|
+
# Sinatra app for spoonerizing phrases in the browser.
|
|
10
|
+
class Web < Sinatra::Base
|
|
11
|
+
##
|
|
12
|
+
# Boolean Spoonerism options exposed by the web form.
|
|
13
|
+
#
|
|
14
|
+
# @return [Array<String>]
|
|
15
|
+
OPTION_NAMES = %w[reverse lazy consonants_only].freeze
|
|
16
|
+
|
|
17
|
+
set :root, File.expand_path(File.join(__dir__, "..", ".."))
|
|
18
|
+
set :public_folder, File.expand_path(File.join(__dir__, "web", "public"))
|
|
19
|
+
set :views, File.expand_path(File.join(__dir__, "web", "views"))
|
|
20
|
+
set :static, true
|
|
21
|
+
set :show_exceptions, false
|
|
22
|
+
|
|
23
|
+
configure do
|
|
24
|
+
Spoonerize.load_config_file(Spoonerize::CONFIG_FILE) if File.file?(Spoonerize::CONFIG_FILE)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
helpers do
|
|
28
|
+
##
|
|
29
|
+
# HTML checkbox attribute for a truthy option value.
|
|
30
|
+
#
|
|
31
|
+
# @param [String] name The option name.
|
|
32
|
+
#
|
|
33
|
+
# @return [String, nil]
|
|
34
|
+
def checked?(name)
|
|
35
|
+
option_value(name) ? "checked" : nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Current boolean value for a web form option.
|
|
40
|
+
#
|
|
41
|
+
# @param [String] name The option name.
|
|
42
|
+
#
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def option_value(name)
|
|
45
|
+
@options.fetch(name.to_sym)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Escapes a value for safe HTML output.
|
|
50
|
+
#
|
|
51
|
+
# @param [Object] value The value to escape.
|
|
52
|
+
#
|
|
53
|
+
# @return [String]
|
|
54
|
+
def h(value)
|
|
55
|
+
Rack::Utils.escape_html(value)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
get "/" do
|
|
60
|
+
prepare_request(false)
|
|
61
|
+
erb :index
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
post "/" do
|
|
65
|
+
prepare_request(true)
|
|
66
|
+
@result = spoonerize_phrase if @submitted && !@phrase.strip.empty?
|
|
67
|
+
|
|
68
|
+
erb :index
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def options_from_params
|
|
74
|
+
OPTION_NAMES.to_h do |name|
|
|
75
|
+
value = @submitted ? params.key?(name) : Spoonerize.config.public_send(name)
|
|
76
|
+
[name.to_sym, value]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def excluded_words_from(value)
|
|
81
|
+
value.split(/[,\s]+/).reject(&:empty?)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def excluded_words_from_params
|
|
85
|
+
return excluded_words_from(params["excluded_words"].to_s) if @submitted
|
|
86
|
+
|
|
87
|
+
Spoonerize.config.excluded_words
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def prepare_request(submitted)
|
|
91
|
+
@submitted = submitted
|
|
92
|
+
@phrase = params["phrase"].to_s
|
|
93
|
+
@options = options_from_params
|
|
94
|
+
@excluded_words = excluded_words_from_params
|
|
95
|
+
@excluded_words_value = @excluded_words.join(" ")
|
|
96
|
+
@save = @submitted && params.key?("save")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def spoonerize_phrase
|
|
100
|
+
spoonerism = Spoonerism.new(
|
|
101
|
+
*@phrase.split,
|
|
102
|
+
**@options,
|
|
103
|
+
excluded_words: @excluded_words
|
|
104
|
+
)
|
|
105
|
+
result = spoonerism.to_s
|
|
106
|
+
spoonerism.save if @save
|
|
107
|
+
@saved = @save
|
|
108
|
+
|
|
109
|
+
result
|
|
110
|
+
rescue => error
|
|
111
|
+
@error = friendly_error(error)
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def friendly_error(error)
|
|
116
|
+
return "Not enough words to flip." if error.message == "Not enough words to flip"
|
|
117
|
+
|
|
118
|
+
error.message
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
data/lib/spoonerize.rb
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
##
|
|
4
|
+
# The main namespace for the gem.
|
|
5
|
+
module Spoonerize
|
|
6
|
+
##
|
|
7
|
+
# The config file the user can create to change default runtime options.
|
|
8
|
+
#
|
|
9
|
+
# @return [String]
|
|
10
|
+
CONFIG_FILE = File.expand_path(File.join(ENV["HOME"], ".spoonerizerc"))
|
|
11
|
+
end
|
|
12
|
+
|
|
3
13
|
require_relative "spoonerize/config"
|
|
4
14
|
require_relative "spoonerize/spoonerism"
|
|
5
15
|
require_relative "spoonerize/bumper"
|
|
@@ -53,7 +63,7 @@ module Spoonerize
|
|
|
53
63
|
##
|
|
54
64
|
# Loads a config file.
|
|
55
65
|
#
|
|
56
|
-
# @param [String]
|
|
66
|
+
# @param [String] config_file
|
|
57
67
|
#
|
|
58
68
|
# @return [String] file
|
|
59
69
|
def load_config_file(config_file)
|
data/spoonerize.gemspec
CHANGED
|
@@ -10,6 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.summary = %(Spoonerize phrases from the command line.)
|
|
11
11
|
spec.description = %(Spoonerize phrases from the command line. Comes with an API)
|
|
12
12
|
spec.homepage = "https://evanthegrayt.github.io/spoonerize/"
|
|
13
|
+
spec.required_ruby_version = ">= 3.2"
|
|
13
14
|
|
|
14
15
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
15
16
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
@@ -29,7 +30,11 @@ Gem::Specification.new do |spec|
|
|
|
29
30
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
30
31
|
spec.require_paths = ["lib"]
|
|
31
32
|
spec.add_dependency "csv"
|
|
33
|
+
spec.add_dependency "puma", "~> 8.0"
|
|
34
|
+
spec.add_dependency "rackup", "~> 2.3"
|
|
35
|
+
spec.add_dependency "sinatra", "~> 4.2"
|
|
32
36
|
spec.add_development_dependency "rdoc"
|
|
33
37
|
spec.add_development_dependency "rake", "~> 13.0", ">= 13.0.1"
|
|
38
|
+
spec.add_development_dependency "standard", "= 1.54.0"
|
|
34
39
|
spec.add_development_dependency "test-unit", "~> 3.3", ">= 3.3.5"
|
|
35
40
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evan Gray
|
|
@@ -23,6 +23,48 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: puma
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '8.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '8.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rackup
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.3'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.3'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: sinatra
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '4.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '4.2'
|
|
26
68
|
- !ruby/object:Gem::Dependency
|
|
27
69
|
name: rdoc
|
|
28
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,6 +99,20 @@ dependencies:
|
|
|
57
99
|
- - ">="
|
|
58
100
|
- !ruby/object:Gem::Version
|
|
59
101
|
version: 13.0.1
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: standard
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - '='
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: 1.54.0
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - '='
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: 1.54.0
|
|
60
116
|
- !ruby/object:Gem::Dependency
|
|
61
117
|
name: test-unit
|
|
62
118
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -81,6 +137,7 @@ description: Spoonerize phrases from the command line. Comes with an API
|
|
|
81
137
|
email: evanthegrayt@vivaldi.net
|
|
82
138
|
executables:
|
|
83
139
|
- spoonerize
|
|
140
|
+
- spoonerize-web
|
|
84
141
|
extensions: []
|
|
85
142
|
extra_rdoc_files: []
|
|
86
143
|
files:
|
|
@@ -93,6 +150,7 @@ files:
|
|
|
93
150
|
- Rakefile
|
|
94
151
|
- _config.yml
|
|
95
152
|
- bin/spoonerize
|
|
153
|
+
- bin/spoonerize-web
|
|
96
154
|
- lib/spoonerize.rb
|
|
97
155
|
- lib/spoonerize/bumper.rb
|
|
98
156
|
- lib/spoonerize/cli.rb
|
|
@@ -100,6 +158,11 @@ files:
|
|
|
100
158
|
- lib/spoonerize/log.rb
|
|
101
159
|
- lib/spoonerize/spoonerism.rb
|
|
102
160
|
- lib/spoonerize/version.rb
|
|
161
|
+
- lib/spoonerize/web.rb
|
|
162
|
+
- lib/spoonerize/web/cli.rb
|
|
163
|
+
- lib/spoonerize/web/public/styles.css
|
|
164
|
+
- lib/spoonerize/web/views/index.erb
|
|
165
|
+
- lib/spoonerize/web/views/layout.erb
|
|
103
166
|
- spoonerize.gemspec
|
|
104
167
|
homepage: https://evanthegrayt.github.io/spoonerize/
|
|
105
168
|
licenses:
|
|
@@ -116,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
116
179
|
requirements:
|
|
117
180
|
- - ">="
|
|
118
181
|
- !ruby/object:Gem::Version
|
|
119
|
-
version: '
|
|
182
|
+
version: '3.2'
|
|
120
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
184
|
requirements:
|
|
122
185
|
- - ">="
|