@rip-lang/print 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.
- package/README.md +65 -0
- package/bin/rip-print +12 -0
- package/package.json +40 -0
- package/print.rip +365 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
+
|
|
3
|
+
# Rip Print - @rip-lang/print
|
|
4
|
+
|
|
5
|
+
> **Syntax-highlighted source code printer**
|
|
6
|
+
|
|
7
|
+
Highlights source code using highlight.js and serves the result in the browser for viewing and printing. Serves once and exits — no leftover files, no cleanup.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install
|
|
13
|
+
bun add -g @rip-lang/print
|
|
14
|
+
|
|
15
|
+
# Print all source files in a directory
|
|
16
|
+
rip-print src/
|
|
17
|
+
|
|
18
|
+
# Print specific files
|
|
19
|
+
rip-print src/compiler.js src/lexer.js
|
|
20
|
+
|
|
21
|
+
# Dark theme
|
|
22
|
+
rip-print -d src/
|
|
23
|
+
|
|
24
|
+
# Strip leading comment blocks
|
|
25
|
+
rip-print -b src/
|
|
26
|
+
|
|
27
|
+
# Exclude extensions
|
|
28
|
+
rip-print -x lock,map src/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **highlight.js** — 190+ languages, beautiful GitHub-style themes
|
|
34
|
+
- **Auto-detect languages** — 40+ languages supported
|
|
35
|
+
- **Light and dark themes** — GitHub Light (default) and GitHub Dark
|
|
36
|
+
- **Print-optimized CSS** — page breaks between files, clean headers
|
|
37
|
+
- **Table of contents** — auto-generated for multi-file output
|
|
38
|
+
- **Sticky headers** — file names stay visible while scrolling
|
|
39
|
+
- **Serve once** — opens browser, serves the page, exits automatically
|
|
40
|
+
- **No leftover files** — everything is in-memory
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Flag | Description |
|
|
45
|
+
|------|-------------|
|
|
46
|
+
| `-b`, `--bypass` | Strip leading comment blocks from files |
|
|
47
|
+
| `-d`, `--dark` | Use dark theme (default: light) |
|
|
48
|
+
| `-h`, `--help` | Show help |
|
|
49
|
+
| `-x <exts>` | Comma list of extensions to exclude |
|
|
50
|
+
|
|
51
|
+
## How It Works
|
|
52
|
+
|
|
53
|
+
1. Walks the specified paths, discovers source files
|
|
54
|
+
2. Highlights each file using Shiki with the detected language
|
|
55
|
+
3. Builds a single HTML page with table of contents
|
|
56
|
+
4. Starts a Bun server on a random port
|
|
57
|
+
5. Opens the browser
|
|
58
|
+
6. Exits after serving the page
|
|
59
|
+
|
|
60
|
+
Print from the browser with **Cmd+P** (Mac) or **Ctrl+P** (Windows/Linux).
|
|
61
|
+
|
|
62
|
+
## Links
|
|
63
|
+
|
|
64
|
+
- [Rip Language](https://github.com/shreeve/rip-lang)
|
|
65
|
+
- [highlight.js](https://highlightjs.org)
|
package/bin/rip-print
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
|
|
6
|
+
const printRip = join(dirname(new URL(import.meta.url).pathname), '..', 'print.rip');
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
execFileSync('rip', [printRip, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
10
|
+
} catch (e) {
|
|
11
|
+
process.exit(e.status || 1);
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rip-lang/print",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Syntax-highlighted source code printer — Shiki-powered, serves once, auto-opens browser",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "print.rip",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rip-print": "./bin/rip-print"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"print",
|
|
12
|
+
"syntax",
|
|
13
|
+
"highlighting",
|
|
14
|
+
"highlight.js",
|
|
15
|
+
"source",
|
|
16
|
+
"code",
|
|
17
|
+
"bun",
|
|
18
|
+
"rip"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/shreeve/rip-lang.git",
|
|
23
|
+
"directory": "packages/print"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/shreeve/rip-lang/tree/main/packages/print#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/shreeve/rip-lang/issues"
|
|
28
|
+
},
|
|
29
|
+
"author": "Steve Shreeve <steve.shreeve@gmail.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"rip-lang": "^3.4.4",
|
|
33
|
+
"highlight.js": "^11.11.1"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"print.rip",
|
|
37
|
+
"bin/rip-print",
|
|
38
|
+
"README.md"
|
|
39
|
+
]
|
|
40
|
+
}
|
package/print.rip
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# rip-print — Syntax-highlighted source code printer
|
|
3
|
+
#
|
|
4
|
+
# Author: Steve Shreeve (steve.shreeve@gmail.com)
|
|
5
|
+
# Date: Feb 9, 2026
|
|
6
|
+
#
|
|
7
|
+
# Highlights source code using Shiki (VS Code's syntax engine) and serves
|
|
8
|
+
# the result in the browser for viewing and printing. Serves once and exits.
|
|
9
|
+
# ============================================================================
|
|
10
|
+
|
|
11
|
+
import hljs from 'highlight.js'
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs'
|
|
13
|
+
import { basename, extname, join, relative } from 'path'
|
|
14
|
+
import { execSync } from 'child_process'
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# CLI argument parsing
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
args = process.argv.slice(2)
|
|
21
|
+
|
|
22
|
+
if args.includes('-h') or args.includes('--help')
|
|
23
|
+
console.log """
|
|
24
|
+
usage: rip-print [options] <paths ...>
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
-b, --bypass Strip leading comment blocks from files
|
|
28
|
+
-d, --dark Use dark theme (default: light)
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
-x <exts> Comma list of extensions to exclude
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
rip-print src/ # Print all source files in src/
|
|
34
|
+
rip-print *.rip # Print all .rip files
|
|
35
|
+
rip-print -d src/ # Dark theme
|
|
36
|
+
rip-print -b src/ # Strip top comments
|
|
37
|
+
rip-print -x lock,map src/ # Exclude .lock and .map files
|
|
38
|
+
"""
|
|
39
|
+
process.exit(0)
|
|
40
|
+
|
|
41
|
+
dark = args.includes('-d') or args.includes('--dark')
|
|
42
|
+
bypass = args.includes('-b') or args.includes('--bypass')
|
|
43
|
+
|
|
44
|
+
# Extract -x excludes
|
|
45
|
+
excludeExts = new Set()
|
|
46
|
+
for arg, i in args
|
|
47
|
+
if arg is '-x' and args[i + 1]
|
|
48
|
+
for ext in args[i + 1].split(',')
|
|
49
|
+
excludeExts.add ext.toLowerCase().replace(/^\./, '')
|
|
50
|
+
|
|
51
|
+
# Filter out flags, get paths
|
|
52
|
+
xIdx = args.indexOf('-x')
|
|
53
|
+
paths = args.filter (a, i) -> not a.startsWith('-') and (xIdx < 0 or i isnt xIdx + 1)
|
|
54
|
+
paths = ['.'] if paths.length is 0
|
|
55
|
+
|
|
56
|
+
# Default exclusions (binary, generated, etc.)
|
|
57
|
+
defaultExclude = new Set [
|
|
58
|
+
'css', 'gif', 'ico', 'jpg', 'jpeg', 'png', 'svg', 'pdf', 'webp',
|
|
59
|
+
'otf', 'ttf', 'eot', 'woff', 'woff2',
|
|
60
|
+
'o', 'a', 'dylib', 'so', 'dll',
|
|
61
|
+
'gem', 'gz', 'zip', 'tar', 'br',
|
|
62
|
+
'lock', 'db', 'sqlite3', 'sqlite',
|
|
63
|
+
'map', 'min.js', 'min.css',
|
|
64
|
+
'vsix', 'DS_Store'
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for ext in excludeExts
|
|
68
|
+
defaultExclude.add ext
|
|
69
|
+
|
|
70
|
+
# ============================================================================
|
|
71
|
+
# File discovery
|
|
72
|
+
# ============================================================================
|
|
73
|
+
|
|
74
|
+
skipDirs = new Set ['.git', 'node_modules', '.rip-cache', '.zig-cache', 'zig-out', 'dist', 'misc']
|
|
75
|
+
|
|
76
|
+
def walkDir(dir, base = dir)
|
|
77
|
+
files = []
|
|
78
|
+
try
|
|
79
|
+
entries = readdirSync dir
|
|
80
|
+
catch
|
|
81
|
+
return files
|
|
82
|
+
for entry in entries
|
|
83
|
+
full = join dir, entry
|
|
84
|
+
try
|
|
85
|
+
stat = statSync full
|
|
86
|
+
catch
|
|
87
|
+
continue
|
|
88
|
+
if stat.isDirectory()
|
|
89
|
+
continue if entry.startsWith('.') or skipDirs.has(entry)
|
|
90
|
+
files = files.concat walkDir(full, base)
|
|
91
|
+
else if stat.isFile()
|
|
92
|
+
ext = entry.split('.').pop()?.toLowerCase() or ''
|
|
93
|
+
continue if defaultExclude.has(ext)
|
|
94
|
+
continue if entry.startsWith('.')
|
|
95
|
+
files.push relative(base, full) or full
|
|
96
|
+
files.sort()
|
|
97
|
+
|
|
98
|
+
allFiles = []
|
|
99
|
+
for p in paths
|
|
100
|
+
if not existsSync(p)
|
|
101
|
+
console.error "Not found: #{p}"
|
|
102
|
+
continue
|
|
103
|
+
stat = statSync p
|
|
104
|
+
if stat.isDirectory()
|
|
105
|
+
allFiles = allFiles.concat walkDir(p, p).map (f) -> join(p, f)
|
|
106
|
+
else if stat.isFile()
|
|
107
|
+
allFiles.push p
|
|
108
|
+
|
|
109
|
+
if allFiles.length is 0
|
|
110
|
+
console.error "No files found"
|
|
111
|
+
process.exit(1)
|
|
112
|
+
|
|
113
|
+
console.log "#{allFiles.length} file#{if allFiles.length > 1 then 's' else ''}"
|
|
114
|
+
|
|
115
|
+
# ============================================================================
|
|
116
|
+
# Language detection
|
|
117
|
+
# ============================================================================
|
|
118
|
+
|
|
119
|
+
extToLang =
|
|
120
|
+
rip: 'coffeescript'
|
|
121
|
+
coffee: 'coffeescript'
|
|
122
|
+
js: 'javascript'
|
|
123
|
+
mjs: 'javascript'
|
|
124
|
+
cjs: 'javascript'
|
|
125
|
+
ts: 'typescript'
|
|
126
|
+
mts: 'typescript'
|
|
127
|
+
cts: 'typescript'
|
|
128
|
+
jsx: 'jsx'
|
|
129
|
+
tsx: 'tsx'
|
|
130
|
+
rb: 'ruby'
|
|
131
|
+
py: 'python'
|
|
132
|
+
rs: 'rust'
|
|
133
|
+
go: 'go'
|
|
134
|
+
sh: 'bash'
|
|
135
|
+
bash: 'bash'
|
|
136
|
+
zsh: 'bash'
|
|
137
|
+
fish: 'fish'
|
|
138
|
+
yml: 'yaml'
|
|
139
|
+
yaml: 'yaml'
|
|
140
|
+
json: 'json'
|
|
141
|
+
jsonc: 'jsonc'
|
|
142
|
+
md: 'markdown'
|
|
143
|
+
html: 'html'
|
|
144
|
+
htm: 'html'
|
|
145
|
+
xml: 'xml'
|
|
146
|
+
css: 'css'
|
|
147
|
+
scss: 'scss'
|
|
148
|
+
sass: 'sass'
|
|
149
|
+
less: 'less'
|
|
150
|
+
sql: 'sql'
|
|
151
|
+
c: 'c'
|
|
152
|
+
h: 'c'
|
|
153
|
+
cpp: 'cpp'
|
|
154
|
+
hpp: 'cpp'
|
|
155
|
+
zig: 'zig'
|
|
156
|
+
toml: 'toml'
|
|
157
|
+
ini: 'ini'
|
|
158
|
+
dockerfile: 'dockerfile'
|
|
159
|
+
makefile: 'makefile'
|
|
160
|
+
|
|
161
|
+
def getLang(file)
|
|
162
|
+
base = basename(file).toLowerCase()
|
|
163
|
+
return 'makefile' if base is 'makefile' or base is 'gnumakefile'
|
|
164
|
+
return 'dockerfile' if base is 'dockerfile'
|
|
165
|
+
return 'yaml' if base is '.prettierrc' or base is '.eslintrc'
|
|
166
|
+
return 'json' if base is '.babelrc'
|
|
167
|
+
return 'bash' if base is '.bashrc' or base is '.zshrc' or base is '.profile'
|
|
168
|
+
return 'toml' if base is 'bunfig.toml'
|
|
169
|
+
|
|
170
|
+
ext = extname(file).slice(1).toLowerCase()
|
|
171
|
+
lang = extToLang[ext]
|
|
172
|
+
return lang if lang and hljs.getLanguage(lang)
|
|
173
|
+
return 'plaintext'
|
|
174
|
+
|
|
175
|
+
# ============================================================================
|
|
176
|
+
# Highlight and build HTML
|
|
177
|
+
# ============================================================================
|
|
178
|
+
|
|
179
|
+
def stripTopComments(code)
|
|
180
|
+
lines = code.split('\n')
|
|
181
|
+
i = 0
|
|
182
|
+
i++ while i < lines.length and (lines[i].startsWith('#') or lines[i].trim() is '')
|
|
183
|
+
return code if i is 0
|
|
184
|
+
lines.slice(i).join('\n')
|
|
185
|
+
|
|
186
|
+
def escapeHtml(str)
|
|
187
|
+
str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
188
|
+
|
|
189
|
+
def highlightCode(code, lang)
|
|
190
|
+
highlighted = null
|
|
191
|
+
try
|
|
192
|
+
if lang isnt 'plaintext'
|
|
193
|
+
result = hljs.highlight code, { language: lang }
|
|
194
|
+
highlighted = result.value
|
|
195
|
+
catch
|
|
196
|
+
# fall through
|
|
197
|
+
highlighted ?= escapeHtml code
|
|
198
|
+
|
|
199
|
+
# Add line numbers
|
|
200
|
+
lines = highlighted.split('\n')
|
|
201
|
+
width = String(lines.length).length
|
|
202
|
+
numbered = lines.map (line, i) ->
|
|
203
|
+
num = String(i + 1).padStart(width)
|
|
204
|
+
"<span class=\"line-num\">#{num}</span> #{line}"
|
|
205
|
+
numbered.join('\n')
|
|
206
|
+
|
|
207
|
+
sections = []
|
|
208
|
+
for file in allFiles
|
|
209
|
+
try
|
|
210
|
+
code = readFileSync file, 'utf-8'
|
|
211
|
+
catch
|
|
212
|
+
console.error " Skipping: #{file} (unreadable)"
|
|
213
|
+
continue
|
|
214
|
+
code = code.replace /\t/g, ' '
|
|
215
|
+
code = code.replace /\r\n?/g, '\n'
|
|
216
|
+
code = stripTopComments(code) if bypass
|
|
217
|
+
lang = getLang file
|
|
218
|
+
lineCount = code.split('\n').length
|
|
219
|
+
console.log " #{file} (#{lineCount} lines) [#{lang}]"
|
|
220
|
+
highlighted = highlightCode code, lang
|
|
221
|
+
html = "<pre><code class=\"hljs\">#{highlighted}</code></pre>"
|
|
222
|
+
sections.push { file, lineCount, lang, html }
|
|
223
|
+
|
|
224
|
+
# ============================================================================
|
|
225
|
+
# HTML template
|
|
226
|
+
# ============================================================================
|
|
227
|
+
|
|
228
|
+
bgColor = if dark then '#0d1117' else '#ffffff'
|
|
229
|
+
textColor = if dark then '#e6edf3' else '#1f2328'
|
|
230
|
+
headerBg = if dark then '#161b22' else '#f6f8fa'
|
|
231
|
+
borderColor = if dark then '#30363d' else '#d0d7de'
|
|
232
|
+
|
|
233
|
+
toc = ''
|
|
234
|
+
if sections.length > 1
|
|
235
|
+
toc = """
|
|
236
|
+
<div class="toc">
|
|
237
|
+
<h2>Files (#{sections.length})</h2>
|
|
238
|
+
<ol>
|
|
239
|
+
"""
|
|
240
|
+
for section in sections
|
|
241
|
+
toc += """
|
|
242
|
+
<li><a href="##{section.file}">#{section.file}</a> <span class="meta">(#{section.lineCount} lines)</span></li>
|
|
243
|
+
"""
|
|
244
|
+
toc += """
|
|
245
|
+
</ol>
|
|
246
|
+
</div>
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
fileSections = ''
|
|
250
|
+
count = sections.length
|
|
251
|
+
for section, i in sections
|
|
252
|
+
prev = sections[(i - 1 + count) % count]
|
|
253
|
+
next = sections[(i + 1) % count]
|
|
254
|
+
nav = ""
|
|
255
|
+
nav += "<a href=\"##{prev.file}\">prev</a> " if count > 1
|
|
256
|
+
nav += "<a href=\"##{next.file}\">next</a> " if count > 1
|
|
257
|
+
nav += "<a href=\"#top\">↑ top</a>"
|
|
258
|
+
fileSections += """
|
|
259
|
+
<div class="file-section">
|
|
260
|
+
<div class="file-header" id="#{section.file}">
|
|
261
|
+
<span>#{section.file} <span class="meta">(#{section.lineCount} lines) [#{section.lang}]</span></span>
|
|
262
|
+
<span class="nav">#{nav}</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="code-container">
|
|
265
|
+
#{section.html}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
# Read highlight.js CSS theme
|
|
272
|
+
hljsTheme = if dark then 'github-dark' else 'github'
|
|
273
|
+
hljsDir = join import.meta.dir, 'node_modules', 'highlight.js', 'styles'
|
|
274
|
+
hljsCss = readFileSync join(hljsDir, "#{hljsTheme}.css"), 'utf-8'
|
|
275
|
+
|
|
276
|
+
html = """
|
|
277
|
+
<!DOCTYPE html>
|
|
278
|
+
<html lang="en">
|
|
279
|
+
<head>
|
|
280
|
+
<meta charset="utf-8">
|
|
281
|
+
<title>rip-print</title>
|
|
282
|
+
<link rel="icon" href="data:,">
|
|
283
|
+
<style>#{hljsCss}</style>
|
|
284
|
+
<style>
|
|
285
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
286
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #{bgColor}; color: #{textColor}; }
|
|
287
|
+
|
|
288
|
+
.toc { padding: 20px 30px; border-bottom: 1px solid #{borderColor}; }
|
|
289
|
+
.toc h2 { font-size: 18px; margin-bottom: 10px; }
|
|
290
|
+
.toc ol { padding-left: 24px; columns: 2; column-gap: 40px; }
|
|
291
|
+
.toc li { font-size: 13px; line-height: 1.8; }
|
|
292
|
+
.toc a { color: #{textColor}; text-decoration: none; }
|
|
293
|
+
.toc a:hover { text-decoration: underline; }
|
|
294
|
+
.meta { color: #888; font-size: 12px; }
|
|
295
|
+
|
|
296
|
+
.file-section { margin-bottom: 0; }
|
|
297
|
+
.file-header {
|
|
298
|
+
background: #{headerBg}; padding: 8px 16px; font-size: 13px; font-weight: 600;
|
|
299
|
+
border-top: 1px solid #{borderColor}; border-bottom: 1px solid #{borderColor};
|
|
300
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
301
|
+
}
|
|
302
|
+
.nav { font-weight: normal; font-size: 12px; }
|
|
303
|
+
.nav a { color: #888; text-decoration: none; margin-left: 12px; }
|
|
304
|
+
.nav a:hover { color: #{textColor}; }
|
|
305
|
+
|
|
306
|
+
.code-container { overflow-x: auto; }
|
|
307
|
+
.code-container pre { margin: 0; border-radius: 0; }
|
|
308
|
+
.code-container code { font-size: 13px; line-height: 1.5; padding: 16px !important; display: block; }
|
|
309
|
+
.line-num { color: #aaa; user-select: none; display: inline-block; min-width: 2em; text-align: right; }
|
|
310
|
+
|
|
311
|
+
@media print {
|
|
312
|
+
.toc { page-break-after: always; }
|
|
313
|
+
.file-header { background: #f0f0f0 !important; color: #000 !important; }
|
|
314
|
+
.nav { display: none; }
|
|
315
|
+
.file-section { page-break-before: always; }
|
|
316
|
+
.file-section:first-of-type { page-break-before: avoid; }
|
|
317
|
+
body { background: white !important; color: black !important; }
|
|
318
|
+
code { font-size: 11px !important; }
|
|
319
|
+
}
|
|
320
|
+
</style>
|
|
321
|
+
</head>
|
|
322
|
+
<body>
|
|
323
|
+
<a name="top"></a>
|
|
324
|
+
#{toc}
|
|
325
|
+
#{fileSections}
|
|
326
|
+
</body>
|
|
327
|
+
</html>
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
# ============================================================================
|
|
331
|
+
# Serve once and open browser
|
|
332
|
+
# ============================================================================
|
|
333
|
+
|
|
334
|
+
served = false
|
|
335
|
+
|
|
336
|
+
server = Bun.serve
|
|
337
|
+
port: 0
|
|
338
|
+
fetch: (req) ->
|
|
339
|
+
served = true
|
|
340
|
+
new Response html, headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
341
|
+
|
|
342
|
+
console.log "\nOpening browser..."
|
|
343
|
+
|
|
344
|
+
# Open browser
|
|
345
|
+
try
|
|
346
|
+
if process.platform is 'darwin'
|
|
347
|
+
execSync "open 'http://localhost:#{server.port}/'"
|
|
348
|
+
else if process.platform is 'linux'
|
|
349
|
+
execSync "xdg-open 'http://localhost:#{server.port}/'"
|
|
350
|
+
else
|
|
351
|
+
execSync "start 'http://localhost:#{server.port}/'"
|
|
352
|
+
catch
|
|
353
|
+
console.log "Visit: http://localhost:#{server.port}/"
|
|
354
|
+
|
|
355
|
+
# Exit after serving
|
|
356
|
+
setTimeout ->
|
|
357
|
+
if served
|
|
358
|
+
server.stop()
|
|
359
|
+
process.exit(0)
|
|
360
|
+
else
|
|
361
|
+
setTimeout ->
|
|
362
|
+
server.stop()
|
|
363
|
+
process.exit(0)
|
|
364
|
+
, 5000
|
|
365
|
+
, 3000
|