1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
// Fountain is a Markdown-like language for screenplays and the main inspiration for this tool.
// Read more at fountain.io
package fountain
import (
"bufio"
"github.com/lapingvino/lexington/lex"
"io"
"strings"
)
var Scene = []string{"INT", "EXT", "EST", "INT./EXT", "INT/EXT", "I/E"}
func CheckScene(row string) (bool, string, string) {
var scene bool
row = strings.ToUpper(row)
for _, prefix := range Scene {
if strings.HasPrefix(row, prefix+" ") ||
strings.HasPrefix(row, prefix+".") {
scene = true
}
}
return scene, "scene", row
}
func CheckTransition(row string) (bool, string, string) {
var trans bool
row = strings.ToUpper(row)
if strings.HasPrefix(row, ">") || strings.HasSuffix(row, " TO:") {
trans = true
}
return trans, "trans", strings.Trim(row, ">< ")
}
func CheckSynopse(row string) (bool, string, string) {
var synopse bool
if strings.HasPrefix(row, "=") {
synopse = true
}
return synopse, "synopse", strings.TrimLeft(row, "= ")
}
func CheckSection(row string) (bool, string, string) {
var section bool
if strings.HasPrefix(row, "#") {
section = true
}
return section, "section", row
}
// This is a Fountain parser, trying to be as close as possible to the description
// found at https://fountain.io/syntax but it can be incomplete.
// Over time more and more parts should be configurable here, e.g. INT/EXT translatable to other languages.
func Parse(file io.Reader) (out lex.Screenplay) {
var err error
var s string
var toParse []string // Fill with two to avoid out of bounds when backtracking
f := bufio.NewReader(file)
for err == nil {
s, err = f.ReadString('\n')
toParse = append(toParse, s)
}
toParse = append(toParse, "") // Trigger the backtracking also for the last line
out = make(lex.Screenplay, 2, len(toParse))
for i, row := range toParse {
i += 2
row = strings.TrimRight(row, "\n\r")
action := "action"
if row == strings.ToUpper(row) {
action = "allcaps"
}
if row == "" {
action = "empty"
// Backtracking for elements that need a following empty line
checkfuncs := []func(string) (bool, string, string){
CheckScene,
CheckTransition,
CheckSynopse,
CheckSection,
}
for _, checkfunc := range checkfuncs {
check, element, contents := checkfunc(out[i-1].Contents)
if check && out[i-2].Contents == "" {
out[i-1].Type = element
out[i-1].Contents = contents
break
}
}
}
if out[i-1].Type != "action" {
out[i-1].Contents = strings.TrimSpace(out[i-1].Contents)
}
// Backtracking to check for dialog sequence
if row != "" {
switch out[i-1].Type {
case "allcaps":
out[i-1].Type = "speaker"
fallthrough
case "paren", "dialog":
if row[0] == '(' && row[len(row)-1] == ')' {
action = "paren"
} else {
action = "dialog"
}
}
}
out = append(out, lex.Line{action, row})
}
return out[2:] // Remove the safety empty rows
}
|