oulipo 0.1.2 → 0.2.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.
- data/Gemfile.lock +1 -1
- data/README.md +66 -2
- data/lib/oulipo/analysis.rb +60 -0
- data/lib/oulipo/substitutor.rb +51 -0
- data/lib/oulipo/word_list.rb +34 -0
- data/lib/oulipo.rb +11 -1
- data/oulipo.gemspec +1 -1
- data/spec/analysis_spec.rb +32 -0
- data/spec/fixtures/word_list.txt +3 -0
- data/spec/substitution_spec.rb +46 -0
- data/spec/word_list_spec.rb +34 -0
- metadata +13 -2
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -40,6 +40,37 @@ sentence = 'Big fjords vex quick waltz nymph.'
|
|
40
40
|
Oulipo.pangram?(sentence) # => true
|
41
41
|
```
|
42
42
|
|
43
|
+
## N+7
|
44
|
+
|
45
|
+
In N+7 (sometimes known as S+7), each noun in a text is replaced with the noun seven entries after it in a dictionary.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
dictionary = Oulipo::WordList.load('big_list_of_nouns.txt')
|
49
|
+
|
50
|
+
play = <<-SHAKESPEARE
|
51
|
+
|
52
|
+
What, jealous Oberon! Fairies, skip hence:
|
53
|
+
I have forsworn his bed and company.
|
54
|
+
|
55
|
+
SHAKESPEARE
|
56
|
+
|
57
|
+
Oulipo.n_plus(7, play, dictionary) # => "What, jealous Oberon! Fallacies, skulk hence:
|
58
|
+
# I have forsworn his bedroom and compensation."
|
59
|
+
|
60
|
+
```
|
61
|
+
|
62
|
+
Oulipo includes a handy `WordList` class for reading one-word-per-line dictionary files, but a dictionary can be any object that responds to `index(word)`, `length`, and `[index]`.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
dictionary = %w{ iron gild mine gold ore paint cast lily }
|
66
|
+
|
67
|
+
king_john = 'To gild refined gold, to paint the lily'
|
68
|
+
|
69
|
+
Oulipo.n_plus(1, king_john, dictionary) # => 'To mine refined ore, to cast the iron'
|
70
|
+
```
|
71
|
+
|
72
|
+
See also: Substitution.
|
73
|
+
|
43
74
|
## Univocalims
|
44
75
|
|
45
76
|
A univocalism is a poem written using only one type of vowel.
|
@@ -84,7 +115,7 @@ poem = <<-WORDS
|
|
84
115
|
|
85
116
|
WORDS
|
86
117
|
|
87
|
-
Oulipo.snowball?
|
118
|
+
Oulipo.snowball?(poem) # => true
|
88
119
|
```
|
89
120
|
|
90
121
|
## Alliteration
|
@@ -100,7 +131,40 @@ Normal alliteration's a little harsh, so you can give it a threshold, too.
|
|
100
131
|
```ruby
|
101
132
|
phrase = 'quick queens quibble over quails'
|
102
133
|
|
103
|
-
Oulipo.
|
134
|
+
Oulipo.alliterativity(phrase) # => 0.8 (4/5 words start with 'q')
|
104
135
|
Oulipo.alleration?(phrase, :threshold => 0.7) # => true
|
105
136
|
Oulipo.alleration?(phrase, :threshold => 0.9) # => false
|
106
137
|
```
|
138
|
+
|
139
|
+
## Analysis
|
140
|
+
|
141
|
+
Rudimentary analysis can be performed by using Oulipo's `Analysis` class.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
line = 'A rose by any other name would smell as sweet'
|
145
|
+
|
146
|
+
word_sets = {
|
147
|
+
:nouns => %w{ bear name rose },
|
148
|
+
:adjectives => %w{ sweet }
|
149
|
+
}
|
150
|
+
|
151
|
+
analysis = Oulipo::Analysis.new(line, word_sets)
|
152
|
+
|
153
|
+
analysis.identified(:nouns) # => ['rose', 'name']
|
154
|
+
analysis.identified(:adjectives) # => ['sweet']
|
155
|
+
```
|
156
|
+
|
157
|
+
## Substitution
|
158
|
+
|
159
|
+
Substitution can be performed on an analysis.
|
160
|
+
|
161
|
+
Carrying on from the example above:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
substitutor = Oulipo::Substitutor.new(analysis)
|
165
|
+
substitutor.replace(:nouns).increment(1) # => "A bear by any other rose would smell as sweet"
|
166
|
+
```
|
167
|
+
|
168
|
+
---
|
169
|
+
|
170
|
+
- Pete Nicholls ([@Aupajo](http://twitter.com/Aupajo))
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Oulipo
|
2
|
+
class Analysis
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :original, :deconstruction, :word_lists
|
6
|
+
|
7
|
+
def initialize(text, options = {})
|
8
|
+
@word_lists = options
|
9
|
+
@original = text
|
10
|
+
deconstruct!
|
11
|
+
end
|
12
|
+
|
13
|
+
def identified(type = nil)
|
14
|
+
result = identified_tuples
|
15
|
+
result.select! { |tuple| tuple.last == type } if type
|
16
|
+
result.map(&:first)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def is_tuple?(object)
|
22
|
+
object.is_a?(Array)
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_plain?(object)
|
26
|
+
object.is_a?(String)
|
27
|
+
end
|
28
|
+
|
29
|
+
def identified_tuples
|
30
|
+
deconstruction.select { |segment| is_tuple?(segment) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def identify(wordish)
|
34
|
+
return wordish if wordish.strip.empty?
|
35
|
+
|
36
|
+
word_lists.each do |type, list|
|
37
|
+
return [wordish, type] if list.index(wordish.downcase)
|
38
|
+
end
|
39
|
+
|
40
|
+
wordish
|
41
|
+
end
|
42
|
+
|
43
|
+
def deconstruct!
|
44
|
+
chunks = original.split(/\b/)
|
45
|
+
|
46
|
+
@deconstruction = []
|
47
|
+
|
48
|
+
chunks.each do |wordish|
|
49
|
+
analysis = identify(wordish)
|
50
|
+
|
51
|
+
if is_plain?(analysis) and is_plain?(deconstruction.last)
|
52
|
+
deconstruction.last << analysis
|
53
|
+
else
|
54
|
+
deconstruction << analysis
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Oulipo
|
2
|
+
class Substitutor
|
3
|
+
|
4
|
+
def initialize(analysis)
|
5
|
+
@analysis = analysis
|
6
|
+
end
|
7
|
+
|
8
|
+
def replace(type)
|
9
|
+
@type = type
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def increment(num)
|
14
|
+
raise 'Increment must be called in conjunction with replace' if !@type
|
15
|
+
|
16
|
+
result = ''
|
17
|
+
|
18
|
+
@analysis.deconstruction.each do |segment|
|
19
|
+
if segment.is_a?(Array) # tuple found
|
20
|
+
if segment.last == @type # ready to replace
|
21
|
+
result << find_successor(segment.first, num)
|
22
|
+
else
|
23
|
+
result << segment.first
|
24
|
+
end
|
25
|
+
else
|
26
|
+
result << segment
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def word_list
|
36
|
+
@analysis.word_lists[@type]
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_successor(word, num)
|
40
|
+
# Find the current index and the limit
|
41
|
+
current_index = word_list.index(word.downcase)
|
42
|
+
limit = word_list.length
|
43
|
+
|
44
|
+
# Calculate the successor's index, wrapping if we need to
|
45
|
+
successor = (current_index + num) % limit
|
46
|
+
|
47
|
+
word_list[successor]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Oulipo
|
2
|
+
class WordList
|
3
|
+
def initialize
|
4
|
+
@words = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.load(file)
|
8
|
+
dict = self.new
|
9
|
+
file = File.open(file) if file.is_a?(String)
|
10
|
+
|
11
|
+
file.each_line do |word|
|
12
|
+
dict.push(word.strip)
|
13
|
+
end
|
14
|
+
|
15
|
+
dict
|
16
|
+
end
|
17
|
+
|
18
|
+
def index(word)
|
19
|
+
@words.index(word)
|
20
|
+
end
|
21
|
+
|
22
|
+
def length
|
23
|
+
@words.length
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](index)
|
27
|
+
@words[index]
|
28
|
+
end
|
29
|
+
|
30
|
+
def push(word)
|
31
|
+
@words.push(word)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/oulipo.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
require 'oulipo/word_list'
|
2
|
+
require 'oulipo/analysis'
|
3
|
+
require 'oulipo/substitutor'
|
4
|
+
|
5
|
+
module Oulipo
|
2
6
|
ALPHABET = 'a'..'z'
|
3
7
|
VOWELS = %w{ a e i o u }
|
4
8
|
|
@@ -66,4 +70,10 @@ class Oulipo
|
|
66
70
|
most_used_count = leading_letter_counts.max_by { |kv| kv.last }.pop
|
67
71
|
most_used_count.to_f / words.length
|
68
72
|
end
|
73
|
+
|
74
|
+
def self.n_plus(places, text, word_list)
|
75
|
+
analysis = Analysis.new(text, :nouns => word_list)
|
76
|
+
substitutor = Substitutor.new(analysis)
|
77
|
+
substitutor.replace(:nouns).increment(places)
|
78
|
+
end
|
69
79
|
end
|
data/oulipo.gemspec
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Oulipo::Analysis do
|
4
|
+
include Oulipo
|
5
|
+
|
6
|
+
let(:folksong) { "O'er the moor and among the heather" }
|
7
|
+
|
8
|
+
let(:word_lists) { { :nouns => %w{ moor heather },
|
9
|
+
:prepositions => %w{ among } } }
|
10
|
+
|
11
|
+
let(:analysis) { Analysis.new(folksong, word_lists) }
|
12
|
+
|
13
|
+
it "can identify words" do
|
14
|
+
analysis.identified.sort.should == %w{ among heather moor }
|
15
|
+
analysis.identified(:nouns).should == %w{ moor heather }
|
16
|
+
analysis.identified(:prepositions).should == %w{ among }
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can return the original string" do
|
20
|
+
analysis.original.should == "O'er the moor and among the heather"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can return the raw deconstruction" do
|
24
|
+
deconstruction = ["O'er the ", ['moor', :nouns], ' and ', ['among', :prepositions], ' the ', ['heather', :nouns]]
|
25
|
+
analysis.deconstruction.should == deconstruction
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can return the word lists it's using" do
|
29
|
+
analysis.word_lists.should == word_lists
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "substitution" do
|
4
|
+
|
5
|
+
def analysis_with(phrase, options)
|
6
|
+
Oulipo::Analysis.new(phrase, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def substitutor_with(*args)
|
10
|
+
Oulipo::Substitutor.new analysis_with(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:performer) { Oulipo }
|
14
|
+
|
15
|
+
let(:phrase) { 'The bear ate the badger' }
|
16
|
+
let(:noun_list) { %w{ badger bat bear } }
|
17
|
+
let(:substitutor) { substitutor_with(phrase, :nouns => noun_list) }
|
18
|
+
|
19
|
+
it "substitutes types" do
|
20
|
+
substitutor.replace(:nouns).increment(1).should == 'The badger ate the bat'
|
21
|
+
substitutor.replace(:nouns).increment(2).should == 'The bat ate the bear'
|
22
|
+
substitutor.replace(:nouns).increment(6).should == 'The bear ate the badger'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an error if increment is called before replace" do
|
26
|
+
lambda { substitutor.increment(3) }.should raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can be accessed from Oulipo with n_plus" do
|
30
|
+
performer.n_plus(1, phrase, noun_list).should == 'The badger ate the bat'
|
31
|
+
performer.n_plus(2, phrase, noun_list).should == 'The bat ate the bear'
|
32
|
+
performer.n_plus(6, phrase, noun_list).should == 'The bear ate the badger'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "handles unused nouns" do
|
36
|
+
nouns = %w{ badger bear bat ball balustrade }
|
37
|
+
substitutor = substitutor_with(phrase, :nouns => nouns)
|
38
|
+
substitutor.replace(:nouns).increment(4).should == 'The badger ate the balustrade'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "matches case" do
|
42
|
+
substitutor = substitutor_with('The BEAR ate the BadgeR', :nouns => noun_list)
|
43
|
+
substitutor.replace(:nouns).increment(1).should == 'The badger ate the bat'
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Oulipo::WordList do
|
4
|
+
|
5
|
+
let(:word_list) { Oulipo::WordList.new }
|
6
|
+
|
7
|
+
it "acts like an array" do
|
8
|
+
word_list.length.should == 0
|
9
|
+
word_list.index('apple').should be_nil
|
10
|
+
word_list[0].should == nil
|
11
|
+
|
12
|
+
word_list.push('apple')
|
13
|
+
|
14
|
+
word_list.length.should == 1
|
15
|
+
word_list.index('apple').should == 0
|
16
|
+
word_list[0].should == 'apple'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "loads from a file name" do
|
20
|
+
list = Oulipo::WordList.load(File.dirname(__FILE__) + '/fixtures/word_list.txt')
|
21
|
+
list.length.should == 3
|
22
|
+
list.index('car').should == 1
|
23
|
+
list[2].should == 'dog'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "loads from a file" do
|
27
|
+
file = File.open(File.dirname(__FILE__) + '/fixtures/word_list.txt')
|
28
|
+
list = Oulipo::WordList.load(file)
|
29
|
+
list.length.should == 3
|
30
|
+
list.index('car').should == 1
|
31
|
+
list[2].should == 'dog'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: oulipo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Pete Nicholls
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-07-
|
13
|
+
date: 2011-07-29 00:00:00 +12:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -29,13 +29,20 @@ files:
|
|
29
29
|
- README.md
|
30
30
|
- Rakefile
|
31
31
|
- lib/oulipo.rb
|
32
|
+
- lib/oulipo/analysis.rb
|
33
|
+
- lib/oulipo/substitutor.rb
|
34
|
+
- lib/oulipo/word_list.rb
|
32
35
|
- oulipo.gemspec
|
33
36
|
- spec/alliteration_spec.rb
|
37
|
+
- spec/analysis_spec.rb
|
34
38
|
- spec/chaterisms_spec.rb
|
39
|
+
- spec/fixtures/word_list.txt
|
35
40
|
- spec/lipograms_spec.rb
|
36
41
|
- spec/palindromes_spec.rb
|
37
42
|
- spec/spec_helper.rb
|
43
|
+
- spec/substitution_spec.rb
|
38
44
|
- spec/univocalisms_spec.rb
|
45
|
+
- spec/word_list_spec.rb
|
39
46
|
has_rdoc: true
|
40
47
|
homepage: http://github.com/Aupajo/oulipo
|
41
48
|
licenses: []
|
@@ -66,8 +73,12 @@ specification_version: 3
|
|
66
73
|
summary: Constrained writing with Ruby.
|
67
74
|
test_files:
|
68
75
|
- spec/alliteration_spec.rb
|
76
|
+
- spec/analysis_spec.rb
|
69
77
|
- spec/chaterisms_spec.rb
|
78
|
+
- spec/fixtures/word_list.txt
|
70
79
|
- spec/lipograms_spec.rb
|
71
80
|
- spec/palindromes_spec.rb
|
72
81
|
- spec/spec_helper.rb
|
82
|
+
- spec/substitution_spec.rb
|
73
83
|
- spec/univocalisms_spec.rb
|
84
|
+
- spec/word_list_spec.rb
|