From 277d15d4bd9d93f86b094b11a26fd98970484e67 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:50:56 +0200 Subject: [PATCH] collect supports adding files and webpage content --- emacs/module/module-collect.el | 178 +++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 43 deletions(-) diff --git a/emacs/module/module-collect.el b/emacs/module/module-collect.el index 049d0ad..5467927 100644 --- a/emacs/module/module-collect.el +++ b/emacs/module/module-collect.el @@ -10,8 +10,24 @@ ;;; collect/store-path is a variable that stores a string defining the path to the directory under which collections are stored. + +(require 'url) +(require 'time-date) + (defvar collect/store-path "~/collections") +(defvar collect/supported-mime-types + '("text/plain" + "text/html" + "text/css" + "text/javascript" + "application/json" + "application/xml" + "text/xml" + "text/markdown" + "text/csv") + "List of MIME types supported for collection.") + (defun collect/delete-collection-internal (collection-name) "Delete an existing collection, moving its contents (except .collection-metadata) into the no-name collection." (let ((source-dir (expand-file-name collection-name collect/store-path)) @@ -33,36 +49,40 @@ (message "Collection '%s' deleted and contents moved to 'no-name' collection." collection-name)) (error "Collection '%s' does not exist" collection-name)))) +(defun collect/delete-collection () + "Interactively delete an existing collection." + (interactive) + (let* ((collections (collect/get-collections)) + (choice (completing-read "Select collection to delete: " (mapcar #'car collections) nil t)) + (dir-to-delete (cdr (assoc choice collections)))) + (when dir-to-delete + (collect/delete-collection-internal (file-name-nondirectory dir-to-delete))))) + (defun collect/create-collection () "Interactively create a new collection." (interactive) (let ((collection-name (read-string "Enter collection name: "))) (collect/create-collection-internal collection-name))) -(defun collect/delete-collection () - "Interactively delete an existing collection." - (interactive) - (let* ((all-files (directory-files collect/store-path t "^[^.]")) - (collections (cl-remove-if - (lambda (dir) - (or (not (file-directory-p dir)) - (string= (file-name-nondirectory dir) "no-name"))) - all-files)) - (collection-names - (mapcar (lambda (dir) - (let ((metadata-file (expand-file-name ".collection-metadata" dir))) - (if (file-exists-p metadata-file) - (with-temp-buffer - (insert-file-contents metadata-file) - (if (re-search-forward "^name: \\(.*\\)$" nil t) - (match-string 1) - (file-name-nondirectory dir))) - (file-name-nondirectory dir)))) - collections)) - (choice (completing-read "Select collection to delete: " collection-names nil t)) - (dir-to-delete (nth (cl-position choice collection-names :test 'string=) collections))) - (when dir-to-delete - (collect/delete-collection-internal (file-name-nondirectory dir-to-delete))))) +(defun collect/create-collection-internal (collection-name) + "Create a new collection with the given name." + (let* ((unix-path (replace-regexp-in-string "[^[:alnum:]_-]" "-" + (downcase (string-trim collection-name)))) + (full-path (expand-file-name unix-path collect/store-path)) + (metadata-file (expand-file-name ".collection-metadata" full-path)) + (current-time (format-time-string "%Y-%m-%d %H:%M:%S"))) + (cond + ((string-empty-p unix-path) + (error "Collection name cannot be empty")) + ((file-exists-p full-path) + (error "Collection '%s' already exists" collection-name)) + (t + (make-directory full-path t) + (with-temp-file metadata-file + (insert (format "name: %s\n" collection-name) + (format "created: %s\n" current-time) + (format "updated: %s\n" current-time))) + (message "Collection '%s' created successfully with metadata." collection-name))))) (defun collect/rename-collection-internal (old-name new-name) "Rename collection from OLD-NAME to NEW-NAME." @@ -101,27 +121,11 @@ (defun collect/rename-collection () "Interactively rename an existing collection." (interactive) - (let* ((all-files (directory-files collect/store-path t "^[^.]")) - (collections (cl-remove-if - (lambda (dir) - (or (not (file-directory-p dir)) - (string= (file-name-nondirectory dir) "no-name"))) - all-files)) - (collection-names - (mapcar (lambda (dir) - (let ((metadata-file (expand-file-name ".collection-metadata" dir))) - (if (file-exists-p metadata-file) - (with-temp-buffer - (insert-file-contents metadata-file) - (if (re-search-forward "^name: \\(.*\\)$" nil t) - (match-string 1) - (file-name-nondirectory dir))) - (file-name-nondirectory dir)))) - collections)) - (old-name (completing-read "Select collection to rename: " collection-names nil t)) + (let* ((collections (collect/get-collections)) + (old-name (completing-read "Select collection to rename: " (mapcar #'car collections) nil t)) (new-name (read-string (format "Enter new name for collection '%s': " old-name)))) (when (and old-name new-name) - (let ((dir-to-rename (nth (cl-position old-name collection-names :test 'string=) collections))) + (let ((dir-to-rename (cdr (assoc old-name collections)))) (when dir-to-rename (collect/rename-collection-internal (file-name-nondirectory dir-to-rename) new-name)))))) @@ -159,5 +163,93 @@ (file-name-nondirectory file-to-add) selected-collection))))) +(defun collect/add-webpage-to-collection () + "Add a webpage to a specified collection." + (interactive) + (let* ((url (read-string "Enter URL to add: ")) + (all-files (directory-files collect/store-path t "^[^.]")) + (collections (cl-remove-if + (lambda (dir) + (or (not (file-directory-p dir)) + (string= (file-name-nondirectory dir) "no-name"))) + all-files)) + (collection-names + (mapcar (lambda (dir) + (let ((metadata-file (expand-file-name ".collection-metadata" dir))) + (if (file-exists-p metadata-file) + (with-temp-buffer + (insert-file-contents metadata-file) + (if (re-search-forward "^name: \\(.*\\)$" nil t) + (match-string 1) + (file-name-nondirectory dir))) + (file-name-nondirectory dir)))) + collections)) + (selected-collection (completing-read "Select collection to add to: " collection-names nil t)) + (target-dir (nth (cl-position selected-collection collection-names :test 'string=) collections))) + (when (and url target-dir) + (plz 'get url + :as 'response + :then (lambda (response) + (collect/process-webpage-response url response target-dir selected-collection)) + :else (lambda (error) + (message "Error fetching webpage: %s" error)))))) + +(defun collect/get-collections () + "Get a list of collections with their names and directories." + (let ((all-dirs (directory-files collect/store-path t "^[^.]"))) + (cl-remove-if #'null + (mapcar (lambda (dir) + (when (file-directory-p dir) + (let* ((metadata-file (expand-file-name ".collection-metadata" dir)) + (name (collect/get-collection-name metadata-file))) + (when name + (cons name dir))))) + all-dirs)))) + +(defun collect/get-collection-name (metadata-file) + "Get the collection name from the metadata file." + (when (file-exists-p metadata-file) + (with-temp-buffer + (insert-file-contents metadata-file) + (goto-char (point-min)) + (when (re-search-forward "^name: \\(.+\\)$" nil t) + (match-string 1))))) + + +(defun collect/add-webpage-to-collection () + "Add a webpage to a specified collection." + (interactive) + (let* ((url (read-string "Enter URL to add: ")) + (collections (collect/get-collections)) + (selected-collection (completing-read "Select collection to add to: " (mapcar #'car collections) nil t)) + (target-dir (cdr (assoc selected-collection collections))) + (filename (collect/generate-filename url)) + (target-file (expand-file-name filename target-dir))) + (when (and url (file-directory-p target-dir)) + (url-retrieve url + (lambda (status target-file filename collection-name) + (if (plist-get status :error) + (message "Error fetching webpage: %s" (plist-get status :error)) + (goto-char (point-min)) + (re-search-forward "\n\n") + (write-region (point) (point-max) target-file) + (message "Webpage '%s' added to collection '%s'." filename collection-name))) + (list target-file filename selected-collection))))) + +(defun collect/generate-filename (url) + "Generate a filename for the given URL." + (let* ((timestamp (format-time-string "%Y%m%dT%H%M%S")) + (parsed-url (url-generic-parse-url url)) + (host (url-host parsed-url)) + (path (url-filename parsed-url)) + (path-without-schema (replace-regexp-in-string "^/+" "" path)) + (sanitized-path (replace-regexp-in-string "[^a-zA-Z0-9._-]" "_" path-without-schema)) + (full-path (concat host "_" sanitized-path)) + (extension (if (string-match "\\.[^.]+$" full-path) + (match-string 0 full-path) + ".html"))) + (concat timestamp "_" (file-name-sans-extension full-path) extension))) + + (provide 'module-collect)