commit d4a587ffe950b9d19bcff0cd80d843972da7744d
parent 0301ba5432fb3096ef5da2aabb4cb9770b16cb41
Author: Antoine Amarilli <a3nm@a3nm.net>
Date: Sun, 27 Jul 2014 18:18:40 +0200
disallow poor rhymes which are also poor pour l'œil
Diffstat:
7 files changed, 117 insertions(+), 24 deletions(-)
diff --git a/TODO b/TODO
@@ -1,4 +1,8 @@
- profiling
+- option to forbid rhyme with only "-ment" except for "doublement",
+ "triplement", "-uplement", and "complètement" -- not with boileau
+- document "poor_adverb_ok"
+- change additions to give numerical syllable count
- highlight possible hemistiche positions where an hemistiche could have been placed
- document expected errors of test.sh and commit necessary files
- remove kludge for invalid characters, split them in specific chunks
diff --git a/error.py b/error.py
@@ -88,9 +88,10 @@ class ErrorHiatus(ErrorBadElement):
key = "hiatus"
class ErrorBadRhyme:
- def __init__(self, expected, inferred):
+ def __init__(self, expected, inferred, old_phon=None):
self.expected = expected
self.inferred = inferred
+ self.old_phon = old_phon
def report(self, pattern):
return (_("%s for type %s (expected %s, inferred %s)")
@@ -130,7 +131,7 @@ class ErrorBadRhymeEye(ErrorBadRhymeObject):
return _("Bad rhyme ending")
def fmt(self, l):
- return "\"-" + l.sufficient_eye() + "\""
+ return "\"-" + l.sufficient_eye(self.old_phon) + "\""
class ErrorBadMetric:
def report(self, pattern):
diff --git a/options.py b/options.py
@@ -9,5 +9,8 @@ default_options = {
'incomplete_ok': True,
'check_end_hemistiche': True,
'check_occurrences': True,
+ 'poor_eye_required': True,
+ 'poor_eye_supposed_ok': False,
+ 'poor_adverb_ok': False,
'diaeresis': "classical"
}
diff --git a/rhyme.py b/rhyme.py
@@ -63,7 +63,7 @@ class Rhyme:
def supposed_liaison(self, x):
if x[-1] in liaison.keys() and self.options['eye_supposed_ok']:
- return x + liaison[x[-1]]
+ return x[:-1] + liaison[x[-1]]
return x
def __init__(self, line, constraint=None, mergers=None, options=None, phon=None):
@@ -72,6 +72,9 @@ class Rhyme:
else:
self.constraint = Constraint()
self.mergers = {}
+ # length of smallest end-of-verse word in syllables
+ # will be provided later
+ self.last_count = 42
if options:
self.options = options
else:
@@ -84,13 +87,18 @@ class Rhyme:
phon = self.lookup(line)
self.phon = set([self.apply_mergers(x) for x in phon])
self.eye = self.supposed_liaison(self.consonant_suffix(line))
+ self.raw_eye = line
self.old_phon = None
self.old_eye = None
+ self.old_raw_eye = None
+ self.old_last_count = None
self.new_rhyme = None
def rollback(self):
self.phon = self.old_phon
self.eye = self.old_eye
+ self.raw_eye = self.old_raw_eye
+ self.last_count = self.old_last_count
def sufficient_phon(self):
# return the shortest accepted rhymes among old_phon
@@ -105,10 +113,27 @@ class Rhyme:
ok.add(p[-slen:])
return ok
- def sufficient_eye(self):
- return self.eye[-1]
+ def sufficient_eye_length(self, old_phon=None):
+ if not self.constraint.classical:
+ return self.eye, 0 # not classical, nothing required
+ if ((old_phon >= 2 if old_phon else self.satisfied_phon(2))
+ or not self.options['poor_eye_required']):
+ return self.eye, 1
+ if self.last_count == 1:
+ return self.eye, 1
+ if self.options['poor_eye_supposed_ok']:
+ return self.eye, 2
+ else:
+ return self.raw_eye, 2
+
+ def sufficient_eye(self, old_phon=None):
+ d, val = self.sufficient_eye_length(old_phon)
+ if val <= len(d):
+ return d[-val:]
+ else:
+ return d
- def match(self, phon, eye):
+ def match(self, phon, eye, raw_eye):
"""limit our phon and eye to those which match phon and eye and which
respect constraints"""
new_phon = set()
@@ -124,16 +149,28 @@ class Rhyme:
self.eye = ""
else:
self.eye = self.eye[-val:]
+ if self.raw_eye:
+ val = eye_rhyme(self.raw_eye, raw_eye)
+ if val == 0:
+ self.raw_eye = ""
+ else:
+ self.raw_eye = self.raw_eye[-val:]
+
+ def adjustLastCount(self, v):
+ self.last_count = min(self.last_count, v)
def restrict(self, r):
"""take the intersection between us and rhyme object r"""
if self.satisfied():
self.old_phon = self.phon
self.old_eye = self.eye
+ self.old_last_count = self.last_count
+ self.old_raw_eye = self.raw_eye
+ # lastCount will be applied later
self.constraint.restrict(r.constraint)
self.new_rhyme = r
self.match(set([self.apply_mergers(x) for x in r.phon]),
- self.supposed_liaison(self.consonant_suffix(r.eye)))
+ self.supposed_liaison(self.consonant_suffix(r.eye)), r.raw_eye)
def consonant_suffix(self, s):
if not self.options['eye_tolerance_ok']:
@@ -145,13 +182,20 @@ class Rhyme:
def feed(self, line, constraint=None):
"""extend us with a line and a constraint"""
+ # lastCount is not applied yet
return self.restrict(Rhyme(line, constraint, self.mergers, self.options))
- def satisfied_phon(self):
- return len(self.phon) >= self.constraint.phon
+ def satisfied_phon(self, val=None):
+ if not val:
+ val = self.constraint.phon
+ for x in self.phon:
+ if len(x) >= val:
+ return True
+ return False
def satisfied_eye(self):
- return (len(self.eye) > 0 or not self.constraint.classical)
+ d, l = self.sufficient_eye_length()
+ return len(d) >= l
def satisfied(self):
return self.satisfied_phon() and self.satisfied_eye()
@@ -235,7 +279,7 @@ if __name__ == '__main__':
constraint = Constraint()
rhyme = Rhyme(line[0], constraint, self.mergers, self.options)
for x in line[1:]:
- rhyme.feed(x)
+ rhyme.feed(x, 42)
rhyme.pprint()
if not rhyme.satisfied():
print("No.")
diff --git a/template.py b/template.py
@@ -49,7 +49,10 @@ class Template:
'incomplet_ok': 'incomplete_ok',
'phon_supposee_ok': 'phon_supposed_ok',
'oeil_supposee_ok': 'eye_supposed_ok',
- 'oeil_tolerance_ok': 'eye_tolerance_ok'
+ 'oeil_tolerance_ok': 'eye_tolerance_ok',
+ 'pauvre_oeil_requise': 'poor_eye_required',
+ 'pauvre_oeil_supposee_ok': 'poor_eye_supposed_ok',
+ 'pauvre_adverbe_ok': 'poor_adverb_ok',
}
@@ -121,25 +124,22 @@ class Template:
if self.overflowed:
return [error.ErrorOverflowedTemplate()], pattern, v
+ rhyme_failed = False
# rhymes
if pattern.myid not in self.env.keys():
# initialize the rhyme
- self.env[pattern.myid] = rhyme.Rhyme(v.normalized, pattern.constraint,
- self.mergers, self.options)
+ # last_count is passed later
+ self.env[pattern.myid] = rhyme.Rhyme(v.normalized,
+ pattern.constraint, self.mergers, self.options)
else:
# update the rhyme
self.env[pattern.myid].feed(v.normalized, pattern.constraint)
- if not self.env[pattern.myid].satisfied():
- # no more possible rhymes, something went wrong
- phon_ok = self.env[pattern.myid].satisfied_phon()
- eye_ok = self.env[pattern.myid].satisfied_eye()
+ if not self.env[pattern.myid].satisfied_phon():
+ # no more possible rhymes, something went wrong, check phon
self.env[pattern.myid].rollback()
- if not phon_ok:
- errors.append(error.ErrorBadRhymeSound(self.env[pattern.myid],
- self.env[pattern.myid].new_rhyme))
- if not eye_ok:
- errors.append(error.ErrorBadRhymeEye(self.env[pattern.myid],
- self.env[pattern.myid].new_rhyme))
+ rhyme_failed = True
+ errors.append(error.ErrorBadRhymeSound(self.env[pattern.myid],
+ self.env[pattern.myid].new_rhyme))
# occurrences
if self.options['check_occurrences']:
@@ -155,6 +155,19 @@ class Template:
v.phon = self.env[pattern.myid].phon
v.parse()
+
+ # now that we have parsed, adjust rhyme to reflect last word length
+ # and check eye
+ if not rhyme_failed:
+ self.env[pattern.myid].adjustLastCount(v.lastCount())
+ if not self.env[pattern.myid].satisfied_eye():
+ old_phon = len(self.env[pattern.myid].phon)
+ self.env[pattern.myid].rollback()
+ errors.append(error.ErrorBadRhymeEye(self.env[pattern.myid],
+ self.env[pattern.myid].new_rhyme, old_phon))
+
+ rhyme_failed = False
+
errors = v.problems() + errors
if ofile:
diff --git a/verse.py b/verse.py
@@ -468,6 +468,21 @@ class Verse:
'fem': '\\', # preceding word ends by a mute e
}
+ def lastCount(self):
+ """return min number of syllables for last word"""
+
+ tot = 0
+ for c in self.chunks[::-1]:
+ if c['original'].endswith(' '):
+ if tot > 0:
+ break
+ if 'weights' in c.keys():
+ tot += min(c['weights'])
+ if ' ' in c['original'].rstrip():
+ if tot > 0:
+ break
+ return tot
+
def align2str(self, align):
return ''.join([x['text'] for x in align])
diff --git a/views/about.html b/views/about.html
@@ -185,6 +185,12 @@ options suivantes sont reconnues :</p>
<dt>eye_tolerance_ok</dt>
<dd>Autoriser les tolérances pour les rimes pour l'œil telles que "-é"/"-ai"
(vrai par défaut).</dd>
+ <dt>poor_eye_required</dt>
+ <dd>Imposer des rimes pour l'œil d'au moins 2 caractères dans le cas d'une
+ rime pauvre (un seul phonème) qui ne comporte pas de mots monosyllabiques
+ (vrai par défaut, n'a de sens que pour les rimes classiques).</dd>
+ <dt>poor_eye_supposed_ok</dt>
+ <dd>Autoriser la rime pour l'œil pour poor_eye_required (faux par défaut).</dd>
</dl>
<p>Désolé si tout cela semble un peu obscur. Vous pouvez regarder les modèles
@@ -403,6 +409,13 @@ form option:value and separated by spaces, with the following possibilities:</p>
<dt>eye_tolerance_ok</dt>
<dd>Allow tolerances for <em>rimes pour l'œil</em> such as "-é"/"-ai" (default
is true).</dd>
+ <dt>poor_eye_required</dt>
+ <dd>Require at least 2 characters in <em>rimes pour l'œil</em> when the sound
+ rhyme is poor (only one phoneme) and there are no monosyllabic words in the
+ rhyme (default is true, only applies to classical rhymes).</dd>
+ <dt>poor_eye_supposed_ok</dt>
+ <dd>Allow <em>rime pour l'œil</em> for poor_eye_required (default is
+ false).</dd>
</dl>
<p>Sorry if all of this is a bit obscure. You can have a look to the predefined