Repository navigation
Expand file tree
/
Copy pathsource.go
More file actions
172 lines (161 loc) · 4.58 KB
/
Copy pathsource.go
File metadata and controls
172 lines (161 loc) · 4.58 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package main
import (
"bytes"
"io/fs"
"net/url"
"path"
"slices"
"strings"
"github.com/some-programs/present/pkg/present"
)
// A slideSource is the source of one slide of a presentation, which the
// slides show beside it with -doc, or exported with -source, when S is
// pressed.
type slideSource struct {
Number int // the number of the slide, from 1
Text string // the lines of the slide in the presentation file
Files []sourceFile // the files that the slide's commands use
lines []present.Line // the lines of Text
first int // the line number of the first of lines, from 1
}
// A sourceLine is a line of a slideSource or a sourceFile, with its number
// in the file and the class that marks what it is in present markup, if
// anything.
type sourceLine struct {
N int
Text, Class string
}
// Lines returns the lines of s.Text.
func (s slideSource) Lines() []sourceLine {
var lines []sourceLine
for i, l := range s.lines {
var class string
switch l.Kind {
case present.TitleLine, present.HeadingLine:
class = "src-heading"
case present.CommandLine:
class = "src-command"
case present.NoteLine:
class = "src-note"
case present.CommentLine:
class = "src-comment"
}
lines = append(lines, sourceLine{s.first + i, l.Text, class})
}
return lines
}
// A sourceFile is a file, or a page, that a command of a slide uses.
type sourceFile struct {
Name string // as written in the command
URL string // where it is served
Text string // the contents of a text file, such as a program
}
// Lines returns the lines of f.Text.
func (f sourceFile) Lines() []sourceLine {
var lines []sourceLine
for i, text := range strings.Split(strings.TrimSuffix(f.Text, "\n"), "\n") {
lines = append(lines, sourceLine{N: i + 1, Text: text})
}
return lines
}
// slideSources returns the sources of the slides of doc, parsed from src,
// the contents of docFile in fsys, which is served at the URL path urlPath:
// first the title slide, made from the header and authors, then one for
// each section.
func slideSources(doc *present.Doc, src []byte, fsys fs.FS, docFile, urlPath string) ([]slideSource, error) {
lines, err := present.ScanLines(bytes.NewReader(src), docFile)
if err != nil {
return nil, err
}
// Each slide runs up to the heading of the next one.
starts := []int{0}
for _, s := range doc.Sections {
starts = append(starts, s.Line-1)
}
var sources []slideSource
for i, start := range starts {
end := len(lines)
if i+1 < len(starts) {
end = starts[i+1]
}
slide := lines[start:end]
for len(slide) > 0 && strings.TrimSpace(slide[len(slide)-1].Text) == "" {
slide = slide[:len(slide)-1]
}
var text []string
for _, l := range slide {
text = append(text, l.Text)
}
sources = append(sources, slideSource{
Number: i + 1,
Text: strings.Join(text, "\n"),
Files: commandFiles(slide, fsys, docFile, urlPath),
lines: slide,
first: start + 1,
})
}
return sources, nil
}
// commandFiles returns the files that the commands among lines, of docFile
// in fsys, use. The contents of programs, included HTML and diagram sources
// are included.
func commandFiles(lines []present.Line, fsys fs.FS, docFile, urlPath string) []sourceFile {
var files []sourceFile
for _, l := range lines {
if l.Kind != present.CommandLine {
continue
}
line := l.Text
args := strings.Fields(line)
var name string
if len(args) > 1 {
name = args[1]
}
var text bool
switch args[0] {
case ".code", ".play":
// Flags such as -edit come before the file name.
name = ""
for _, a := range args[1:] {
if !strings.HasPrefix(a, "-") {
name = a
break
}
}
text = true
case ".html":
text = true
case ".image":
// Flags with their values, such as -background-color
// transparent, come before the file name.
rest := args[1:]
for len(rest) > 0 && strings.HasPrefix(rest[0], "-") {
rest = rest[min(2, len(rest)):]
}
name = ""
if len(rest) > 0 {
name = rest[0]
}
text = present.IsDiagram(name)
case ".background", ".video", ".iframe":
default:
continue
}
if name == "" || slices.ContainsFunc(files, func(f sourceFile) bool { return f.Name == name }) {
continue
}
f := sourceFile{Name: name, URL: name}
if u, err := url.Parse(name); err != nil || u.Scheme != "" || u.Host != "" || strings.HasPrefix(u.Path, "/") {
text = false
} else {
f.URL = path.Join(path.Dir(urlPath), name)
}
if text {
if data, err := fs.ReadFile(fsys, path.Join(path.Dir(docFile), name)); err == nil {
f.Text = string(data)
}
}
files = append(files, f)
}
return files
}