From 979c26b3b79785be0786f6d99f49469f8277051e Mon Sep 17 00:00:00 2001 From: Damien Cassou Date: Fri, 11 Nov 2016 09:45:17 +0100 Subject: [PATCH] Add assess-with-filesystem (assess-with-filesystem SPEC &rest FORMS) Create temporary file hierarchy according to SPEC and run FORMS. SPEC is a list of specifications for file system entities which are to be created. --- .gitignore | 2 +- assess.el | 98 +++++++++++++++++++++++++++++++++++++++++++++ test/assess-test.el | 50 +++++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 26c5cde..7479b28 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ *.elc /dist /assess-pkg.el - +/test/assess-test.org diff --git a/assess.el b/assess.el index 1460bac..bee76df 100644 --- a/assess.el +++ b/assess.el @@ -590,6 +590,104 @@ See also `assess-make-related-file'." (kill-buffer ,temp-buffer)))))) ;; #+end_src +;; ** Creating Files and Directories +;; I can write some documentation here if Phil wants to merge code below. +;; *** Implementation +;; #+BEGIN_SRC emacs-lisp +(defun assess-with-filesystem--make-parent (spec path) + "If SPEC is a file name, create its parent directory rooted at PATH." + (save-match-data + (when (string-match "\\(.*\\)/" spec) + (make-directory (concat path "/" (match-string 1 spec)) t)))) + +(defun assess-with-filesystem--init (spec &optional path) + "Interpret the SPEC inside PATH." + (setq path (or path ".")) + (cond + ((listp spec) + (cond + ;; non-empty file + ((and (stringp (car spec)) + (stringp (cadr spec))) + (when (string-match-p "/\\'" (car spec)) + (error "Invalid syntax: `%s' - cannot create a directory with text content" (car spec))) + (assess-with-filesystem--make-parent (car spec) path) + (with-temp-file (concat path "/" (car spec)) + (insert (cadr spec)))) + ;; directory + ((and (stringp (car spec)) + (consp (cadr spec))) + (make-directory (concat path "/" (car spec)) t) + (mapc (lambda (s) (assess-with-filesystem--init + s (concat path "/" (car spec)))) (cadr spec))) + ;; recursive spec, this should probably never happen + (t (mapc (lambda (s) (assess-with-filesystem--init s path)) spec)))) + ;; directory specified using a string + ((and (stringp spec) + (string-match-p "/\\'" spec)) + (make-directory (concat path "/" spec) t)) + ;; empty file + ((stringp spec) + (assess-with-filesystem--make-parent spec path) + (write-region "" nil (concat path "/" spec) nil 'no-message)) + (t (error "Invalid syntax: `%s'" spec)))) + +(defmacro assess-with-filesystem (spec &rest forms) + "Create temporary file hierarchy according to SPEC and run FORMS. + +SPEC is a list of specifications for file system entities which +are to be created. + +File system entities are specified as follows: + +1. a string FILE is the name of file to be created + - if the string contains \"/\", parent directories are created + automatically + - if the string ends with \"/\", a directory is created +2. a list of two elements (FILE CONTENT) specifies filename and the + content to put in the file + - the \"/\" rules apply in the same way as in 1., except you can not + create a directory this way +3. a list where car is a string and cadr is a list (DIR SPEC) is a + recursive specification evaluated with DIR as current directory + - the \"/\" rules apply in the same way as in 1., except you can not + create a file this way, a directory is always created + +An example showing all the possibilities: + + (\"empty_file\" + \"dir/empty_file\" + \"dir/subdir/\" + (\"non_empty_file\" \"content\") + (\"dir/anotherdir/non_empty_file\" \"tralala\") + (\"big_dir\" (\"empty_file\" + (\"non_empty_file\" \"content\") + \"subdir/empty_file\"))) + +If we want to run some code in a directory with an empty file +\"foo.txt\" present, we call: + + (assess-with-filesystem '(\"foo\") + (code-here) + (and-some-more-forms)) + +You should *not* depend on where exactly the hierarchy is created. +By default, a new directory in `temporary-file-directory' is +created and the specification is evaluated there, but this is up +for change." + (declare (indent 1)) + (let ((temp-root (make-symbol "temp-root")) + (old-dd (make-symbol "old-dd"))) + `(let ((,temp-root (make-temp-file "temp-fs-" t)) + (,old-dd default-directory)) + (unwind-protect + (progn + (setq default-directory ,temp-root) + (mapc (lambda (s) (assess-with-filesystem--init s ".")) ,spec) + ,@forms) + (delete-directory ,temp-root t) + (setq default-directory ,old-dd))))) +;; #+END_SRC ;; ** Indentation functions ;; There are two main ways to test indentation -- we can either take unindented diff --git a/test/assess-test.el b/test/assess-test.el index d3a0617..4fa902d 100644 --- a/test/assess-test.el +++ b/test/assess-test.el @@ -288,6 +288,56 @@ This also tests the advice on string=." ;; #+end_src +;; ** Creating Files and Directories +;; #+BEGIN_SRC emacs-lisp +(ert-deftest assess-test-create-multiple-files () + (assess-with-filesystem '("foo" "bar" "baz") + (should (file-regular-p "foo")) + (should (file-regular-p "bar")) + (should (file-regular-p "baz")))) + +(ert-deftest assess-test-create-multiple-directories-and-files () + (assess-with-filesystem '("foo/" "bar/" "baz") + (should (file-directory-p "foo")) + (should (file-directory-p "bar")) + (should (file-regular-p "baz")))) + +(ert-deftest assess-test-create-nested-directories () + (assess-with-filesystem '("foo/bar" "foo/baz/") + (should (file-regular-p "foo/bar")) + (should (file-directory-p "foo/baz")))) + +(defun assess-test-file-contain-p (file content) + "Return nil iff FILE does not contain CONTENT." + (and (file-regular-p file) + (with-temp-buffer + (insert-file-contents file) + (string-match-p content (buffer-string))))) + +(ert-deftest assess-test-create-non-empty-file () + (assess-with-filesystem '(("foo" "amazing content")) + (should (assess-test-file-contain-p "foo" "amazing content")))) + +(ert-deftest assess-test-create-non-empty-nested-file () + (assess-with-filesystem '(("foo/bar" "amazing content")) + (should (assess-test-file-contain-p "foo/bar" "amazing content")))) + +(ert-deftest assess-test-nest-files-recursively () + (assess-with-filesystem '(("foo" ("bar" "baz" "bam/")) + ("a/b" ("c" "d/")) + ("x" (("y" ("z")) + ("content" "content") + "w"))) + (should (file-regular-p "foo/bar")) + (should (file-regular-p "foo/baz")) + (should (file-regular-p "a/b/c")) + (should (file-regular-p "x/y/z")) + (should (file-regular-p "x/content")) + (should (file-regular-p "x/w")) + (should (assess-test-file-contain-p "x/content" "content")) + (should (file-directory-p "foo/bam")) + (should (file-directory-p "a/b/d")))) +;; #+END_SRC ;; ** Indentation Tests ;; #+begin_src emacs-lisp