///|
fn character_parts(cue : String) -> (String, String) {
match cue.find("(") {
Some(i) if cue.has_suffix(")") =>
(stripped(cue[:i].to_owned()), cue[i:].to_owned())
_ => (stripped(cue), "")
}
}
///|
fn is_character(text : String) -> Bool {
if text.has_prefix("@") {
let forced = stripped(text[1:].to_owned())
let cue = if forced.has_suffix("^") {
stripped(forced[:forced.length() - 1].to_owned())
} else {
forced
}
let (name, _) = character_parts(cue)
return name != ""
}
if text.has_suffix("TO:") {
return false
}
let cue = if text.has_suffix("^") {
stripped(text[:text.length() - 1].to_owned())
} else {
text
}
let (name, _) = character_parts(cue)
name != "" &&
name == name.to_upper() &&
name.to_array().any(fn(c) { c >= 'A' && c <= 'Z' })
}
///|
pub(all) struct SpeechPart {
text : String
direction : Bool
line : Int
} derive(Eq, Debug, ToJson)
///|
pub(all) struct Turn {
character : String
extension : String
line : Int
end_line : Int
dialogue : Array[String]
directions : Array[String]
parts : Array[SpeechPart]
dual : Bool
} derive(Eq, Debug, ToJson)
///|
/// A turn is a cue followed by contiguous dialogue/parenthetical elements.
pub fn turns(doc : Document) -> Array[Turn] {
let out : Array[Turn] = []
let mut i = 0
while i < doc.elements.length() {
let e = doc.elements[i]
if e.kind != Character {
i += 1
continue
}
let dialogue : Array[String] = []
let directions : Array[String] = []
let parts : Array[SpeechPart] = []
let mut end_line = e.line
let mut j = i + 1
while j < doc.elements.length() {
let next = doc.elements[j]
if next.kind == Dialogue {
dialogue.push(next.text)
} else if next.kind == Parenthetical {
directions.push(next.text)
} else {
break
}
parts.push({
text: next.text,
direction: next.kind == Parenthetical,
line: next.line,
})
end_line = next.line
j += 1
}
out.push({
character: e.text,
extension: e.detail,
line: e.line,
end_line,
dialogue,
directions,
parts,
dual: e.dual,
})
i = j
}
out
}
///|
/// Walk only the preceding turn; action/headings/outline break pairing.
fn can_pair_dual(elements : Array[Element]) -> Bool {
let mut i = elements.length() - 1
let mut spoken = false
while i >= 0 {
let e = elements[i]
match e.kind {
Dialogue => spoken = true
Parenthetical => ()
Character => return spoken && !e.dual
_ => return false
}
i -= 1
}
false
}
///|
pub(all) struct DualPair {
left : Turn
right : Turn
} derive(Eq, Debug, ToJson)
///|
pub fn dual_pairs(doc : Document) -> Array[DualPair] {
let ts = turns(doc)
let out : Array[DualPair] = []
for i = 1; i < ts.length(); i = i + 1 {
if ts[i].dual {
out.push({ left: ts[i - 1], right: ts[i], })
}
}
out
}