-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathassemble.go
More file actions
144 lines (135 loc) · 3.74 KB
/
assemble.go
File metadata and controls
144 lines (135 loc) · 3.74 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package binder
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)
// AssemblyConfig holds the parameters for assembling a manuscript.
type AssemblyConfig struct {
InputFile string
OutputDir string
WordCount bool
SceneHeadings bool // include scene filenames as ## headings
}
// WordCountResult holds the word count for a single scene file.
type WordCountResult struct {
Scene string
Count int
}
// AssembleMarkdown assembles a book's scenes into per-chapter markdown files
// and writes a metadata.yaml for pandoc. Returns the parsed FrontMatter and
// any word count results (if config.WordCount is true).
func AssembleMarkdown(config AssemblyConfig) (*FrontMatter, []WordCountResult, error) {
_ = os.RemoveAll(config.OutputDir)
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
return nil, nil, err
}
frontMatter, book, err := LoadBook(config.InputFile)
if err != nil {
return nil, nil, err
}
var counts []WordCountResult
cnum := 1
for chapter := range book.GetChapters() {
if err := chapter.Validate(); err != nil {
return nil, nil, err
}
chapterOutPath := filepath.Join(config.OutputDir, fmt.Sprintf("%03d-%s.md", cnum, chapter.HeadingToFilename()))
cnum += 1
fd, err := os.OpenFile(chapterOutPath, os.O_CREATE|os.O_WRONLY|os.O_SYNC, 0644)
if err != nil {
return nil, nil, err
}
if chapter.Heading != "" {
if _, err := fmt.Fprintf(fd, "# %s\n\n", chapter.Heading); err != nil {
fd.Close()
return nil, nil, err
}
}
if config.WordCount {
for _, scene := range chapter.Scenes {
wc, err := SceneWordCount(scene)
if err != nil {
fd.Close()
return nil, nil, err
}
counts = append(counts, WordCountResult{
Scene: filepath.Base(scene),
Count: wc,
})
}
}
if err := WriteMarkdownScenes(fd, chapter.Scenes, config.SceneHeadings); err != nil {
fd.Close()
return nil, nil, err
}
if err := fd.Close(); err != nil {
return nil, nil, err
}
}
if err := WriteMetadata(frontMatter, config.OutputDir); err != nil {
return nil, nil, err
}
return frontMatter, counts, nil
}
// WriteMarkdownScenes writes the contents of scene files to fd, separated
// by scene break markers. When sceneHeadings is true, each scene is preceded
// by a ## heading with the scene filename (without extension).
func WriteMarkdownScenes(fd *os.File, sceneFiles []string, sceneHeadings bool) error {
lastSceneIndex := len(sceneFiles) - 1
for i, sceneFile := range sceneFiles {
if sceneHeadings {
name := strings.TrimSuffix(filepath.Base(sceneFile), ".md")
if _, err := fmt.Fprintf(fd, "## %s\n\n", name); err != nil {
return err
}
}
sceneText, err := os.ReadFile(sceneFile)
if err != nil {
return err
}
if _, err := fd.Write(sceneText); err != nil {
return err
}
if i < lastSceneIndex {
if _, err := fd.WriteString("\n\n***\n\n"); err != nil {
return err
}
}
}
return nil
}
// SceneWordCount counts the words in a file using pure Go.
func SceneWordCount(path string) (int, error) {
f, err := os.Open(path)
if err != nil {
return 0, err
}
defer f.Close()
count := 0
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
return 0, err
}
return count, nil
}
// FormatWordCount formats a WordCountResult as a human-readable string.
func FormatWordCount(result WordCountResult) string {
return fmt.Sprintf("%s: %d words", result.Scene, result.Count)
}
// OutputFiles returns a sorted list of the assembled chapter markdown files
// in the output directory.
func OutputFiles(outputDir string) ([]string, error) {
pattern := filepath.Join(outputDir, "0*.md")
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
return matches, nil
}