Skip to content

Commit a6605c7

Browse files
committed
Merge remote-tracking branch 'upstream/master' into link-to-sections
2 parents 6dbbef9 + 67b8eb3 commit a6605c7

File tree

9 files changed

+131
-81
lines changed

9 files changed

+131
-81
lines changed

README.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,29 +83,37 @@ For more in-depth examples, see my article:
8383
## Comparison with Literate
8484

8585
srcweave is inspired by Zach Yedidia's [Literate](https://zyedidia.github.io/literate/).
86-
I like his system and have used it for several years, but I have developed my own preferences.
86+
I like his program, have used it for several years, and am grateful for his contribution.
87+
However, I have since developed my own preferences and ideas for improvement:
8788

8889
- Written in Common Lisp instead of D.
89-
This makes it more stable and portable (BSD, etc).
90-
- More modular UNIX design. srcweave completely delegates HTML formatting and libraries
91-
to a shell script. It provides the same features by default, but is much easier to customize.
92-
- Simpler handling of multifile systems. The "books" feature is a little cumbersome.
93-
- GPL license instead of MIT. It's intended to be used as a program, so
94-
this should not restrict commercial use while maximizing user freedom (contact me if you have an issue).
90+
This makes it more stable and portable (works on BSD, etc).
91+
- More modular UNIX design.
92+
srcweave completely delegates HTML formatting and libraries to a shell script.
93+
It provides high quality document output right out of the box, but is much easier to customize.
94+
- Simpler handling of multiple files.
95+
The "books" feature in Literate is a little cumbersome.
96+
In srcweave you can have multiple chapters in a single file,
97+
or divide them each into their own file. There is no distinction.
98+
- GPL license instead of MIT.
99+
srcweave is a program for end users.
100+
GPL should not restrict commercial use of the program while maximizing user freedom (contact me if you have an issue).
95101

96102
The [vim plugin](https://github.com/zyedidia/literate.vim) should be compatible.
97103

98104
**Known incompatibilities:**
99105

100-
You can move files from Literate with only minor changes.
101-
Here are the major differences:
106+
You can migrate files from Literate with only minor changes.
107+
Here are the important differences:
102108

109+
- In literate, any block named with a file extension becomes a file.
110+
In srcweave, all file blocks must be prefixed with a path. For example `/out.txt` instead of `out.txt`.
103111
- formatting commands like `@add_css`, `@colorscheme` are ignored.
104-
Use the shared `.css` or a custom format script.
112+
Use the shared `.css` created by `srcweave-format-init` or a custom format script.
105113
- no support for books `@book`. Just pass multiple `.lit` files to the tool in the order you want.
106-
- no support for `@change` commands. Use shell scripts in your build process.
107-
- `@title` only sets the page title.
108-
- Prefer markdown `# heading 1` and `## heading 2` instead of `@s`, etc.
114+
- no support for `@change` commands. Use shell scripts in your build process, instead.
115+
- `@title` only sets the page title, it does not create a heading.
116+
- Prefer markdown headings `# heading 1` and `## heading 2` instead of `@s`, etc.
109117

110118
## Acknowledgments
111119

command-line.lisp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
(when (and (not (getf options :weave))
7373
(not (getf options :tangle)))
74-
(format *error-output* "warning: no tangle or weave specified."))
74+
(warn "no tangle or weave command specified."))
7575

7676
(setf *markdown-command*
7777
(getf options :md-compiler *markdown-command*))
@@ -99,7 +99,7 @@
9999
(format t "DONE~%")))))
100100

101101
(defun unknown-option (condition)
102-
(format *error-output* "warning: unknown option ~s!~%" (opts:option condition))
102+
(warn "unknown option: ~a" (opts:option condition))
103103
(invoke-restart 'opts:skip-option))
104104

105105
(defun toplevel ()

tangle.lisp

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,23 @@
3232
:input (make-string-input-stream (get-output-stream-string s)))
3333
(if (= status 0)
3434
output
35-
(error 'user-error :format-control "dependency resolution failed")))))
35+
(error 'user-error
36+
:format-control "dependency resolution failed")))))
3637

3738
(defun resolve-includes (block-table sorted-id-list)
38-
"perform inclusion on all blocks in the table"
39+
"perform inclusion on all blocks in the table.
40+
sorted-id-list should be topolgically sorted"
41+
3942
(dolist (id sorted-id-list)
4043
(multiple-value-bind (block present)
4144
(gethash id block-table)
42-
(assert present)
43-
(setf (gethash id block-table)
44-
(textblock-include block block-table)))))
45-
45+
46+
; If a referenced block id doesn't exist in the table.
47+
; just ignore it for now. The inclusion code will warn
48+
; if it's a problem.
49+
(when present
50+
(setf (gethash id block-table)
51+
(textblock-include block block-table))))))
4652

4753
(defun tangle-output-block (block &optional (stream t))
4854
(let ((first t))
@@ -56,9 +62,9 @@
5662
(error "block has not been fully resolved ~s" expr)))))
5763

5864
(when (and *trailing-newline*
59-
(not (alexandria-2:emptyp (textblock-lines block))))
60-
(when (not (null (alexandria-2:last-elt (textblock-lines block))))
61-
(write-line "" stream))))
65+
(not (alexandria-2:emptyp (textblock-lines block)))
66+
(not (null (alexandria-2:last-elt (textblock-lines block)))))
67+
(write-line "" stream)))
6268

6369
(defun tangle-build-pathname (title base)
6470
(assert (uiop:string-prefix-p "/" title))
@@ -78,14 +84,12 @@
7884
(resolve-includes
7985
block-table
8086
(topological-sort-dependencies dependencies))
81-
8287

8388
(loop for def in root-defs do
8489
(let* ((title (textblockdef-title def))
8590
(file-path (tangle-build-pathname title output-dir))
8691
(block (gethash (textblock-slug title) block-table)))
8792

88-
8993
(assert block)
9094
(if (or ignore-dates
9195
(>= (textblock-modify-date block)
@@ -103,14 +107,15 @@
103107

104108
; Check for warning and errors
105109
(when (null root-defs)
106-
(format *error-output* "warning: no file blocks to tangle~%"))
110+
(warn "no file blocks to tangle"))
107111

108112
(let ((reference-counts (dependency-count-references dependencies)))
109113
(loop for def in root-defs do
110114
(incf (gethash (textblock-slug (textblockdef-title def)) reference-counts 0)))
115+
111116
(maphash (lambda (k _)
112117
(when (= (gethash k reference-counts 0) 0)
113-
(format *error-output* "warning: block ~s was never used.~%" k)))
118+
(warn "block ~s was never used." k)))
114119
block-table)
115120
)
116121
))

tests/expected-errors/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ then
1515
fi
1616

1717
echo "EXPECT warning"
18-
../srcweave-test --force-output --tangle ./ unused-block.lit
18+
../srcweave-test --force-output --weave ./ --tangle ./ unused-block.lit

tests/expected-errors/circular.lit

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Circular dependencies
22

3+
A circular dependency between blocks should be an error
4+
as there is no well defined way to handle this situation.
5+
6+
Here is an example where `a -> b -> c -> a`.
7+
38
--- a
49
Duck
510
@{b}

tests/expected-errors/missing-block-name.lit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Block with no name
22

3-
A block with no name should fail.
3+
A block with no name should be an error.
44

55
---
66
printf("Hello, World!\n");
Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
# Unused block
22

3-
4-
One block is unused:
3+
An unused block should display a warning, but no errors.
54

65
--- unused block
7-
Hello
6+
Hello
87
---
98

10-
This counts as it's own usage.
9+
Including an unknown block should also be a warning, whether it's
10+
in prose, like @{undefined block} or in the code.
11+
12+
--- including undefined
13+
Hello @{undefined block}
14+
---
15+
16+
File blocks (prefixed with `/`) are terminal.
17+
These blocks are never "unused" as they produce a file, so no warning should be produced.
1118

1219
--- /used block.txt
13-
Hello
20+
@{including undefined}
1421
---
22+

textblock.lisp

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
; Design: DSL for manpulating textblocks.
1818
; This means well defined operations that are ideally closed.
19-
2019
; For example:
2120
; - append two blocks and get a block
2221
; - include references in a block to get a new block
@@ -36,22 +35,23 @@
3635
list))
3736

3837
(defun textblock-slug (title) (string-to-slug title))
39-
40-
(defun leading-whitespace (code-line)
41-
(if (stringp (car code-line))
42-
(multiple-value-bind (match groups)
43-
(ppcre:scan-to-strings "(\\s*)" (car code-line))
44-
(assert match)
45-
(aref groups 0)) ""))
4638

4739
(defun textblock-concat (a b)
48-
"concatenate the text of two blocks"
40+
"concatenate the text of two blocks. The modification date is the most recent of the two dates."
4941
(make-textblock
5042
:modify-date (max (textblock-modify-date a)
5143
(textblock-modify-date b))
5244
:lines (concatenate 'vector
5345
(textblock-lines a)
5446
(textblock-lines b))))
47+
48+
49+
(defun leading-whitespace (code-line)
50+
(if (stringp (car code-line))
51+
(multiple-value-bind (match groups)
52+
(ppcre:scan-to-strings "(\\s*)" (car code-line))
53+
(assert match)
54+
(aref groups 0)) ""))
5555

5656
(defun include-lines (block prefix whitespace output)
5757
(let* ((src (textblock-lines block))
@@ -65,46 +65,58 @@
6565
(cons whitespace (aref src (- n 1)))))))
6666

6767
(defun include-helper (line output block-table)
68+
; Handling white space properly in block inclusion is tricky.
69+
; Suppose you have something like this:
70+
; int main() {
71+
; @{body}
72+
; }
73+
; We want all the lines from body to be indented at same level.
74+
; So we need to record the whitespace prefix and prepend that to each line.
75+
; See the tests for additional examples.
76+
77+
; What should the following do if body has multiple lines?
78+
; int main() { @{body} }
79+
6880
(let ((prefix '()))
6981
(loop for expr in line do
7082
(cond ((stringp expr) (setf prefix (append prefix (list expr))))
7183
((commandp expr)
7284
(case (first expr)
7385
(:INCLUDE
74-
(multiple-value-bind (other present)
86+
(multiple-value-bind (other-block present)
7587
(gethash (textblock-slug (second expr)) block-table)
7688
(if present
7789
(setf prefix (include-lines
78-
other
90+
other-block
7991
prefix
8092
(leading-whitespace line) output))
81-
(error 'user-error
82-
:format-control "cannot find block to include: ~s"
83-
:format-arguments (list (second expr))))))
93+
(warn "attempting to include unknown block: ~s" (second expr)))))
8494
(otherwise (error "unknown code command ~S" (first expr)))))
8595
(t (error "unknown structure"))))
8696
(vector-push-extend prefix output)))
8797

8898
(defun textblock-include (root block-table)
8999
"form a new block by including the contents of all immediate dependencies (nonrecursive)."
90-
(let* ((titles (textblock-referenced-titles root))
91-
(dependencies (mapcar (lambda (title)
92-
(gethash (textblock-slug title) block-table)) titles))
93-
(output (make-array 16 :fill-pointer 0 :adjustable t)))
94-
100+
(let ((output (make-array 16 :fill-pointer 0 :adjustable t)))
95101
(loop for line across (textblock-lines root) do
96102
(include-helper line output block-table))
97103

98-
(make-textblock :modify-date (reduce #'max
99-
(mapcar #'textblock-modify-date dependencies)
100-
:initial-value (textblock-modify-date root))
101-
:lines output)))
104+
(let* ((titles (textblock-referenced-titles root))
105+
(dependencies (remove-if #'null (mapcar (lambda (title)
106+
(gethash (textblock-slug title) block-table))
107+
titles))))
108+
(make-textblock :modify-date (reduce #'max
109+
(mapcar #'textblock-modify-date dependencies)
110+
:initial-value (textblock-modify-date root))
111+
:lines output))))
102112

103113

104114
(defun textblock-find-title (block)
115+
"Find the first title command in the block."
105116
(find-map (lambda (line)
106117
(find-map (lambda (expr)
107-
(when (and (commandp expr) (eq (first expr) :TITLE))
118+
(when (and (commandp expr)
119+
(eq (first expr) :TITLE))
108120
(second expr)))
109121
line))
110122
(textblock-lines block)))
@@ -168,7 +180,7 @@
168180
table))
169181

170182
(defun textblockdefs-apply (defs)
171-
"construct a table of blocks by evaluating combination operations"
183+
"construct a table of blocks by evaluating the block operations (include, concat, etc)."
172184
(let ((block-table (make-hash-table :test #'equal)))
173185
(loop for def in defs do
174186
(let ((block (textblockdef-block def))

0 commit comments

Comments
 (0)