diff options
author | EuAndreh <eu@euandre.org> | 2022-05-17 09:41:27 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2022-05-17 09:41:27 -0300 |
commit | fc796833209867319296393c6fa30ae879070336 (patch) | |
tree | 9d94348e1f0bfbc54952317614e103bd4d06e793 | |
parent | Initial empty commit (diff) | |
download | guile-heredoc-fc796833209867319296393c6fa30ae879070336.tar.gz guile-heredoc-fc796833209867319296393c6fa30ae879070336.tar.xz |
Add first workikng version, with proper Makefile with "install" target
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 60 | ||||
-rw-r--r-- | src/heredoc.scm | 97 | ||||
-rw-r--r-- | tests/heredoc.scm | 337 |
4 files changed, 498 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2311b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/heredoc.log +/src/*.go +/vendor/ +/FIXME diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dfeabe4 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +.POSIX: +DATE = 1970 +VERSION = 0.1.0 +NAME = guile-heredoc +MAILING_LIST = public-inbox +TRANSLATIONS = pt fr eo es +CONTRIBLANGS = +PREFIX = /usr/local +LIBDIR = $(PREFIX)/lib +SHAREDIR = $(PREFIX)/share +GUILE = guile +GUILD = guild +MODDIR = $(SHAREDIR)/guile/site/$(GUILE_EFFECTIVE_VERSION) +OBJDIR = $(LIBDIR)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache + + +.SUFFIXES: +.SUFFIXES: .scm .go + +.scm.go: + $(GUILD) compile -L src/ $(GUILE_FLAGS)-o $@ $< + + +sources = src/heredoc.scm +objects = $(sources:.scm=.go) + + +all: $(objects) + +GUILE_EFFECTIVE_VERSION = `$(GUILE) -c '(display (effective-version))'` +install: all + mkdir -p \ + "$(DESTDIR)$(MODDIR)" \ + "$(DESTDIR)$(OBJDIR)" + for s in $(sources); do \ + cp $$s "$(DESTDIR)$(MODDIR)"; \ + done + for o in $(objects); do \ + cp $$o "$(DESTDIR)$(OBJDIR)"; \ + done + +uninstall: + for s in $(sources); do \ + rm -f "$(DESTDIR)$(MODDIR)/`basename $$s`"; \ + done + for o in $(objects); do \ + rm -f "$(DESTDIR)$(OBJDIR)/`basename $$o`"; \ + done + +check-guile: + $(GUILE) -L src/ tests/heredoc.scm + +check: check-guile + +clean: + rm -f \ + heredoc.log $(objects) + + +dev-check: check diff --git a/src/heredoc.scm b/src/heredoc.scm new file mode 100644 index 0000000..fb10088 --- /dev/null +++ b/src/heredoc.scm @@ -0,0 +1,97 @@ +(define-module (heredoc) + #:use-module ((srfi srfi-1) #:prefix s1:) + #:export (heredoc-reader)) + +(define consumable-chars + '(#\space #\tab)) + +(define (extract-single-line-indentation chars) + (s1:fold (lambda (curr acc) + (let* ((prev (s1:first acc)) + (halted? (s1:second acc)) + (n (s1:third acc)) + (non-blank? (not (member curr consumable-chars))) + (changed? (and (not (null? prev)) + (not (equal? curr prev))))) + (cond + (halted? acc) + (non-blank? (list prev #t n)) + (changed? (list prev #t n)) + (#:else (list curr #f (+ 1 n)))))) + '(#nil #f 0) + chars)) + +(define (extract-line-indentations lines) + (map (compose (lambda (triple) + (list (s1:first triple) + (s1:third triple))) + extract-single-line-indentation + string->list) + lines)) + +(define (maximum-indentation lines) + (let* ((line-indentations (extract-line-indentations lines)) + (chars (map s1:first line-indentations)) + (different-indents? (not (= 1 (length (s1:delete-duplicates chars)))))) + (if different-indents? + 0 + (apply min (map s1:second line-indentations))))) + +(define (trim-indentation s) + (let* ((lines (string-split s #\newline)) + (trim-n (maximum-indentation + (filter (lambda (s) (not (equal? "" s))) + lines)))) + (string-join + (map (lambda (line) + (if (equal? "" line) + line + (substring line trim-n))) + lines) + "\n"))) + +(define (non-quote-chars? chars) + (let ((non-quote-chars (filter (lambda (c) + (not (equal? #\" c))) + chars))) + (< 0 (length non-quote-chars)))) + +(define (heredoc-reader _char port) + (let ((should-trim? #f) + (chars '())) + (do ((curr (read-char port) + (read-char port))) + ((equal? #\newline curr)) + (set! chars (cons curr chars))) + (when (and (not (null? chars)) + (equal? #\- (car chars))) + (set! should-trim? #t) + (set! chars (cdr chars))) + (let ((non-quote-chars (non-quote-chars? chars))) + (when non-quote-chars + (error + (format + #f + "Invalid characters at the beginning of the multiline reader: ~s" + (reverse non-quote-chars))))) + (let* ((quote-count (+ 1 (length chars))) + (quote-n 0) + (output '())) + (while #t + (let ((curr (read-char port))) + (when (eof-object? curr) + (error "EOF while reading #\"\"# multiline string")) + (set! output (cons curr output)) + (set! quote-n + (cond + ((and (>= quote-n quote-count) + (equal? #\# curr)) + (break)) + ((equal? #\" curr) (+ 1 quote-n)) + (#:else 0))))) + (let ((s (list->string + (reverse + (s1:drop output (+ 1 quote-count)))))) + (if should-trim? + (trim-indentation s) + s))))) diff --git a/tests/heredoc.scm b/tests/heredoc.scm new file mode 100644 index 0000000..a445ac1 --- /dev/null +++ b/tests/heredoc.scm @@ -0,0 +1,337 @@ +(use-modules + (heredoc) + ((srfi srfi-64) #:prefix t:)) + +(eval-when (expand load eval) + (read-hash-extend #\" heredoc-reader)) + +(define (test-extract-single-line-indentation) + (define extract-single-line-indentation + (@@ (heredoc) extract-single-line-indentation)) + (t:test-group "extract-single-line-indentation" + (t:test-equal '(#nil #f 0) + (extract-single-line-indentation + '())) + + (t:test-equal '(#nil #t 0) + (extract-single-line-indentation + '(#\a #\b #\c))) + + (t:test-equal '(#\space #t 1) + (extract-single-line-indentation + '(#\space #\b #\c))) + + (t:test-equal '(#\space #t 5) + (extract-single-line-indentation + '(#\space #\space #\space #\space #\space #\b #\c))) + + (t:test-equal '(#\space #f 5) + (extract-single-line-indentation + '(#\space #\space #\space #\space #\space))) + + (t:test-equal '(#\space #f 1) + (extract-single-line-indentation + '(#\space))) + + (t:test-equal '(#\tab #t 3) + (extract-single-line-indentation + '(#\tab #\tab #\tab #\b #\c))) + + (t:test-equal '(#\tab #f 3) + (extract-single-line-indentation + '(#\tab #\tab #\tab))) + + (t:test-equal '(#\tab #f 1) + (extract-single-line-indentation + '(#\tab))) + + (t:test-equal '(#\space #t 1) + (extract-single-line-indentation + '(#\space #\tab))) + + (t:test-equal '(#\space #t 2) + (extract-single-line-indentation + '(#\space #\space #\tab #\space))) + + (t:test-equal '(#\tab #t 3) + (extract-single-line-indentation + '(#\tab #\tab #\tab #\space))) + + (t:test-equal '(#\tab #t 3) + (extract-single-line-indentation + '(#\tab #\tab #\tab #\a #\tab))) + + (t:test-equal '(#\tab #t 3) + (extract-single-line-indentation + '(#\tab #\tab #\tab #\a #\space))))) + +(define (test-extract-line-indentations) + (define extract-line-indentations + (@@ (heredoc) extract-line-indentations)) + (t:test-group "extract-line-indentations" + (t:test-equal '() + (extract-line-indentations + '())) + + (t:test-equal '((#nil 0)) + (extract-line-indentations + '(""))) + + (t:test-equal '((#nil 0) (#nil 0) (#nil 0)) + (extract-line-indentations + '("" "" ""))) + + (t:test-equal '((#nil 0) (#nil 0) (#nil 0)) + (extract-line-indentations + '("a" "b" "c"))) + + (t:test-equal '((#\space 1) (#\space 2) (#\space 3)) + (extract-line-indentations + '(" " " " " "))) + + (t:test-equal '((#\space 1) (#\tab 1) (#\space 3)) + (extract-line-indentations + '(" " " " " "))) + + (t:test-equal '((#nil 0) (#\space 3) (#\tab 2)) + (extract-line-indentations + '("no spaces" " with spaces" " with tabs"))))) + +(define (test-maximum-indentation) + (define maximum-indentation + (@@ (heredoc) maximum-indentation)) + (t:test-group "maximum-indentation" + (t:test-equal 0 + (maximum-indentation + '())) + + (t:test-equal 0 + (maximum-indentation + '(""))) + + (t:test-equal 0 + (maximum-indentation + '("" "" ""))) + + (t:test-equal 0 + (maximum-indentation + '("" " " ""))) + + (t:test-equal 1 + (maximum-indentation + '(" " " " " "))) + + (t:test-equal 2 + (maximum-indentation + '(" a" " b" " c"))) + + (t:test-equal 1 + (maximum-indentation + '(" space space" " space tab" " space space tab"))) + + (t:test-equal 0 + (maximum-indentation + '(" space space" " space tab" "none"))))) + +(define (test-trim-indentation) + (define trim-indentation + (@@ (heredoc) trim-indentation)) + (t:test-group "trim-indentation" + (t:test-equal "" + (trim-indentation "")) + + (t:test-equal "alltogether" + (trim-indentation "alltogether")) + + (t:test-equal "with spaces between" + (trim-indentation "with spaces between")) + + (t:test-equal "with spaces around " + (trim-indentation " with spaces around ")) + + (t:test-equal "with multiple spaces " + (trim-indentation " with multiple spaces ")) + + (t:test-equal "tabs " + (trim-indentation " tabs ")) + + (t:test-equal "tabs and spaces " + (trim-indentation " tabs and spaces ")) + + (t:test-equal "\n" + (trim-indentation "\n")) + + (t:test-equal "\n\n\nmultiple empty lines\n\n\n" + (trim-indentation "\n\n\nmultiple empty lines\n\n\n")) + + (t:test-equal "\n\nmixed\n indentations\n" + (trim-indentation "\n\nmixed\n indentations\n")) + + (t:test-equal "\n\n\n \n lines with only spaces\n" + (trim-indentation "\n\n \n \n lines with only spaces\n")) + + (t:test-equal "\n\n mixed spaces\n and tabs" + (trim-indentation "\n\n mixed spaces\n and tabs")) + + (t:test-equal "\nstripped\n tabs" + (trim-indentation "\n stripped\n tabs")) + + (t:test-equal " +#!/bin/sh +set -eu + +ret=0 +if cmd; then + echo 'Done!' + ret=1 +fi +exit $ret +" + (trim-indentation " + #!/bin/sh + set -eu + + ret=0 + if cmd; then + echo 'Done!' + ret=1 + fi + exit $ret +")))) + +(define (test-non-quote-chars?) + (define non-quote-chars? + (@@ (heredoc) non-quote-chars?)) + (t:test-group "non-quote-chars?" + (t:test-equal #f + (non-quote-chars? + '())) + + (t:test-equal #f + (non-quote-chars? + '(#\"))) + + (t:test-equal #f + (non-quote-chars? + '(#\" #\" #\"))) + + (t:test-equal #t + (non-quote-chars? + '(#\" #\" #\-))) + + (t:test-equal #t + (non-quote-chars? + '(#\a #\"))))) + +(define (test-heredoc-reader) + (t:test-group "heredoc-reader" + (t:test-equal "" + #"" +""#) + + (t:test-equal " " + #"" + ""#) + + (t:test-equal " " + #"" + ""#) + + (t:test-equal "" + #""- + ""#) + + (t:test-equal " some\n text" + #"" + some + text""#) + + (t:test-equal "no trailing newline" + #""- + no trailing newline""#) + + (t:test-equal "trailing newline\n" + #""- + trailing newline + ""#) + + (t:test-equal "some\ntext" + #""- + some + text""#) + + (t:test-equal " indented\ntext" + #""- + indented + text""#) + + (t:test-equal "with\nnewline\n" + #""- + with + newline + ""#) + + (t:test-equal " unindented\n newline\n" + #""- + unindented + newline + ""#) + + (t:test-equal "single quote: \"inside\"\n" + #"- + single quote: "inside" + "#) + + (t:test-equal "multiple quotes: #\"\"\"inside\"\"\"#\n" + #""""- + multiple quotes: #"""inside"""# + """"#) + + (t:test-equal " indented" + #" + indented"#) + + (t:test-equal "unindented" + #""- + unindented""#) + + (t:test-equal "#!/bin/sh +set -eu + +some-cmd +echo \"$SOMETHING\"# here is a valid glued comment +if cmd; then + echo 'Indentation here!' + echo \"\"# anothe valid comment, glued to an empty string +fi +" + #"""- + #!/bin/sh + set -eu + + some-cmd + echo "$SOMETHING"# here is a valid glued comment + if cmd; then + echo 'Indentation here!' + echo ""# anothe valid comment, glued to an empty string + fi + """#))) + +(define test-fns + (list + test-extract-single-line-indentation + test-extract-line-indentations + test-maximum-indentation + test-trim-indentation + test-non-quote-chars? + test-heredoc-reader)) + +(define (main) + (t:test-begin "heredoc") + (for-each (lambda (fn) (fn)) test-fns) + (let ((n-fail (t:test-runner-fail-count (t:test-runner-get)))) + (t:test-end) + (when (not (= 0 n-fail)) + (exit 1)))) + +(main) |