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 CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oulipo (0.1.1)
4
+ oulipo (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
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? poem # => true
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.allitertivity(phrase) # => 0.8 (4/5 words start with 'q')
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
- class Oulipo
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
@@ -3,7 +3,7 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "oulipo"
6
- s.version = "0.1.2"
6
+ s.version = "0.2.0"
7
7
  s.date = Time.now.strftime('%Y-%m-%d')
8
8
  s.homepage = "http://github.com/Aupajo/oulipo"
9
9
  s.email = "pete@metanation.com"
@@ -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,3 @@
1
+ apple
2
+ car
3
+ dog
@@ -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.1.2
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-28 00:00:00 +12:00
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