ligarb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/assets/style.css +592 -0
- data/exe/ligarb +6 -0
- data/lib/ligarb/asset_manager.rb +90 -0
- data/lib/ligarb/builder.rb +135 -0
- data/lib/ligarb/chapter.rb +246 -0
- data/lib/ligarb/cli.rb +341 -0
- data/lib/ligarb/config.rb +118 -0
- data/lib/ligarb/initializer.rb +97 -0
- data/lib/ligarb/template.rb +38 -0
- data/lib/ligarb/version.rb +5 -0
- data/templates/book.html.erb +335 -0
- metadata +107 -0
data/lib/ligarb/cli.rb
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version"
|
|
4
|
+
require_relative "builder"
|
|
5
|
+
require_relative "initializer"
|
|
6
|
+
|
|
7
|
+
module Ligarb
|
|
8
|
+
module CLI
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def run(args)
|
|
12
|
+
command = args.shift
|
|
13
|
+
|
|
14
|
+
case command
|
|
15
|
+
when "build"
|
|
16
|
+
config_path = args.first || "book.yml"
|
|
17
|
+
Builder.new(config_path).build
|
|
18
|
+
when "init"
|
|
19
|
+
Initializer.new(args.first).run
|
|
20
|
+
when "--help", "-h", nil
|
|
21
|
+
print_usage
|
|
22
|
+
when "help"
|
|
23
|
+
print_spec
|
|
24
|
+
when "version", "--version", "-v"
|
|
25
|
+
puts "ligarb #{VERSION}"
|
|
26
|
+
else
|
|
27
|
+
$stderr.puts "Unknown command: #{command}"
|
|
28
|
+
$stderr.puts "Run 'ligarb --help' for usage information."
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def print_usage
|
|
34
|
+
puts <<~USAGE
|
|
35
|
+
ligarb #{VERSION} - Generate a single-page HTML book from Markdown files
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
ligarb init [DIRECTORY] Create a new book project
|
|
39
|
+
ligarb build [CONFIG] Build the HTML book (default CONFIG: book.yml)
|
|
40
|
+
ligarb help Show detailed specification (for AI integration)
|
|
41
|
+
ligarb version Show version number
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
-h, --help Show this usage summary
|
|
45
|
+
-v, --version Show version number
|
|
46
|
+
|
|
47
|
+
Configuration (book.yml):
|
|
48
|
+
title (required) Book title
|
|
49
|
+
chapters (required) Book structure (chapters, parts, appendix)
|
|
50
|
+
author (optional) Author name (default: "")
|
|
51
|
+
language (optional) HTML lang attribute (default: "en")
|
|
52
|
+
output_dir (optional) Output directory (default: "build")
|
|
53
|
+
chapter_numbers (optional) Show chapter/section numbers (default: true)
|
|
54
|
+
style (optional) Custom CSS file path (default: none)
|
|
55
|
+
repository (optional) GitHub repository URL for "Edit on GitHub" links
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
ligarb build
|
|
59
|
+
ligarb build path/to/book.yml
|
|
60
|
+
USAGE
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def print_spec
|
|
64
|
+
puts <<~SPEC
|
|
65
|
+
ligarb - Generate a single-page HTML book from Markdown files
|
|
66
|
+
|
|
67
|
+
Version: #{VERSION}
|
|
68
|
+
|
|
69
|
+
== Overview ==
|
|
70
|
+
|
|
71
|
+
ligarb converts multiple Markdown files into a self-contained index.html.
|
|
72
|
+
The generated HTML includes:
|
|
73
|
+
- A left sidebar with a searchable table of contents (h1-h3)
|
|
74
|
+
- Chapter-based content switching in the main area
|
|
75
|
+
- Permalink support via URL hash (#chapter-slug)
|
|
76
|
+
- Responsive design with print-friendly styles
|
|
77
|
+
- Syntax-highlighted code blocks
|
|
78
|
+
- Search with content highlighting
|
|
79
|
+
- Chapter and section numbering (configurable)
|
|
80
|
+
- Previous/Next chapter navigation
|
|
81
|
+
- Dark mode toggle (saved to localStorage)
|
|
82
|
+
- Custom CSS support
|
|
83
|
+
- "Edit on GitHub" links (optional)
|
|
84
|
+
- Footnotes (kramdown syntax)
|
|
85
|
+
|
|
86
|
+
== Commands ==
|
|
87
|
+
|
|
88
|
+
ligarb init [DIRECTORY] Create a new book project with scaffolding.
|
|
89
|
+
If DIRECTORY is given, creates and populates that directory.
|
|
90
|
+
If omitted, populates the current directory.
|
|
91
|
+
Generates book.yml, 01-introduction.md, and images/.
|
|
92
|
+
If .md files already exist, registers them as chapters.
|
|
93
|
+
Aborts if book.yml already exists.
|
|
94
|
+
|
|
95
|
+
ligarb build [CONFIG] Build the HTML book.
|
|
96
|
+
CONFIG defaults to 'book.yml' in the current directory.
|
|
97
|
+
|
|
98
|
+
ligarb help Show this detailed specification.
|
|
99
|
+
|
|
100
|
+
ligarb --help Show short usage summary.
|
|
101
|
+
|
|
102
|
+
ligarb version Show the version number.
|
|
103
|
+
|
|
104
|
+
== Configuration: book.yml ==
|
|
105
|
+
|
|
106
|
+
The configuration file is a YAML file with the following fields:
|
|
107
|
+
|
|
108
|
+
title: (required) The book title displayed in the header and <title> tag.
|
|
109
|
+
author: (optional) Author name displayed in the header. Default: empty.
|
|
110
|
+
language: (optional) HTML lang attribute value. Default: "en".
|
|
111
|
+
output_dir: (optional) Output directory relative to book.yml. Default: "build".
|
|
112
|
+
chapter_numbers: (optional) Show chapter/section numbers (e.g. "1.", "1.1", "1.1.1").
|
|
113
|
+
Default: true.
|
|
114
|
+
style: (optional) Path to a custom CSS file relative to book.yml.
|
|
115
|
+
Loaded after the default styles, so it can override any rule.
|
|
116
|
+
repository: (optional) GitHub repository URL (e.g. "https://github.com/user/repo").
|
|
117
|
+
When set, each chapter shows a "View on GitHub" link.
|
|
118
|
+
The link points to {repository}/blob/HEAD/{path-from-git-root}.
|
|
119
|
+
The chapter path is resolved relative to the Git repository root.
|
|
120
|
+
chapters: (required) Book structure. An array that can contain:
|
|
121
|
+
- A cover: a centered title/landing page
|
|
122
|
+
- A string: a chapter Markdown file path (relative to book.yml)
|
|
123
|
+
- A part: groups chapters under a titled section
|
|
124
|
+
- An appendix: groups chapters with alphabetic numbering (A, B, C, ...)
|
|
125
|
+
|
|
126
|
+
The chapters array supports four element types:
|
|
127
|
+
|
|
128
|
+
1. Cover (object with 'cover' key):
|
|
129
|
+
chapters:
|
|
130
|
+
- cover: cover.md # Markdown file: displayed as centered title page
|
|
131
|
+
# Not shown in the TOC sidebar.
|
|
132
|
+
|
|
133
|
+
2. Plain chapter (string):
|
|
134
|
+
chapters:
|
|
135
|
+
- 01-introduction.md
|
|
136
|
+
|
|
137
|
+
3. Part (object with 'part' and 'chapters' keys):
|
|
138
|
+
chapters:
|
|
139
|
+
- part: part1.md # Markdown file: h1 = part title, body = opening text
|
|
140
|
+
chapters:
|
|
141
|
+
- 01-introduction.md
|
|
142
|
+
- 02-getting-started.md
|
|
143
|
+
|
|
144
|
+
4. Appendix (object with 'appendix' key, value is array of chapter files):
|
|
145
|
+
chapters:
|
|
146
|
+
- appendix:
|
|
147
|
+
- a1-references.md
|
|
148
|
+
- a2-glossary.md
|
|
149
|
+
|
|
150
|
+
These can be combined freely:
|
|
151
|
+
|
|
152
|
+
chapters:
|
|
153
|
+
- cover: cover.md
|
|
154
|
+
- part: part1.md
|
|
155
|
+
chapters:
|
|
156
|
+
- 01-introduction.md
|
|
157
|
+
- 02-getting-started.md
|
|
158
|
+
- part: part2.md
|
|
159
|
+
chapters:
|
|
160
|
+
- 03-advanced.md
|
|
161
|
+
- appendix:
|
|
162
|
+
- a1-references.md
|
|
163
|
+
|
|
164
|
+
Part numbering is sequential across parts (1, 2, 3, ...).
|
|
165
|
+
Appendix numbering uses letters (A, B, C, ...).
|
|
166
|
+
|
|
167
|
+
Example book.yml (simple):
|
|
168
|
+
|
|
169
|
+
title: "My Software Guide"
|
|
170
|
+
author: "Author Name"
|
|
171
|
+
language: "ja"
|
|
172
|
+
chapters:
|
|
173
|
+
- 01-introduction.md
|
|
174
|
+
- 02-getting-started.md
|
|
175
|
+
- 03-advanced.md
|
|
176
|
+
|
|
177
|
+
Example book.yml (with parts and appendix):
|
|
178
|
+
|
|
179
|
+
title: "My Software Guide"
|
|
180
|
+
author: "Author Name"
|
|
181
|
+
language: "ja"
|
|
182
|
+
chapters:
|
|
183
|
+
- part: part1.md
|
|
184
|
+
chapters:
|
|
185
|
+
- 01-introduction.md
|
|
186
|
+
- 02-getting-started.md
|
|
187
|
+
- part: part2.md
|
|
188
|
+
chapters:
|
|
189
|
+
- 03-advanced.md
|
|
190
|
+
- 04-deployment.md
|
|
191
|
+
- appendix:
|
|
192
|
+
- a1-config-reference.md
|
|
193
|
+
|
|
194
|
+
== Directory Structure ==
|
|
195
|
+
|
|
196
|
+
A typical book project has this structure:
|
|
197
|
+
|
|
198
|
+
my-book/
|
|
199
|
+
├── book.yml # Configuration file
|
|
200
|
+
├── part1.md # Part opening page (optional)
|
|
201
|
+
├── 01-introduction.md # Markdown source files
|
|
202
|
+
├── 02-getting-started.md
|
|
203
|
+
├── 03-advanced.md
|
|
204
|
+
└── images/ # Image files (optional)
|
|
205
|
+
├── screenshot.png
|
|
206
|
+
└── diagram.svg
|
|
207
|
+
|
|
208
|
+
After running 'ligarb build', the output is:
|
|
209
|
+
|
|
210
|
+
my-book/
|
|
211
|
+
└── build/
|
|
212
|
+
├── index.html # Single-page HTML book
|
|
213
|
+
├── js/ # Auto-downloaded (only if needed)
|
|
214
|
+
├── css/ # Auto-downloaded (only if needed)
|
|
215
|
+
└── images/ # Copied image files
|
|
216
|
+
|
|
217
|
+
== Markdown Files ==
|
|
218
|
+
|
|
219
|
+
Each Markdown file represents one chapter. ligarb uses GitHub Flavored
|
|
220
|
+
Markdown (GFM) via kramdown. Supported syntax includes:
|
|
221
|
+
|
|
222
|
+
- Headings (# h1, ## h2, ### h3) — used for TOC generation
|
|
223
|
+
- Code blocks with language-specific syntax highlighting (``` fenced blocks)
|
|
224
|
+
- Tables, task lists, strikethrough, and other GFM extensions
|
|
225
|
+
- Inline HTML
|
|
226
|
+
|
|
227
|
+
The first heading (h1) in each file becomes the chapter title in the TOC.
|
|
228
|
+
|
|
229
|
+
== Fenced Code Blocks ==
|
|
230
|
+
|
|
231
|
+
The following fenced code block types are automatically detected and
|
|
232
|
+
rendered. Required JS/CSS is auto-downloaded on first build to build/js/
|
|
233
|
+
and build/css/.
|
|
234
|
+
|
|
235
|
+
```ruby, ```python, etc. Syntax highlighting (highlight.js, BSD-3-Clause)
|
|
236
|
+
```mermaid Diagrams: flowcharts, sequence, class, etc.
|
|
237
|
+
(mermaid, MIT)
|
|
238
|
+
```math LaTeX math equations (KaTeX, MIT)
|
|
239
|
+
|
|
240
|
+
These are rendered visually in the output HTML — use them freely.
|
|
241
|
+
|
|
242
|
+
Mermaid example (flowchart):
|
|
243
|
+
|
|
244
|
+
```mermaid
|
|
245
|
+
graph TD
|
|
246
|
+
A[Start] --> B{Check}
|
|
247
|
+
B -->|Yes| C[OK]
|
|
248
|
+
B -->|No| D[Retry]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Mermaid example (sequence diagram):
|
|
252
|
+
|
|
253
|
+
```mermaid
|
|
254
|
+
sequenceDiagram
|
|
255
|
+
Client->>Server: Request
|
|
256
|
+
Server-->>Client: Response
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Math example (KaTeX, LaTeX syntax):
|
|
260
|
+
|
|
261
|
+
```math
|
|
262
|
+
E = mc^2
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
== Images ==
|
|
266
|
+
|
|
267
|
+
Place image files in the 'images/' directory next to book.yml.
|
|
268
|
+
Reference them from Markdown with relative paths:
|
|
269
|
+
|
|
270
|
+

|
|
271
|
+
|
|
272
|
+
ligarb rewrites image paths to 'images/filename' in the output and copies
|
|
273
|
+
all files from the images/ directory to the output.
|
|
274
|
+
|
|
275
|
+
== Build ==
|
|
276
|
+
|
|
277
|
+
Run from the directory containing book.yml:
|
|
278
|
+
|
|
279
|
+
ligarb build
|
|
280
|
+
|
|
281
|
+
Or specify a path to book.yml:
|
|
282
|
+
|
|
283
|
+
ligarb build path/to/book.yml
|
|
284
|
+
|
|
285
|
+
The generated index.html is a fully self-contained HTML file (CSS and JS
|
|
286
|
+
are embedded). Open it directly in a browser — no web server needed.
|
|
287
|
+
|
|
288
|
+
== Footnotes ==
|
|
289
|
+
|
|
290
|
+
Footnotes use kramdown syntax:
|
|
291
|
+
|
|
292
|
+
This is a sentence with a footnote[^1].
|
|
293
|
+
|
|
294
|
+
[^1]: This is the footnote content.
|
|
295
|
+
|
|
296
|
+
Footnote IDs are scoped per chapter to avoid collisions in the single-page
|
|
297
|
+
output.
|
|
298
|
+
|
|
299
|
+
== Custom CSS ==
|
|
300
|
+
|
|
301
|
+
Add a 'style' field to book.yml to inject custom CSS:
|
|
302
|
+
|
|
303
|
+
style: "custom.css"
|
|
304
|
+
|
|
305
|
+
The custom CSS is loaded after the default styles. You can override any
|
|
306
|
+
CSS custom property (e.g. colors, fonts, sidebar width) or add new rules.
|
|
307
|
+
|
|
308
|
+
Example custom.css:
|
|
309
|
+
|
|
310
|
+
:root {
|
|
311
|
+
--color-accent: #e63946;
|
|
312
|
+
--sidebar-width: 320px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
== Dark Mode ==
|
|
316
|
+
|
|
317
|
+
The generated HTML includes a dark mode toggle button (moon icon) in the
|
|
318
|
+
sidebar header. The user's preference is saved to localStorage and persists
|
|
319
|
+
across page reloads.
|
|
320
|
+
|
|
321
|
+
Custom CSS can override dark mode colors using the [data-theme="dark"]
|
|
322
|
+
selector.
|
|
323
|
+
|
|
324
|
+
== Edit on GitHub ==
|
|
325
|
+
|
|
326
|
+
Add a 'repository' field to book.yml:
|
|
327
|
+
|
|
328
|
+
repository: "https://github.com/user/repo"
|
|
329
|
+
|
|
330
|
+
Each chapter will show a "View on GitHub" link pointing to:
|
|
331
|
+
{repository}/blob/HEAD/{path-from-git-root}
|
|
332
|
+
|
|
333
|
+
== Previous/Next Navigation ==
|
|
334
|
+
|
|
335
|
+
Each chapter displays Previous and Next navigation links at the bottom.
|
|
336
|
+
These follow the flat chapter order (including across parts and appendix).
|
|
337
|
+
Part title pages do not show navigation.
|
|
338
|
+
SPEC
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Ligarb
|
|
6
|
+
class Config
|
|
7
|
+
REQUIRED_KEYS = %w[title chapters].freeze
|
|
8
|
+
|
|
9
|
+
# Represents a structural entry in the book
|
|
10
|
+
StructEntry = Struct.new(:type, :path, :children, keyword_init: true)
|
|
11
|
+
# type: :chapter, :part, or :appendix_group
|
|
12
|
+
# path: markdown file path (for :chapter and :part), nil for :appendix_group
|
|
13
|
+
# children: array of StructEntry (for :part and :appendix_group)
|
|
14
|
+
|
|
15
|
+
attr_reader :title, :author, :language, :output_dir, :base_dir,
|
|
16
|
+
:chapter_numbers, :structure, :style, :repository
|
|
17
|
+
|
|
18
|
+
def initialize(path)
|
|
19
|
+
@base_dir = File.dirname(File.expand_path(path))
|
|
20
|
+
data = YAML.safe_load_file(path)
|
|
21
|
+
|
|
22
|
+
validate!(data)
|
|
23
|
+
|
|
24
|
+
@title = data["title"]
|
|
25
|
+
@author = data.fetch("author", "")
|
|
26
|
+
@language = data.fetch("language", "en")
|
|
27
|
+
@output_dir = data.fetch("output_dir", "build")
|
|
28
|
+
@chapter_numbers = data.fetch("chapter_numbers", true)
|
|
29
|
+
@style = data.fetch("style", nil)
|
|
30
|
+
@repository = data.fetch("repository", nil)
|
|
31
|
+
@structure = parse_structure(data["chapters"])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def output_path
|
|
35
|
+
File.join(@base_dir, @output_dir)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def style_path
|
|
39
|
+
@style ? File.join(@base_dir, @style) : nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def appendix_label
|
|
43
|
+
@language == "ja" ? "付録" : "Appendix"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns a flat list of all chapter file paths (excluding part title pages)
|
|
47
|
+
def chapter_paths
|
|
48
|
+
collect_chapter_paths(@structure)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns all file paths including part title pages
|
|
52
|
+
def all_file_paths
|
|
53
|
+
collect_all_paths(@structure)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def parse_structure(entries)
|
|
59
|
+
entries.map do |entry|
|
|
60
|
+
case entry
|
|
61
|
+
when String
|
|
62
|
+
StructEntry.new(type: :chapter, path: File.join(@base_dir, entry))
|
|
63
|
+
when Hash
|
|
64
|
+
if entry.key?("part")
|
|
65
|
+
children = (entry["chapters"] || []).map do |ch|
|
|
66
|
+
StructEntry.new(type: :chapter, path: File.join(@base_dir, ch))
|
|
67
|
+
end
|
|
68
|
+
StructEntry.new(type: :part, path: File.join(@base_dir, entry["part"]), children: children)
|
|
69
|
+
elsif entry.key?("cover")
|
|
70
|
+
StructEntry.new(type: :cover, path: File.join(@base_dir, entry["cover"]))
|
|
71
|
+
elsif entry.key?("appendix")
|
|
72
|
+
children = entry["appendix"].map do |ch|
|
|
73
|
+
StructEntry.new(type: :chapter, path: File.join(@base_dir, ch))
|
|
74
|
+
end
|
|
75
|
+
StructEntry.new(type: :appendix_group, path: nil, children: children)
|
|
76
|
+
else
|
|
77
|
+
abort "Error: unknown entry type in chapters: #{entry.inspect}"
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
abort "Error: invalid entry in chapters: #{entry.inspect}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def collect_chapter_paths(entries)
|
|
86
|
+
entries.flat_map do |entry|
|
|
87
|
+
case entry.type
|
|
88
|
+
when :chapter, :cover
|
|
89
|
+
[entry.path]
|
|
90
|
+
when :part
|
|
91
|
+
[entry.path] + collect_chapter_paths(entry.children || [])
|
|
92
|
+
when :appendix_group
|
|
93
|
+
collect_chapter_paths(entry.children || [])
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def collect_all_paths(entries)
|
|
99
|
+
collect_chapter_paths(entries)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def validate!(data)
|
|
103
|
+
unless data.is_a?(Hash)
|
|
104
|
+
abort "Error: book.yml must be a YAML mapping"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
REQUIRED_KEYS.each do |key|
|
|
108
|
+
unless data.key?(key)
|
|
109
|
+
abort "Error: book.yml is missing required key '#{key}'"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
unless data["chapters"].is_a?(Array) && !data["chapters"].empty?
|
|
114
|
+
abort "Error: 'chapters' must be a non-empty array"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Ligarb
|
|
7
|
+
class Initializer
|
|
8
|
+
def initialize(directory = nil)
|
|
9
|
+
@directory = directory || "."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
target = File.expand_path(@directory)
|
|
14
|
+
book_yml = File.join(target, "book.yml")
|
|
15
|
+
|
|
16
|
+
if File.exist?(book_yml)
|
|
17
|
+
$stderr.puts "Error: book.yml already exists in #{target}"
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
FileUtils.mkdir_p(target)
|
|
22
|
+
FileUtils.mkdir_p(File.join(target, "images"))
|
|
23
|
+
|
|
24
|
+
title = dir_to_title(File.basename(File.expand_path(target)))
|
|
25
|
+
|
|
26
|
+
existing_md = collect_markdown_files(target)
|
|
27
|
+
if existing_md.any?
|
|
28
|
+
chapter_paths = existing_md
|
|
29
|
+
else
|
|
30
|
+
chapter_paths = ["01-introduction.md"]
|
|
31
|
+
File.write(File.join(target, "01-introduction.md"), generate_chapter)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
File.write(book_yml, generate_book_yml(title, chapter_paths))
|
|
35
|
+
File.write(File.join(target, "images", ".gitkeep"), "")
|
|
36
|
+
|
|
37
|
+
print_success(target, chapter_paths)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def dir_to_title(dirname)
|
|
43
|
+
dirname.gsub(/[-_]/, " ").gsub(/\b\w/, &:upcase)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def collect_markdown_files(target)
|
|
47
|
+
Dir.glob("*.md", base: target).sort
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def generate_book_yml(title, chapter_paths)
|
|
51
|
+
{
|
|
52
|
+
"title" => title,
|
|
53
|
+
"author" => "",
|
|
54
|
+
"language" => "en",
|
|
55
|
+
"output_dir" => "build",
|
|
56
|
+
"chapters" => chapter_paths,
|
|
57
|
+
}.to_yaml
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def generate_chapter
|
|
61
|
+
<<~MARKDOWN
|
|
62
|
+
# Introduction
|
|
63
|
+
|
|
64
|
+
Welcome to your new book.
|
|
65
|
+
MARKDOWN
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def print_success(target, chapter_paths)
|
|
69
|
+
rel = relative_path(target)
|
|
70
|
+
name = File.basename(File.expand_path(target))
|
|
71
|
+
|
|
72
|
+
puts "Created new book project in #{rel}"
|
|
73
|
+
puts
|
|
74
|
+
puts " #{name}/"
|
|
75
|
+
puts " ├── book.yml"
|
|
76
|
+
chapter_paths.each_with_index do |path, i|
|
|
77
|
+
prefix = i == chapter_paths.size - 1 && !File.exist?(File.join(target, "images", ".gitkeep")) ? "└──" : "├──"
|
|
78
|
+
puts " #{prefix} #{path}"
|
|
79
|
+
end
|
|
80
|
+
puts " └── images/"
|
|
81
|
+
puts
|
|
82
|
+
puts "Next steps:"
|
|
83
|
+
puts " cd #{rel}" if @directory && @directory != "."
|
|
84
|
+
puts " Edit book.yml to set your book title and author"
|
|
85
|
+
puts " Add Markdown files and list them in book.yml"
|
|
86
|
+
puts " Run 'ligarb build' to generate HTML"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def relative_path(target)
|
|
90
|
+
if @directory && @directory != "."
|
|
91
|
+
@directory.start_with?("/") ? @directory : "./#{@directory}"
|
|
92
|
+
else
|
|
93
|
+
"."
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Ligarb
|
|
6
|
+
class Template
|
|
7
|
+
TEMPLATE_DIR = File.expand_path("../../templates", __dir__)
|
|
8
|
+
ASSETS_DIR = File.expand_path("../../assets", __dir__)
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@template_path = File.join(TEMPLATE_DIR, "book.html.erb")
|
|
12
|
+
@css_path = File.join(ASSETS_DIR, "style.css")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render(config:, chapters:, structure:, assets:)
|
|
16
|
+
css = File.read(@css_path)
|
|
17
|
+
template = File.read(@template_path)
|
|
18
|
+
|
|
19
|
+
custom_css = if config.style_path && File.exist?(config.style_path)
|
|
20
|
+
File.read(config.style_path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
b = binding
|
|
24
|
+
b.local_variable_set(:title, config.title)
|
|
25
|
+
b.local_variable_set(:author, config.author)
|
|
26
|
+
b.local_variable_set(:language, config.language)
|
|
27
|
+
b.local_variable_set(:chapters, chapters)
|
|
28
|
+
b.local_variable_set(:structure, structure)
|
|
29
|
+
b.local_variable_set(:css, css)
|
|
30
|
+
b.local_variable_set(:custom_css, custom_css)
|
|
31
|
+
b.local_variable_set(:assets, assets)
|
|
32
|
+
b.local_variable_set(:repository, config.repository)
|
|
33
|
+
b.local_variable_set(:appendix_label, config.appendix_label)
|
|
34
|
+
|
|
35
|
+
ERB.new(template, trim_mode: "-").result(b)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|