forked from benrowe/books
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbook.go
244 lines (209 loc) · 6.06 KB
/
book.go
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package main
import (
"fmt"
"html/template"
"io/ioutil"
"path/filepath"
"github.com/kjk/notionapi"
"github.com/kjk/notionapi/caching_downloader"
"github.com/kjk/u"
)
// Book represents a book
type Book struct {
Title string // "Go", "jQuery" etcc
TitleLong string // "Essential Go", "Essential jQuery" etc.
// used by page index. defaults to: "<b>${TitleLong}</b> is a free book about ${Title} programming language."
summary string
NotionStartPageID string
RootPage *Page // a tree of pages
cachedPages []*Page // pages flattened into an array
idToPage map[string]*Page
Dir string // directory name for the book e.g. "go"
// generated toc javascript data
tocData []byte
// url of combined tocData and app.js
AppJSURL string
// name of a file in covers/ directory
// e.g. "Python.png"
CoverImageName string
client *notionapi.Client
// cache related
cache *Cache
}
// CacheDir returns a cache dir for this book
func (b *Book) CacheDir() string {
u.PanicIf(b.Dir == "", "b.Dir should not be empty")
return filepath.Join("cache", b.Dir)
}
// OutputCacheDir returns output cache dir for this book
func (b *Book) OutputCacheDir() string {
return filepath.Join(b.CacheDir(), "output")
}
// NotionCacheDir returns output cache dir for this book
func (b *Book) NotionCacheDir() string {
return filepath.Join(b.CacheDir(), "notion")
}
func (b *Book) cachePath() string {
return filepath.Join(b.CacheDir(), "cache.txt")
}
// SourceDir is where source files for a given book are
func (b *Book) SourceDir() string {
return filepath.Join("books", b.Dir)
}
// this is where html etc. files for a book end up
func (b *Book) destDir() string {
return filepath.Join(destEssentialDir, b.Dir)
}
// URL returns url of the book, used in index.tmpl.html
func (b *Book) URL() string {
return fmt.Sprintf("/essential/%s/", b.Dir)
}
func (b *Book) Summary() template.HTML {
if b.summary == "" {
b.summary = fmt.Sprintf("<b>%s</b> is a free book about %s programming language.", b.TitleLong, b.Title)
}
return template.HTML(b.summary)
}
// CanonnicalURL returns full url including host
func (b *Book) CanonnicalURL() string {
return urlJoin(siteBaseURL, b.URL())
}
// ShareOnTwitterText returns text for sharing on twitter
func (b *Book) ShareOnTwitterText() string {
return fmt.Sprintf(`"%s" - a free programming book`, b.TitleLong)
}
// CoverURL returns url to cover image
func (b *Book) CoverURL() string {
u.PanicIf(b.CoverImageName == "")
return fmt.Sprintf("/covers/%s", b.CoverImageName)
}
// CoverSmallURL returns url to small cover image
func (b *Book) CoverSmallURL() string {
u.PanicIf(b.CoverImageName == "")
return fmt.Sprintf("/covers_small/%s", b.CoverImageName)
}
// CoverFullURL returns a URL for the cover including host
func (b *Book) CoverFullURL() string {
return urlJoin(siteBaseURL, b.CoverURL())
}
// CoverTwitterFullURL returns a URL for the cover including host
func (b *Book) CoverTwitterFullURL() string {
u.PanicIf(b.CoverImageName == "")
coverURL := fmt.Sprintf("/covers/twitter/%s", b.CoverImageName)
return urlJoin(siteBaseURL, coverURL)
}
// Chapters returns pages that are top-level chapters
func (b *Book) Chapters() []*Page {
return b.RootPage.Pages
}
// GetAllPages returns all pages, flattened
func (b *Book) GetAllPages() []*Page {
// to prevent infinite recursion if pages show up twice (shouldn't happen)
if len(b.cachedPages) > 0 {
return b.cachedPages
}
if b.RootPage == nil {
return nil
}
seen := map[*Page]bool{}
pages := []*Page{b.RootPage}
curr := 0
for curr < len(pages) {
page := pages[curr]
curr++
if seen[page] {
continue
}
seen[page] = true
for _, p := range page.Pages {
p.Parent = page
}
pages = append(pages, page.Pages...)
}
return pages
}
// PagesCount returns total number of articles
func (b *Book) PagesCount() int {
return len(b.GetAllPages()) - 1 // don't count top page
}
// ChaptersCount returns number of chapters
func (b *Book) ChaptersCount() int {
return len(b.RootPage.Pages)
}
func updateBookAppJS(book *Book) {
srcName := fmt.Sprintf("app-%s.js", book.Dir)
d := book.tocData
if doMinify {
d2, err := minifier.Bytes("text/javascript", d)
maybePanicIfErr(err)
if err == nil {
logf("Minified %s from %d => %d (saved %d)\n", srcName, len(d), len(d2), len(d)-len(d2))
d = d2
}
}
sha1Hex := u.Sha1HexOfBytes(d)
name := nameToSha1Name(srcName, sha1Hex)
dst := filepath.Join("www", "s", name)
err := ioutil.WriteFile(dst, d, 0644)
maybePanicIfErr(err)
if err != nil {
return
}
book.AppJSURL = "/s/" + name
logf("Created %s\n", dst)
}
func calcPageHeadings(page *Page) {
var headings []*HeadingInfo
cb := func(block *notionapi.Block) {
isHeader := false
switch block.Type {
case notionapi.BlockHeader, notionapi.BlockSubHeader, notionapi.BlockSubSubHeader:
isHeader = true
}
if !isHeader {
return
}
id := notionapi.ToNoDashID(block.ID)
s := getInlinesPlain(block.InlineContent)
h := &HeadingInfo{
Text: s,
ID: id,
}
headings = append(headings, h)
}
blocks := []*notionapi.Block{page.NotionPage.Root()}
notionapi.ForEachBlock(blocks, cb)
page.Headings = headings
}
func (book *Book) afterPageDownload(page *notionapi.Page) error {
id := toNoDashID(page.ID)
p := &Page{
NotionPage: page,
NotionID: id,
Book: book,
}
book.idToPage[id] = p
downloadImages(book, p)
calcPageHeadings(p)
return nil
}
func downloadBook(book *Book) {
logf("Loading %s...\n", book.Title)
nProcessed = 0
nNotionPagesFromCache = 0
nDownloadedPages = 0
book.client = newNotionClient()
cacheDir := book.NotionCacheDir()
dirCache, err := caching_downloader.NewDirectoryCache(cacheDir)
must(err)
d := caching_downloader.New(dirCache, book.client)
d.EventObserver = eventObserver
d.RedownloadNewerVersions = !flgNoDownload
d.NoReadCache = flgDisableNotionCache
startPageID := book.NotionStartPageID
pages, err := d.DownloadPagesRecursively(startPageID, book.afterPageDownload)
must(err)
nPages := len(pages)
logf("Got %d pages for %s, downloaded: %d, from cache: %d\n", nPages, book.Title, nDownloadedPages, nNotionPagesFromCache)
bookFromPages(book)
}