#lang racket/base
(require racket/file
syntax/parse
syntax/srcloc
(planet neil/overeasy:2)
"progedit.rkt")
(test-section 'progedit
(test-section 'sanity-check-positions
(test 'source-location-position
(source-location-position (read-syntax 'foo (open-input-string "(aaa)")))
1)
(test 'file-position
(let ((p (open-input-string "(aaa)"))) (port-count-lines! p) (read-char p) (file-position p))
1))
(test-section 'jane
(define (%define-name-as-jane in-str)
(let ((in (open-input-string in-str)))
(let loop ((name-stx #f))
(let ((stx (read-syntax 'my-source in)))
(if (eof-object? stx)
(let-values (((inserts replaces)
(if name-stx
(values '()
`((,name-stx ,#'"Jane")))
(values `((#f #\newline
,#'(define name "Jane")
#\newline))
'())))
((out) (open-output-string)))
(progedit (open-input-string in-str)
out
#:inserts inserts
#:replaces replaces)
(get-output-string out))
(syntax-parse stx
(((~datum define) (~datum name) VAL)
(if name-stx
(raise-syntax-error
'%define-name-as-jane
"(define name VAL) occurred multiple times"
stx
#f
(list name-stx))
(loop #'VAL)))
(_ (loop name-stx))))))))
(test 'name-is-defined
(%define-name-as-jane
"(define honorific \"Dr.\")\n;; House.\n\n(define name \"John\")\n\n(define age 29)\n")
"(define honorific \"Dr.\")\n;; House.\n\n(define name \"Jane\")\n\n(define age 29)\n")
(test 'name-is-not-defined
(%define-name-as-jane
"(define honorific \"Dr.\")\n;; House.\n\n(define age 29)\n")
"(define honorific \"Dr.\")\n;; House.\n\n(define age 29)\n\n(define name \"Jane\")\n"))
(test-section 'tricky-ordering
(define (%aaa-bbb-ccc proc)
(let ((in-str "(aaa bbb ccc)"))
(let ((stx (read-syntax 'my-source (open-input-string in-str))))
(syntax-parse stx
((A B C)
(let ((out (open-output-string)))
(proc (open-input-string in-str) out #'A #'B #'C)
(get-output-string out)))))))
(test 'insert-after-replace
(%aaa-bbb-ccc
(lambda (in out a-stx b-stx c-stx)
(progedit in
out
#:inserts `(((after ,b-stx) " After"))
#:replaces `((,b-stx "Bee")))))
"(aaa Bee After ccc)")
(test 'insert-before-replace
(%aaa-bbb-ccc
(lambda (in out a-stx b-stx c-stx)
(progedit in
out
#:inserts `(((before ,b-stx) "Before "))
#:replaces `((,b-stx "Bee")))))
"(aaa Before Bee ccc)")
(test 'inserts-before-after-delete
(%aaa-bbb-ccc
(lambda (in out a-stx b-stx c-stx)
(progedit in
out
#:deletes `(,b-stx)
#:inserts `(((before ,b-stx) "Before ")
((after ,b-stx) "After")))))
"(aaa Before After ccc)")
(test 'inserts-after-before-delete
(%aaa-bbb-ccc
(lambda (in out a-stx b-stx c-stx)
(progedit in
out
#:deletes `(,b-stx)
#:inserts `(((after ,b-stx) "After")
((before ,b-stx) "Before ")))))
"(aaa Before After ccc)"))
(test-section 'progedit-file
(test 'basic
(let ((actual-path "temporary-progedit-test-1")
(backup-path "temporary-progedit-test-1-backup"))
(dynamic-wind
void
(lambda ()
(display-to-file ";; Comment\n(aaa bbb ccc)\n" actual-path #:exists 'replace)
(with-handlers ((exn:fail? void)) (delete-file backup-path))
(let ((result (progedit-file
actual-path
#:read
(lambda (in)
(syntax-parse (read-syntax 'my-source in)
((A B C)
(let ((out (open-output-string)))
(values #'A #'B #'C)))))
#:write
(lambda (in out a-stx b-stx c-stx)
(progedit in
out
#:inserts `(((after ,b-stx) " After")
((before ,b-stx) "Before ")
(1 "#lang foo" #\newline #\newline)
(#f "\n;; EOF\n"))
#:replaces `((,b-stx "Replaced")))
42)
#:backup (lambda args backup-path))))
(values result
(file->string actual-path))))
(lambda ()
(with-handlers ((exn:fail? void)) (delete-file actual-path))
(with-handlers ((exn:fail? void)) (delete-file backup-path)))))
(values 42
"#lang foo\n\n;; Comment\n(aaa Before Replaced After ccc)\n\n;; EOF\n"))
(test 'aborted-without-writing
(let ((actual-path "temporary-progedit-test-1")
(backup-path "temporary-progedit-test-1-backup"))
(dynamic-wind
void
(lambda ()
(display-to-file "original" actual-path #:exists 'replace)
(let ((identity-before (file-or-directory-identity actual-path)))
(with-handlers ((exn:fail? void)) (delete-file backup-path))
(let/ec abort-the-progedit-ec
(progedit-file actual-path
#:read
(lambda (in)
(abort-the-progedit-ec))
#:write
(lambda (in out . args)
(display "stomped" out #:exists 'replace)
(flush-output out))
#:backup
(lambda args backup-path)))
(values (if (equal? (file-or-directory-identity actual-path)
identity-before)
'identity-same
'identity-changed)
(file->string actual-path))))
(lambda ()
(with-handlers ((exn:fail? void)) (delete-file actual-path))
(with-handlers ((exn:fail? void)) (delete-file backup-path)))))
(values 'identity-same
"original"))
))