oulipo 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|