;;; nix-instantiate.el -- run nix commands in Emacs -*- lexical-binding: t -*-

;; Author: Matthew Bauer <mjbauer95@gmail.com>
;; Homepage: https://github.com/NixOS/nix-mode
;; Keywords: nix
;; Version: 1.4.0

;; This file is NOT part of GNU Emacs.

;;; Commentary:

;;; Code:

(require 'nix)
(require 'json)

(defun nix-instantiate--parsed (drv)
  "Get the parsed version of the .drv file.
DRV file to load from."
  (let ((stdout (generate-new-buffer "nix show-derivation"))
	result)
    (call-process nix-executable nil (list stdout nil) nil
		  "show-derivation" drv)
    (setq result
	  (cdar (with-current-buffer stdout
		  (when (eq (buffer-size) 0)
		    (error "Nix’s show-derivation %s failed to produce any output"
			   drv))
		  (goto-char (point-min))
		  (json-read))))
    (kill-buffer stdout)
    result))

(defun nix-instantiate (nix-file &optional attribute parse)
  "Run nix-instantiate on a Nix expression.
NIX-FILE the file to instantiate.
ATTRIBUTE an attribute of the Nix file to use.
PARSE whether to parse nix-instantiate output."
  (interactive (list (read-file-name "Nix file: ") nil t))

  (let ((stdout (generate-new-buffer "nix-instantiate"))
	result)
    (if attribute
	(call-process nix-instantiate-executable nil (list stdout nil) nil
		      nix-file "-A" attribute)
      (call-process nix-instantiate-executable nil (list stdout nil) nil
		    nix-file))
    (with-current-buffer stdout
      (when (eq (buffer-size) 0)
	(error
	 "Error: nix-instantiate %s failed to produce any output"
	 nix-file))
      (setq result (substring (buffer-string) 0 (- (buffer-size) 1)))
      (when parse
	(setq result (nix-instantiate--parsed result))))
    (kill-buffer stdout)
    result))

(defvar nix-instantiate--running-processes nil)

(defun nix-instantiate--sentinel (prop err proc event)
  "Make a nix-instantiate process.
PROP the prop name of nix-instantiate--running-processes.
ERR the error buffer.
PROC the process that has been run.
EVENT the event that was fired."
  (when (string= event "finished\n")
    (with-current-buffer (process-buffer proc)
      (unless (eq (buffer-size) 0)
	(let ((drv (nix-instantiate--parsed
		    (substring (buffer-string) 0 (- (buffer-size) 1)))))
	  (dolist
	      (callback (lax-plist-get nix-instantiate--running-processes prop))
	    (funcall callback drv)))))
    (setq nix-instantiate--running-processes
	  (lax-plist-put nix-instantiate--running-processes prop nil)))
  (unless (process-live-p proc)
    (kill-buffer (process-buffer proc))
    (kill-buffer err)))

(defun nix-instantiate-async (callback nix-file &optional attribute)
  "Run nix-instantiate on a Nix expression, asynchronously.
CALLBACK the function to call when instantiate completes.
NIX-FILE the file to instantiate
ATTRIBUTE an attribute of the Nix file to use."
  (setq nix-file (expand-file-name nix-file))
  (let* ((prop (if attribute
		   (expand-file-name attribute nix-file) nix-file))
	 (data (lax-plist-get nix-instantiate--running-processes prop))
	 (stdout (generate-new-buffer "nix-instantiate"))
	 (stderr (generate-new-buffer "nix-instantiate error")))
    (setq nix-instantiate--running-processes
	  (lax-plist-put nix-instantiate--running-processes
			 prop (cons callback data)))
    (make-process
     :name "nix-instantiate"
     :buffer stdout
     :command (append (list nix-instantiate-executable nix-file)
		      (when attribute (list "-A" attribute)))
     :noquery t
     :sentinel (apply-partially 'nix-instantiate--sentinel prop stderr)
     :stderr stderr)))

(provide 'nix-instantiate)
;;; nix-instantiate.el ends here
