Browse Source

Correct paths

master
Joop Kiefte 7 months ago
parent
commit
3509ac11c0
  1. 20
      fountain/example.fountain
  2. 202
      fountain/parse.go
  3. 65
      fountain/write.go
  4. 2
      go.mod
  5. 2
      lex/type.go
  6. 94
      main.go
  7. 12
      pdf/create.go
  8. 18
      readme.md
  9. 60
      rules/rules.go

20
fountain/example.fountain

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
INT. HOUSE - DAY
MARY
I can't believe how easy it is to write in Fountain.
TOM
(typing)
Look! I just made a parenthetical!
SOMETHING HAPPENS!
(what? I don't know...)
EXT. GARDEN
TOM
What am I doing here now?
To be honest, I have absolutely no idea!
And that means really no idea!

202
fountain/parse.go

@ -1,202 +0,0 @@ @@ -1,202 +0,0 @@
// 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"
)
// Scene contains all the prefixes the scene detection looks for.
// This can be changed with the toml configuration in the rules package.
var Scene = []string{"INT", "EXT", "EST", "INT./EXT", "INT/EXT", "EXT/INT", "EXT./INT", "I/E"}
func last(out *lex.Screenplay, i int) *lex.Line {
if len(*out) >= i {
return &(*out)[len(*out)-i]
} else {
line := lex.Line{Type: "empty"}
return &line
}
}
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
}
}
if strings.HasPrefix(row, ".") && !strings.HasPrefix(row, "..") {
row = row[1:]
scene = true
}
return scene, "scene", row
}
func CheckCrow(row string) (bool, string, string) {
var crow bool
var el string
row = strings.ToUpper(row)
if strings.HasPrefix(row, ">") || strings.HasSuffix(row, " TO:") {
crow = true
el = "trans"
}
if strings.HasPrefix(row, ">") && strings.HasSuffix(row, "<") {
el = "center"
}
return crow, el, strings.Trim(row, ">< ")
}
func CheckEqual(row string) (bool, string, string) {
var equal bool
var el string
if strings.HasPrefix(row, "=") {
equal = true
el = "synopse"
}
if len(row) >= 3 && strings.Trim(row, "=") == "" {
el = "newpage"
}
return equal, el, strings.TrimLeft(row, "= ")
}
func CheckSection(row string) (bool, string, string) {
var section bool
if strings.HasPrefix(row, "#") {
section = true
}
return section, "section", row
}
func CheckForce(row string) (bool, string, string) {
var force = true
var ftype string
if len(row) < 1 {
return false, "", ""
}
switch row[0] {
case '@':
ftype = "speaker"
case '~':
ftype = "lyrics"
case '!':
ftype = "action"
default:
force = false
}
if force {
row = row[1:]
}
return force, ftype, 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.
func Parse(scenes []string, file io.Reader) (out lex.Screenplay) {
Scene = scenes
var err error
var titlepage, dialog bool = true, false
var s, titletag string
var toParse []string
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
for _, row := range toParse {
if titlepage && !strings.Contains(toParse[0], ":") {
out = append(out, lex.Line{Type: "titlepage"})
out = append(out, lex.Line{Type: "Title", Contents: "Untitled"})
out = append(out, lex.Line{Type: "newpage"})
titlepage = false
}
row = strings.TrimRight(row, "\n\r")
action := "action"
if row == "" {
action = "empty"
if titlepage {
titlepage = false
out = append(out, lex.Line{Type: "newpage"})
continue
}
}
if last(&out, 1).Type != "action" {
last(&out, 1).Contents = strings.TrimSpace(last(&out, 1).Contents)
}
// Backtracking to check for dialog sequence
if dialog {
if row == "" {
dialog = false
action = "empty"
if last(&out, 1).Type == "speaker" {
last(&out, 1).Type = "action"
}
} else {
if row[0] == '(' && row[len(row)-1] == ')' {
action = "paren"
} else {
action = "dialog"
}
}
}
charcheck := strings.Split(row, "(")
if charcheck[0] == strings.ToUpper(charcheck[0]) && action == "action" {
action = "speaker"
dialog = true
}
checkfuncs := []func(string) (bool, string, string){
CheckScene, // should actually check for empty lines, but doing that creates more problems than it solves
CheckCrow,
CheckEqual,
CheckSection,
CheckForce,
}
for _, checkfunc := range checkfuncs {
check, element, contents := checkfunc(row)
if check {
action = element
row = contents
if action == "speaker" {
dialog = true
}
break
}
}
if titlepage {
if titletag == "" {
out = append(out, lex.Line{Type: "titlepage"})
}
split := strings.SplitN(row, ":", 2)
if len(split) == 2 && !strings.HasPrefix(row, " ") {
action = split[0]
switch strings.ToLower(action) {
case "title", "credit", "author", "authors":
titletag = "title"
default:
if titletag == "title" {
out = append(out, lex.Line{Type: "metasection"})
}
titletag = action
}
row = strings.TrimSpace(split[1])
if row == "" {
continue
}
} else {
action = titletag
row = strings.TrimSpace(row)
}
}
out = append(out, lex.Line{action, row})
}
return out
}

65
fountain/write.go

@ -1,65 +0,0 @@ @@ -1,65 +0,0 @@
package fountain
import (
"github.com/lapingvino/lexington/lex"
"io"
"fmt"
"strings"
)
func Write(f io.Writer, scene []string, screenplay lex.Screenplay) {
var titlepage = "start"
for _, line := range screenplay {
element := line.Type
if titlepage == "start" && line.Type != "titlepage" {
titlepage = ""
}
if titlepage != "" {
element = titlepage
}
switch element {
case "start":
titlepage = "titlepage"
case "titlepage":
if line.Type == "metasection" {
continue
}
if line.Type == "newpage" {
fmt.Fprintln(f, "")
titlepage = ""
continue
}
fmt.Fprintf(f, "%s: %s\n", line.Type, line.Contents)
case "newpage":
fmt.Fprintln(f, "===")
case "empty":
fmt.Fprintln(f, "")
case "speaker":
if line.Contents != strings.ToUpper(line.Contents) {
fmt.Fprint(f, "@")
}
fmt.Fprintln(f, line.Contents)
case "scene":
var supported bool
for _, prefix := range scene {
if strings.HasPrefix(line.Contents, prefix+" ") ||
strings.HasPrefix(line.Contents, prefix+".") {
supported = true
}
}
if !supported {
fmt.Fprint(f, ".")
}
fmt.Fprintln(f, line.Contents)
case "lyrics":
fmt.Fprintf(f, "~%s\n", line.Contents)
case "action":
if line.Contents == strings.ToUpper(line.Contents) {
fmt.Fprint(f, "!")
}
fmt.Fprintln(f, line.Contents)
default:
fmt.Fprintln(f, line.Contents)
}
}
}

2
go.mod

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
module github.com/lapingvino/lexington
module github.com/lapingvino/printdraftfast
require (
github.com/BurntSushi/toml v0.3.1

2
lex/type.go

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
// The lex format is basically a parse tree for screenplays, which enables quick debugging.
package lex
type Screenplay []Line
type Document []Line
type Line struct {
Type string

94
main.go

@ -5,10 +5,8 @@ @@ -5,10 +5,8 @@
package main
import (
"github.com/lapingvino/lexington/fountain"
"github.com/lapingvino/lexington/lex"
"github.com/lapingvino/lexington/pdf"
"github.com/lapingvino/lexington/rules"
"git.kiefte.eu/lapingvino/printdraftfast/pdf"
"git.kiefte.eu/lapingvino/printdraftfast/rules"
"flag"
"io"
@ -16,7 +14,6 @@ import ( @@ -16,7 +14,6 @@ import (
"os"
"strings"
"time"
)
func main() {
@ -24,15 +21,11 @@ func main() { @@ -24,15 +21,11 @@ func main() {
defer func() {
log.Printf("Conversion took %v", time.Since(start))
}()
config := flag.String("config", "lexington.toml", "Configuration file to use.")
config := flag.String("config", "printdraftfast.toml", "Configuration file to use.")
dump := flag.Bool("dumpconfig", false, "Dump the default configuration to the location of --config to be adapted manually.")
scenein := flag.String("scenein", "", "Configuration to use for scene header detection on input.")
sceneout := flag.String("sceneout", "", "Configuration to use for scene header detection on output.")
elements := flag.String("e", "default", "Element settings from settings file to use.")
input := flag.String("i", "-", "Input from provided filename. - means standard input.")
output := flag.String("o", "-", "Output to provided filename. - means standard output.")
from := flag.String("from", "", "Input file type. Choose from fountain, lex [, fdx]. Formats between angle brackets are planned to be supported, but are not supported by this binary.")
to := flag.String("to", "", "Output file type. Choose from pdf (built-in, doesn't support inline markup), lex (helpful for troubleshooting and correcting fountain parsing), fountain, [html, epub*, mobi*, docx*, odt*, fdx]. Formats marked with a little star need an additional external tool to work. Formats between angle brackets are planned to be supported, but are not supported by this binary.")
output := flag.String("o", "-", "Output to provided filename.")
help := flag.Bool("help", false, "Show this help message")
flag.Parse()
@ -50,40 +43,8 @@ func main() { @@ -50,40 +43,8 @@ func main() {
return
}
ins := strings.Split(*input, ".")
if len(ins) > 1 && *from == "" {
*from = ins[len(ins)-1]
}
if len(ins) > 2 && *scenein == "" {
*scenein = ins[len(ins)-2]
}
outs := strings.Split(*output, ".")
if len(outs) > 1 && *to == "" {
*to = outs[len(outs)-1]
}
if len(outs) > 2 && *sceneout == "" {
*sceneout = outs[len(outs)-2]
}
if *from == "" {
*from = "fountain"
}
if *to == "" && *output == "-" {
*to = "lex"
}
if *scenein == "" {
*scenein = "en"
}
if *sceneout == "" {
*sceneout = "en"
}
log.Printf("Scenein: %s ; Sceneout: %s ;\n", *scenein, *sceneout)
var infile io.Reader
var outfile io.Writer
var i lex.Screenplay
conf := rules.GetConf(*config)
@ -99,47 +60,14 @@ func main() { @@ -99,47 +60,14 @@ func main() {
}
}
if *output == "-" {
outfile = os.Stdout
log.Println("Writing to Stdout")
} else {
if *output == "" && len(ins) > 0 && ins[0] != "" {
*output = ins[0] + "." + *to
}
var err error
outfile, err = os.Create(*output)
if err != nil {
log.Println("Error creating output file: ", err)
return
}
}
// TODO: Parser goes here
log.Println("Input type is ", *from)
switch *from {
case "lex":
i = lex.Parse(infile)
case "fountain":
i = fountain.Parse(conf.Scenes[*scenein], infile)
default:
log.Printf("%s is not a valid input type", *from)
if *output == "-" && len(ins) > 0 && ins[0] != "" {
*output = ins[0] + ".pdf"
}
log.Println("Output type is ", *to)
switch *to {
case "pdf":
if *output == "-" && len(ins) > 0 && ins[0] != "" {
*output = ins[0] + ".pdf"
}
if *output == "-" {
log.Println("Cannot write PDF to standard output")
return
}
pdf.Create(*output, conf.Elements[*elements], i)
case "lex":
lex.Write(i, outfile)
case "fountain":
fountain.Write(outfile, conf.Scenes[*sceneout], i)
default:
log.Printf("%s is not a valid output type", *to)
if *output == "-" {
log.Println("Cannot write PDF to standard output")
return
}
pdf.Create(*output, conf.Elements[*elements], i)
}

12
pdf/create.go

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
// The PDF package of Lexington creates a Screenplay PDF out of the Lex screenplay parsetree. This can be generated with the several other packages, e.g. the fountain package that parses fountain to lex in preparation.
// The PDF package of PrintDraftFast creates a PDF out of the Lex markdown parsetree.
package pdf
import (
"github.com/lapingvino/lexington/lex"
"github.com/lapingvino/lexington/rules"
"github.com/lapingvino/lexington/font"
"git.kiefte.eu/lapingvino/printdraftfast/lex"
"git.kiefte.eu/lapingvino/printdraftfast/rules"
"git.kiefte.eu/lapingvino/printdraftfast/font"
"strconv"
"strings"
@ -15,7 +15,7 @@ import ( @@ -15,7 +15,7 @@ import (
type Tree struct {
PDF *gofpdf.Fpdf
Rules rules.Set
F lex.Screenplay
F lex.Document
HTML gofpdf.HTMLBasicType
}
@ -34,7 +34,7 @@ func (t Tree) Render() { @@ -34,7 +34,7 @@ func (t Tree) Render() {
t.PDF.SetHeaderFuncMode(func() {
t.PDF.SetFont("CourierPrime", "", 12)
t.PDF.SetXY(-1, 0.5)
t.PDF.Cell(0, 0, strconv.Itoa(t.PDF.PageNo()-1)+".")
t.PDF.Cell(0, 0, strconv.Itoa(t.PDF.PageNo())+".")
}, true)
continue
case "titlepage":

18
readme.md

@ -1,16 +1,4 @@ @@ -1,16 +1,4 @@
Lexington commandline tool for screenwriters
============================================
Print Draft Fast
================
Lexington helps you convert between Final Draft, Fountain and its own lex file formats, and output to PDF, HTML and ebook formats.
At the moment the Fountain parser should be pretty decent, although still lacking features like simultaneous dialog, and inline markup is not yet supported and might never be. Also the PDF output doesn't do anything to keep pieces from your screenplay together. Feel free to contribute and help me out in knowing how best to handle this!
To run the tool, make sure to have Go installed first and then run
`go get github.com/lapingvino/lexington`
to install Lexington to your go/bin directory. If this directory is in your execution path, you can then run it like
`lexington -i inputfile.fountain -o outputfile.pdf -to pdf`
For a more complete overview of the command line options, use `lexington -help`.
Based on my work on [lexington](https://github.com/lapingvino/lexington) and a question on Reddit about having a fast Markdown to PDF conversion, here is my attempt to create a quick and dirty PDF creation tool for Markdown source files.

60
rules/rules.go

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// The rules package of Lexington provides the tools around configuration of how a screenplay should look. The default should work but can be adjusted for a personal touch..
// The rules package of PrintDraftFast provides the tools around configuration of how a draft should look. The default should work but can be adjusted for a personal touch..
package rules
type Format struct {
@ -15,10 +15,10 @@ type Format struct { @@ -15,10 +15,10 @@ type Format struct {
type Set map[string]Format
func (s Set) Get(action string) (f Format) {
f, ok := s[action]
func (s Set) Get(style string) (f Format) {
f, ok := s[style]
if !ok {
f = s["action"]
f = s["default"]
f.Hide = true
}
if f.Font == "" {
@ -34,62 +34,16 @@ func (s Set) Get(action string) (f Format) { @@ -34,62 +34,16 @@ func (s Set) Get(action string) (f Format) {
}
var Default = Set{
"action": {
"default": {
Left: 1.5,
Right: 1,
},
"speaker": {
Left: 3.7,
Right: 1.5,
},
"dialog": {
Left: 2.5,
Right: 1.5,
},
"scene": {
Left: 1.5,
Right: 1,
Style: "b",
},
"paren": {
Left: 3.1,
Right: 1.5,
},
"trans": {
Left: 1.5,
Right: 1,
Align: "R",
},
"note": {
Left: 1.5,
Right: 1,
},
"allcaps": {
Left: 1.5,
Right: 1,
},
"empty": {
Left: 1.5,
Right: 1,
},
"title": {
Left: 1.5,
Right: 1,
Align: "C",
},
"meta": {
Left: 1.5,
Right: 1,
"h1": {
Size: 2,
},
"center": {
Left: 1.5,
Right: 1,
Align: "C",
},
"lyrics": {
Left: 2,
Right: 2,
Style: "i",
Font: "Helvetica",
},
}

Loading…
Cancel
Save