summaryrefslogtreecommitdiff
path: root/gptel-tools/list_directory_files.el
diff options
context:
space:
mode:
Diffstat (limited to 'gptel-tools/list_directory_files.el')
-rw-r--r--gptel-tools/list_directory_files.el200
1 files changed, 200 insertions, 0 deletions
diff --git a/gptel-tools/list_directory_files.el b/gptel-tools/list_directory_files.el
new file mode 100644
index 00000000..8da9ba28
--- /dev/null
+++ b/gptel-tools/list_directory_files.el
@@ -0,0 +1,200 @@
+;;; list_directory_files.el --- List directory files for GPTel -*- coding: utf-8; lexical-binding: t; -*-
+
+;; Copyright (C) 2025
+
+;; Author: gptel-tool-writer
+;; Keywords: convenience, tools
+;; Version: 2.0.0
+
+;; 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 tool provides a comprehensive directory listing function for use within gptel.
+;; It lists files and directories with detailed attributes such as size, last modification time,
+;; permissions, and executable status, supporting optional recursive traversal and filtering
+;; by file extension.
+;;
+;; Features:
+;; - Lists files with Unix-style permissions, size, and modification date
+;; - Optional recursive directory traversal
+;; - Filter files by extension
+;; - Graceful error handling and reporting
+;; - Human-readable file sizes and dates
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'seq)
+(require 'subr-x)
+
+;;; Helper Functions
+
+(defun list-directory-files--mode-to-permissions (mode)
+ "Convert numeric MODE to symbolic Unix style permissions string."
+ (concat
+ (if (eq (logand #o40000 mode) #o40000) "d" "-")
+ (mapconcat
+ (lambda (bit)
+ (cond ((eq bit ?r) (if (> (logand mode #o400) 0) "r" "-"))
+ ((eq bit ?w) (if (> (logand mode #o200) 0) "w" "-"))
+ ((eq bit ?x) (if (> (logand mode #o100) 0) "x" "-"))))
+ '(?r ?w ?x) "")
+ (mapconcat
+ (lambda (bit)
+ (cond ((eq bit ?r) (if (> (logand mode #o040) 0) "r" "-"))
+ ((eq bit ?w) (if (> (logand mode #o020) 0) "w" "-"))
+ ((eq bit ?x) (if (> (logand mode #o010) 0) "x" "-"))))
+ '(?r ?w ?x) "")
+ (mapconcat
+ (lambda (bit)
+ (cond ((eq bit ?r) (if (> (logand mode #o004) 0) "r" "-"))
+ ((eq bit ?w) (if (> (logand mode #o002) 0) "w" "-"))
+ ((eq bit ?x) (if (> (logand mode #o001) 0) "x" "-"))))
+ '(?r ?w ?x) "")))
+
+(defun list-directory-files--get-file-info (filepath)
+ "Get file information for FILEPATH as a plist."
+ (condition-case err
+ (let* ((attrs (file-attributes filepath 'string))
+ (size (file-attribute-size attrs))
+ (last-mod (file-attribute-modification-time attrs))
+ (dirp (eq t (file-attribute-type attrs)))
+ (mode (file-modes filepath))
+ (perm (list-directory-files--mode-to-permissions mode))
+ (execp (file-executable-p filepath)))
+ (list :success t
+ :path filepath
+ :size size
+ :last-modified last-mod
+ :is-directory dirp
+ :permissions perm
+ :executable execp))
+ (error
+ (list :success nil
+ :path filepath
+ :error (error-message-string err)))))
+
+(defun list-directory-files--filter-by-extension (extension)
+ "Create a filter function for files with EXTENSION."
+ (when extension
+ (lambda (file-info)
+ (or (plist-get file-info :is-directory) ; Always include directories
+ (and (plist-get file-info :success)
+ (string-suffix-p (concat "." extension)
+ (file-name-nondirectory (plist-get file-info :path))
+ t))))))
+
+(defun list-directory-files--format-file-entry (file-info base-path)
+ "Format a single FILE-INFO entry relative to BASE-PATH."
+ (format " %s%s %10s %s %s"
+ (plist-get file-info :permissions)
+ (if (plist-get file-info :executable) "*" " ")
+ (file-size-human-readable (or (plist-get file-info :size) 0))
+ (format-time-string "%Y-%m-%d %H:%M" (plist-get file-info :last-modified))
+ (file-relative-name (plist-get file-info :path) base-path)))
+
+;;; Core Implementation
+
+(defun list-directory-files--list-directory (path &optional recursive filter max-depth current-depth)
+ "List files in PATH directory.
+RECURSIVE enables subdirectory traversal.
+FILTER is a predicate function for filtering files.
+MAX-DEPTH limits recursion depth (nil for unlimited).
+CURRENT-DEPTH tracks the current recursion level."
+ (let ((files '())
+ (errors '())
+ (current-depth (or current-depth 0))
+ (expanded-path (expand-file-name (or path ".") "~")))
+
+ (if (not (file-directory-p expanded-path))
+ ;; Return error if not a directory
+ (list :files nil
+ :errors (list (format "Not a directory: %s" expanded-path)))
+ ;; Process directory
+ (condition-case err
+ (dolist (entry (directory-files expanded-path t "^\\([^.]\\|\\.[^.]\\|\\.\\..\\)"))
+ (let ((info (list-directory-files--get-file-info entry)))
+ (if (plist-get info :success)
+ (progn
+ ;; Add file if it passes the filter
+ (when (or (not filter) (funcall filter info))
+ (push info files))
+ ;; Recurse into directories if needed
+ (when (and recursive
+ (plist-get info :is-directory)
+ (or (not max-depth) (< current-depth max-depth)))
+ (let ((subdir-result (list-directory-files--list-directory
+ entry recursive filter max-depth (1+ current-depth))))
+ (setq files (nconc files (plist-get subdir-result :files)))
+ (setq errors (nconc errors (plist-get subdir-result :errors))))))
+ ;; Handle file access error
+ (push (format "%s: %s" (plist-get info :path) (plist-get info :error)) errors))))
+ (error
+ (push (format "Error accessing directory %s: %s" expanded-path (error-message-string err)) errors)))
+
+ (list :files (nreverse files) :errors (nreverse errors)))))
+
+(defun list-directory-files--format-output (path result)
+ "Format the directory listing RESULT for PATH as a string."
+ (let ((files (plist-get result :files))
+ (errors (plist-get result :errors))
+ (base-path (expand-file-name "~")))
+ (concat
+ (when files
+ (format "Found %d file%s in %s:\n%s"
+ (length files)
+ (if (= (length files) 1) "" "s")
+ path
+ (mapconcat (lambda (f) (list-directory-files--format-file-entry f base-path))
+ files "\n")))
+ (when (and files errors) "\n\n")
+ (when errors
+ (format "Errors encountered:\n%s"
+ (mapconcat (lambda (e) (format " - %s" e)) errors "\n")))
+ ;; Handle case where there are no files and no errors
+ (when (and (not files) (not errors))
+ (format "No files found in %s" path)))))
+
+;;; Tool Registration
+
+(gptel-make-tool
+ :name "list_directory_files"
+ :function (lambda (path &optional recursive filter-extension)
+ "List files in directory PATH.
+RECURSIVE enables subdirectory listing.
+FILTER-EXTENSION limits results to files with the specified extension."
+ (let* ((filter (list-directory-files--filter-by-extension filter-extension))
+ (result (list-directory-files--list-directory path recursive filter)))
+ (list-directory-files--format-output (or path ".") result)))
+ :description "List files in a directory with detailed attributes. Returns formatted listing with permissions, size, modification time."
+ :args (list '(:name "path"
+ :type string
+ :description "Directory path to list (relative to home directory)")
+ '(:name "recursive"
+ :type boolean
+ :description "Recursively list subdirectories"
+ :optional t)
+ '(:name "filter-extension"
+ :type string
+ :description "Only include files with this extension"
+ :optional t))
+ :category "filesystem"
+ :confirm nil
+ :include t)
+
+;; Automatically add to gptel-tools on load
+(add-to-list 'gptel-tools (gptel-get-tool '("filesystem" "list_directory_files")))
+
+(provide 'list_directory_files)
+;;; list_directory_files.el ends here \ No newline at end of file