- proiectul trebuie refactorizat pentru functioneaza aleator si toate task-urile globale nu mai merg.
- primul lucru: “ascutit creioanele”:
- in versiunea initiala am folosit cask, intretimp am uitat totul despre cask asa ca acum am 2 variante:
- sa reinvat cask
- sa invat eldev
- o sa incerc sa invat eldev pt ca mi se pare mai accesibil
- in versiunea initiala am folosit cask, intretimp am uitat totul despre cask asa ca acum am 2 variante:
- am instalat emacs30 si ent nu mai merge
- pentru a depana ent trebuie intai sa pun la punct un mediu de dezvoltare nix pentru elisp similar cu ce am pentru clojure si python.
- ma inspir din template cljproj:
- flake
{ description = "flake for ent project"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; flake-parts.url = "github:hercules-ci/flake-parts"; }; outputs = { flake-parts, ... }@inputs: flake-parts.lib.mkFlake { inherit inputs;} { debug = true; systems = [ "x86_64-linux" ]; perSystem = { config, self', inputs', pkgs, system, ... }: { packages = rec { ent = pkgs.callPackage ./default.nix { inherit (pkgs) fetchFromGitHub; trivialBuild = pkgs.emacs.pkgs.trivialBuild; seq = pkgs.emacsPackages.seq; dash = pkgs.emacsPackages.dash; }; default = ent; }; devShells.default = pkgs.mkShell { packages = [ self'.packages.default ]; }; }; }; }
- default.nix:
{ lib, trivialBuild, dash, seq, fetchFromGitHub }: trivialBuild rec { pname = "ent"; version = "v2.0"; src = ./.; # elisp dependencies propagatedUserEnvPkgs = [ dash seq ]; buildInputs = propagatedUserEnvPkgs; meta = with lib; { description = "A build tool like ant but working inside emacs and using elisp syntax"; homepage = "https://github.com/dpom/ent"; license = licenses.gpl3; platforms = platforms.all; }; }
- envrc:
source_env_if_exists .envrc.local use flake
- flake
- refactor:
(setq initfile "~/pers/projects/ent/ent.el") (load initfile) ent-tasks [#<obarray n=12> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] (type-of ent-tasks) vector (cl-defstruct task name doc deps action) (defvar tasks ()) (setq test (make-task :doc (documentation 'ent-clean-task) :deps () :func '(lambda (&optional file) (ent-emacs "ent-clean-task" file)))) (defun task (name deps doc func) (let ((tsk (make-task :doc doc :deps deps :func func))) (setq tasks (plist-put tasks name tsk)))) (task 'clean () (documentation 'ent-clean-task) '(lambda (&optional file) (ent-emacs "ent-clean-task" file))) (mapcar #'car tasks) (setq tasks (remove 'clean tasks)) (cl-remf tasks nil) (setq tasks ()) (defun plist-keys (plist) "Get just the keys of a plist." (cl-loop for key in plist by #'cddr collecting key)) (defun plist-values (plist) "Get just the values of a plist." (cl-loop for value in (rest plist) by #'cddr collecting value)) (plist-keys tasks) (defun symbol-to-string (symbol) (format "%s" symbol)) (defun task (name deps doc &optional action) (let ((tsk (make-task :name name :doc doc :deps (mapcar 'symbol-to-string deps) :action action))) (setq tasks (plist-put tasks (symbol-to-string name) tsk)))) (defun run-task (tsk dir out-buffer) (insert (format "Start %s\n" (task-name tsk))) (when (task-deps tsk) (dolist (dt (task-deps tsk)) (run-task (plist-get tasks (symbol-to-string dt)) dir out-buffer))) (let ((action (task-action tsk))) (cond ((functionp action) (funcall action dir)) ((stringp action) (do (shell-cd dir) (shell-command action out-buffer out-buffer))) t (insert "no action\n"))) (insert (format "End %s\n" (task-name tsk)))) (defun ent2 (dir) (let ((initfile (ent-find-project-file)) (dir (locate-dominating-file default-directory ent-project-config-filename)) (out-buffer (generate-new-buffer "*ent-out*" ))) (setq tasks ()) (switch-to-buffer out-buffer) (load initfile) (if (not taskname) (setq taskname (ido-completing-read "Command: " (plist-keys tasks) nil t))) (run-task (plist-get tasks taskname) dir out-buffer))) (defun ent-clean (dir) "Remove all files matching REGEXP or ent-clean-regexp from current dir recursively." (let ((acc 0) (regexp (or ent-clean-regexp ent-clean-default-regexp))) (displaying-byte-compile-warnings (insert "\nclean: %s from %s" regexp dir) (ent-walk dir regexp #'(lambda (x) (delete-file (expand-file-name x)) (setq acc (+ acc 1)) (message "%s deleted" (expand-file-name x)))) (message "clean: command terminated %d files removed" acc) acc)))
- refactor functiile ent-walk si ent-walk-
- corectie run asincron:
In emacs-lisp how to run a callback function when the shell command started with a start-process function exit. ### How to run a callback when a `start‑process` finishes `start‑process` returns a `process` object. You attach a **sentinel** (or a filter) to that object and Emacs calls the sentinel every time the process status changes. When the status becomes `exit` you can run whatever code you want. ```elisp (defun my-run-cmd-with-callback (cmd args &rest callback) "Run CMD with ARGS in a separate process. When the process exits the last element of CALLBACK is run." (let ((proc (start-process "my‑proc" ; process name "*my‑proc-output*" ; buffer for output cmd ; program args...))) ; arguments ;; Attach a sentinel (set-process-sentinel proc (lambda (process event) ;; Event string looks like \"finished\n\" or \"signal 15\n\" … (when (string-match-p "finished" event) ;; Process has terminated – run the callback (apply #'funcall callback (list process))))))) ``` #### Usage ```elisp (my-run-cmd-with-callback "bash" "-c" "echo hello; sleep 2; echo bye" (lambda (process) (message "Process %s finished (exit code %d)" (process-name process) (process-exit-status process)))) ``` The lambda receives the finished process as its sole argument; you can query its exit status, read its output buffer, etc. --- ### Quick notes | Term | What it does | Typical place to set | |------|--------------|----------------------| | **sentinel** | Called on status changes (exit, signal, output). | `set-process-sentinel` (after `start-process`) or `:sentinel` argument in `make-process`. | | **filter** | Called for every chunk of output. | `set-process-filter` (if you only care about output). | Using a sentinel is the idiomatic way to react when the process *exits*, as shown above.
- am cerut gptel sa creeze functia asincrona:
To implement an asynchronous function in Emacs Lisp that runs a shell command, writes its output and error to `ent2-log-buffer`, and stores the exit code in `ent2-shell-result`, you can use the following approach with `shell-command-in-region` (or similar) and non-blocking execution via `process-send-string` and a background process. However, a more idiomatic way is to use `with-process` (or `make-process` for more control) along with an asynchronous process. Here’s how you can do it: 1. Define the buffers and variables if they don’t exist. 2. Define a function that creates a background process to run the command. 3. Use the process functions to capture output/error and store the exit code. Here's the implementation:(defun ent2-run-async-shell-command (command) "Run COMMAND asynchronously, write output/error to `ent2-log-buffer' and store exit code in `ent2-shell-result'." (unless (boundp 'ent2-log-buffer) (setq ent2-log-buffer (get-buffer-create "*Ent2 Shell Log*"))) (with-current-buffer ent2-log-buffer (erase-buffer) (insert (format "Running command: %s\n" command))) (setq ent2-shell-result nil) ;; Create a process to run the shell command (make-process (make-process ;; Process name to identify in `process-list' :name "ent2-shell" ;; Specify a function to receive input :filter (lambda (data &optional proc) ;; Write to buffer regardless of whether it's output or error (with-current-buffer ent2-log-buffer (inserted data))) ;; Specify the command :command command ;; Error handler: write error message to buffer as well :error-handler (lambda (proc) (with-current-buffer ent2-log-buffer (insert (format "Process %s terminated with signal: %s\n" (process-name proc) (process-status proc))))) ;; On completion callback :on-exit (lambda (proc status) (with-current-buffer ent2-log-buffer (insert (format "Exit code: %d\n" status))) (setq ent2-shell-result status))))) `
### Notes: 1. **Buffer Creation:** The function first ensures the `ent2-log-buffer` exists (or creates it). 2. **Buffer Log:** The current buffer is used to write logs. The command is inserted along with its output/error. 3. **Process Management:** The `make-process` function runs the command asynchronously. 4. **Error Handling:** Error messages are written to `ent2-log-buffer`. 5. **Exit Code:** After the command finishes, the exit code is stored in `ent2-shell-result`. ### Adjustments: - Ensure `ent2-log-buffer` and `ent2-shell-result` are properly scoped or use symbols outside the function. - The buffer should not require reinsertion after each run. Modify the `with-current-buffer` usage if needed. ### Usage: Call it with a command string, e.g.: ```elisp (ent2-run-async-shell-command "ls -l") ```
“` reasoning We need to analyze and refactor this function. The instructions: add missing docstrings, improve naming, extract duplicate code, fix bugs, error handling. Provide analysis summary, refactored code, recommendations.
Let’s analyze function:
`ent2-run-shell-command` takes a string command, logs start, resets `ent2-shell-result`, creates a process using `make-process`. It splits command string by spaces which fails for arguments with spaces or quoted. It sets sentinel to log exit code, process object, end task and sets `ent2-shell-result` to status. Should store exit code numeric? The sentinel receives status string like “finished\n”. It sets variable to status, which is string, not exit code.
Also not using lexical binding maybe. Should use `lexical-binding`? The code likely part of package.
Need to add docstring: “Run COMMAND asynchronously in a subprocess, logging output to ent2-log-buffer and storing exit status in ent2-shell-result.” Already exists docstring but could improve.
Name: `ent2-run-shell-command` is fine.
Potential issues: not using `split-string` properly. Use `make-process` with :command list of strings, should be a list like `(“bash” “-c” command)`. Or use :command ‘(“sh” “-c” command). That would allow spaces, quotes. Use `list “sh” “-c” command`. Then not need split.
Also sentinel logs status but not exit code numeric. `status` is a string “finished\n” or “exited abnormally” etc. Could parse.
Also `ent2-log` uses custom formatting; we need to know its signature. Suppose it is similar to `princ` or `message`. We can’t assume.
Also variable names: `ent2-log-buffer` maybe buffer object; but they pass :buffer to make-process. That’s ok.
We should make function use lexical binding and lexical variables.
Also provide optional callback? Not needed.
Add error handling: If `make-process` fails, catch error, log.
Add interactive spec? Not user-facing.
Now produce refactored code:
Use `cl-lib`.
Use `make-process` with :name “ent2-shell” :buffer ent2-log-buffer :command ‘(“bash” “-c” command) ; or “sh”.
Define sentinel as function to log.
Use `setq-local` for process? Not necessary.
Better to set `ent2-shell-result` to exit code numeric.
We can parse exit code from `status` by using `process-exit-status` if we get process object? sentinel receives proc; we can call `process-exit-status` when sentinel triggers.
But sentinel may get called multiple times. In `process-sentinel`, status string like “finished\n” “exited abnormally\n”. Use `process-exit-status` to get code.
So:
“` (make-process :name “ent2-shell” :buffer ent2-log-buffer :command (list “sh” “-c” command) :sentinel (lambda (proc _) (let ((code (process-exit-status proc))) (ent2-log “Exit code: %d\n” code) (ent2-log “Proc: %s\n” proc) (ent2-log “[%s] End task\n” (ent2-time-iso-format)) (setq ent2-shell-result code)))) “`
Also ensure ent2-log-buffer is buffer name or buffer object? The code uses `:buffer ent2-log-buffer`. If it’s a buffer name, fine.
Also handle errors: Surround make-process with `condition-case`. If error, log and set result nil.
Also might want to capture process output? Already writes to buffer.
Also set `ent2-shell-result` nil initially; maybe keep.
Add `declare` for lexical-binding.
Add docstring: “Run a shell command asynchronously, logging output to `ent2-log-buffer` and storing exit status in `ent2-shell-result`. The command is executed via `/bin/sh -c`. The function returns the process object.”
Also return process object.
Add `interactive` maybe not needed.
Now produce analysis summary: identify issues: command splitting wrong; sentinel logs status string not numeric; no error handling; may cause buffer mismatch; no documentation; not returning anything; potential race condition; no lexical binding; not using proper names; etc.
Provide refactored code.
Provide recommendations: add tests; use `defcustom` for shell path; handle errors; add `ent2-sentinel` function; support callback; consider using `process-filter`; etc.
Now produce final answer. “`
## Analysis Summary
| Issue | Description | Risk |
|---|---|---|
| **Incorrect command parsing** | `split-string` naïvely splits on spaces, breaking quoted arguments or file paths containing spaces. | High – commands may fail or behave unpredictably. |
| **Exit‑code handling** | Sentinel receives a status string (`”finished\n”`, `”exited abnormally\n”`). Storing that string as `ent2-shell-result` is misleading; callers normally expect an integer exit code. | High – downstream logic that checks `ent2-shell-result` will not work. |
| **Missing error handling** | `make-process` may raise an error (e.g., if the shell binary is missing). The current code swallows this silently. | Medium – the user would see no feedback. |
| **No return value** | The function never returns the created `process` object. Callers can’t programmatically interact with the process. | Low – but useful for advanced usage. |
| **Docstring & Naming** | The docstring is short and the function name is fine, but other helpers (`ent2-log`, `ent2-log-buffer`, etc.) are not explained. | Low – readability would improve. |
| **Lexical scope** | The code relies on dynamic variables (`ent2-shell-result`). Using lexical binding would prevent accidental global leakage. | Medium – potential for subtle bugs. |
—
## Refactored Code
“`elisp ;;; ent2-shell.el — Asynchronous shell command runner
;;; Commentary: ;; This module provides a thin wrapper around `make-process` that ;; executes a shell command asynchronously, streams its output to ;; `ent2-log-buffer`, and records the numeric exit code in ;; `ent2-shell-result`. The command is passed to `/bin/sh -c` so that ;; shell syntax (quoting, globbing, pipes, etc.) is preserved.
;;; Code:
(require ‘cl-lib)
(defvar ent2-shell-result nil “Numeric exit status of the most recent shell command started with `ent2-run-shell-command’. `nil’ indicates that the command is still running or that it failed to start.”)
(defvar ent2-log-buffer ”ent2-Log” “Name of the buffer where shell command output is logged. This buffer is created automatically if it does not exist.”)
(defun ent2-run-shell-command (command) """Run COMMAND asynchronously in `/bin/sh -c’.
The command is executed in a child process; its standard output and standard error are sent to the buffer named by `ent2-log-buffer’. The function logs the start time, command, and later the exit status via `ent2-log’. The numeric exit status is stored in `ent2-shell-result’ and the created process object is returned.
\nCommand is a plain string, so quoting and shell operators are handled exactly as they would be in an interactive shell.
Example: `(ent2-run-shell-command "git pull && echo finished")’
It is the caller’s responsibility to interpret the returned process object if further interaction (e.g., killing the process) is needed." (declare (lexical-binding t)) (unless (stringp command) (error “ent2-run-shell-command expects a string, got: %S” command))
;; Ensure the log buffer exists. (with-current-buffer (get-buffer-create ent2-log-buffer) (setq buffer-read-only nil))
;; Reset the result state. (setq ent2-shell-result nil)
;; Log the start of the job. (ent2-log “[%s] Running command: %s\n” (ent2-time-iso-format) command)
;; Create a process. Wrap in `condition-case’ so that startup ;; failures are reported. (condition-case err (let ((proc (make-process :name “ent2-shell” :buffer ent2-log-buffer :command (list “sh” “-c” command) :sentinel #’ent2–shell-sentinel))) ;; Return the process object so callers can interact with it. proc) (error (ent2-log “Error creating process for command `%s’: %s\n” command (error-message-string err)) (setq ent2-shell-result nil) nil)))
(defun ent2–shell-sentinel (proc _status) "Internal sentinel used by `ent2-run-shell-command`.
PROCID is the process object. The sentinel logs the numeric exit status to `ent2-log-buffer’ and stores that status in `ent2-shell-result`. It is invoked by `make-process` when the process changes state." (let ((exit-code (process-exit-status proc))) (ent2-log “Exit code: %d\n” exit-code) (ent2-log “Proc: %s\n” proc) (ent2-log “[%s] End task\n” (ent2-time-iso-format)) (setq ent2-shell-result exit-code)))
;; -------------------------------------------------------------------- ;; Optional: a tiny helper that creates the log buffer once (defun ent2-log (fmt &rest args) """Format and append a string to `ent2-log-buffer’.
FMT may contain `%s’ or `%d’ placeholders – see `format’. The output is inserted at the end of the buffer, preserving the current point position." (with-current-buffer (get-buffer-create ent2-log-buffer) (goto-char (point-max)) (insert (apply #’format fmt args))))
(defun ent2-time-iso-format () "Return the current time as an ISO‑8601 string, e.g. "2023‑03‑30T14:23:07+00:00"." (format-time-string “%Y-%m-%dT%H:%M:%SZ” (current-time)))
;;; ent2-shell.el ends here “`
### Key Refactor Points
| Refactor | Reason |
|---|---|
| **Command passing** | `make-process` now receives `(list “sh” “-c” command)` so the entire command string is handed to the shell. No `split-string` is used. |
| **Numeric exit status** | `process-exit-status` is consulted in the sentinel, so `ent2-shell-result` holds an integer. |
| **Error handling** | `condition-case` catches any error from `make-process` and logs it via `ent2-log`. |
| **Return value** | The process object is now returned, enabling programmatic control. |
| **Buffer safety** | `ent2-log-buffer` defaults to `*ent2-Log*` and is created lazily. |
| **Lexical binding** | `declare (lexical-binding t)` (implied by the file header) protects against accidental global variable leakage. |
| **Documentation** | Expanded docstring and added commentary for clarity. |
—
## Recommendations & Next Steps
| Topic | Suggested Improvement |
|---|---|
| **Testing** | Write a test that runs a simple command (`echo hello`) and asserts that `ent2-shell-result` becomes `0` and that the output appears in `ent2-log-buffer`. Use `ert` with `with-temp-buffer` and `let` to capture the process object. |
| **Customization** | Expose a `defcustom` for the shell binary (`ent2-shell-binary`), defaulting to `”sh”`, so users on non‑POSIX systems can set `”powershell”` or `”cmd”`. |
| **Signal Handling** | Add an optional `:on-exit` callback that receives the exit code; useful for scripts that must react when the process finishes. |
| **Process Filter** | If callers need to react to output in real time, provide a `:filter` keyword and expose it to `make-process`. |
| **Compatibility** | Ensure `make-process` options used are supported on the minimum Emacs version targeted (currently 27+). If older versions are required, fallback to `start-process` with a custom filter. |
| **Cleanup** | Periodically purge the `ent2-log-buffer` or provide a command to clear it, to avoid unlimited growth. |
| **Performance** | The refactor removes `split-string` overhead and guarantees correct parsing; the sentinel now uses `process-exit-status`, which is O(1). Overall performance impact is negligible but correctness is greatly improved. |
By adopting these changes, the shell runner becomes **robust**, **documented**, and **extensible** while preserving backward compatibility for existing callers that rely on `ent2-shell-result`.