summaryrefslogtreecommitdiff
path: root/gptel-tools/update_text_file.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
committerCraig Jennings <c@cjennings.net>2025-10-12 11:47:26 -0500
commit092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch)
treeea81999b8442246c978b364dd90e8c752af50db5 /gptel-tools/update_text_file.el
changing repositories
Diffstat (limited to 'gptel-tools/update_text_file.el')
-rw-r--r--gptel-tools/update_text_file.el149
1 files changed, 149 insertions, 0 deletions
diff --git a/gptel-tools/update_text_file.el b/gptel-tools/update_text_file.el
new file mode 100644
index 00000000..0125e2ab
--- /dev/null
+++ b/gptel-tools/update_text_file.el
@@ -0,0 +1,149 @@
+;;; update_text_file.el --- Update text files for gptel -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025
+
+;; Author: gptel-tool-writer
+;; Keywords: convenience, tools
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;;; Commentary:
+
+;; This file provides a gptel tool for updating text files with various
+;; operations including replace, append, prepend, insert-at-line, and
+;; delete-lines. The tool creates timestamped backups and shows diffs
+;; before applying changes.
+
+;;; Code:
+
+(require 'gptel)
+(require 'subr-x)
+
+;; Helper function for building sed commands
+(defun cj/build-sed-command (operation pattern replacement line-num temp-file)
+ "Build appropriate sed/shell command for OPERATION."
+ (pcase operation
+ ("replace"
+ (unless (and pattern replacement)
+ (error "Replace operation requires pattern and replacement"))
+ (format "sed -i 's|%s|%s|g' '%s'"
+ (replace-regexp-in-string "|" "\\\\\\\\|" pattern)
+ (replace-regexp-in-string "|" "\\\\\\\\|" replacement)
+ temp-file))
+ ("append"
+ (unless pattern
+ (error "Append operation requires text to append"))
+ (format "printf '%%s\\\\n' %s >> '%s'"
+ (shell-quote-argument pattern)
+ temp-file))
+ ("prepend"
+ (unless pattern
+ (error "Prepend operation requires text to prepend"))
+ (format "(printf '%%s\\\\n' %s; cat '%s') > '%s.new' && mv '%s.new' '%s'"
+ (shell-quote-argument pattern)
+ temp-file temp-file temp-file temp-file))
+ ("insert-at-line"
+ (unless (and pattern line-num)
+ (error "Insert-at-line requires text and line number"))
+ (format "sed -i '%di\\\\%s' '%s'"
+ line-num
+ (replace-regexp-in-string "/" "\\\\\\\\/" pattern)
+ temp-file))
+ ("delete-lines"
+ (unless pattern
+ (error "Delete-lines requires pattern"))
+ (format "sed -i '/%s/d' '%s'"
+ (replace-regexp-in-string "/" "\\\\\\\\/" pattern)
+ temp-file))
+ (_
+ (error "Unknown operation: %s" operation))))
+
+;; Main tool definition
+(with-eval-after-load 'gptel
+ (gptel-make-tool
+ :name "update_text_file"
+ :function (lambda (path operation &optional pattern replacement line-num)
+ (let* ((full-path (expand-file-name path "~"))
+ (temp-file (make-temp-file "gptel-update-" nil ".tmp"))
+ (backup-name (format "%s-%s.bak"
+ full-path
+ (format-time-string "%Y-%m-%d-%H%M%S"))))
+ (unwind-protect
+ (progn
+ ;; Validate path
+ (unless (string-prefix-p (expand-file-name "~") full-path)
+ (error "Path must be within home directory"))
+ (unless (file-exists-p full-path)
+ (error "File not found: %s" full-path))
+ (unless (file-readable-p full-path)
+ (error "No read permission for file: %s" full-path))
+ ;; Check file size
+ (let ((size (file-attribute-size (file-attributes full-path))))
+ (when (> size (* 10 1024 1024))
+ (error "File too large (%s): exceeds 10MB limit"
+ (file-size-human-readable size))))
+ ;; Create backup
+ (copy-file full-path backup-name t)
+ ;; Copy to temp file for operations
+ (copy-file full-path temp-file t)
+ ;; Execute operation and check diff
+ (let* ((sed-cmd (cj/build-sed-command operation pattern replacement line-num temp-file))
+ (result (shell-command-to-string sed-cmd))
+ (diff-output (shell-command-to-string
+ (format "diff -u '%s' '%s' 2>/dev/null" full-path temp-file))))
+ (if (string-empty-p diff-output)
+ (progn
+ (delete-file backup-name)
+ (format "No changes made to %s" full-path))
+ (if (y-or-n-p (format "Apply these changes to %s?\\n\\n%s\\n"
+ full-path diff-output))
+ (progn
+ (copy-file temp-file full-path t)
+ (format "Updated %s (backup: %s)"
+ full-path (file-name-nondirectory backup-name)))
+ (progn
+ (delete-file backup-name)
+ (error "Update cancelled by user"))))))
+ ;; Cleanup temp file
+ (when (file-exists-p temp-file)
+ (delete-file temp-file)))))
+ :description "Update a text file with various operations: replace, append, prepend, insert-at-line, or delete-lines. Shows diff before applying changes and creates timestamped backups."
+ :args (list '(:name "path"
+ :type string
+ :description "File path relative to home directory, e.g., 'documents/myfile.txt' or '~/documents/myfile.txt'")
+ '(:name "operation"
+ :type string
+ :enum ["replace" "append" "prepend" "insert-at-line" "delete-lines"]
+ :description "The type of update operation to perform")
+ '(:name "pattern"
+ :type string
+ :description "For replace/delete: pattern to match. For append/prepend/insert: text to add"
+ :optional t)
+ '(:name "replacement"
+ :type string
+ :description "For replace operation: the replacement text"
+ :optional t)
+ '(:name "line_num"
+ :type integer
+ :description "For insert-at-line operation: the line number where to insert"
+ :optional t))
+ :category "filesystem"
+ :confirm t
+ :include t))
+
+;; Automatically add to gptel-tools on load
+(add-to-list 'gptel-tools (gptel-get-tool '("filesystem" "update_text_file")))
+
+
+(provide 'update_text_file)
+;;; update_text_file.el ends here"