Skip to content

Instantly share code, notes, and snippets.

@cbilson
Created February 6, 2018 03:27
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save cbilson/ae0d90d163be4d769f8a15ddb58292bc to your computer and use it in GitHub Desktop.
Save cbilson/ae0d90d163be4d769f8a15ddb58292bc to your computer and use it in GitHub Desktop.
Rough draft: execute poweshell from emacs src blocks
;;; ob-powershell.el --- org-babel functions for powershell evaluation
;; Authors: Chris Bilson
;; Keywords: literate programming, reproducible research
;; Homepage: http://orgmode.org
;;; Commentary:
;; Org-Babel support for evaluating powershell source code.
;;; Code:
(require 'ob)
(require 'ob-core)
(eval-when-compile (require 'cl))
(defvar org-babel-tangle-lang-exts)
(add-to-list 'org-babel-tangle-lang-exts '("poweshell" . "ps1"))
(defvar org-babel-default-header-args:powershell '())
(defun org-babel-expand-body:powershell (body params)
body)
(defun org-babel-execute:powershell (body params)
"Execute a block of Powershell code with Babel.
This function is called by `org-babel-execute-src-block'."
(let* ((in-file (org-babel-temp-file "powershell-" ".ps1"))
(cmdline (or (cdr (assq :cmdline params))
"-NoLogo -NonInteractive"))
(cmd (or (cdr (assq :cmd params))
"powershell")))
(with-temp-file in-file
(insert (org-babel-expand-body:powershell body params)))
(message "cmd: %s" cmd)
(message "cmdline: %s" cmdline)
(message "in-file: %s" in-file)
(message "body: %s" (org-babel-expand-body:powershell body params))
(org-babel-eval
(concat cmd " " cmdline
" -File " (org-babel-process-file-name in-file))
"")))
;; TODO: I think I *can* support sessions in powershell and really want to...
(defun org-babel-prep-session:powershell (session params)
"Prepare SESSION according to the header arguments in PARAMS."
(error "Sessions are not supported for Powershell"))
(defun org-babel-variable-assignments:powershell (params)
"Return list of powershell statements assigning the block's variables."
(mapcar
(lambda (pair)
(org-babel-powershell--var-to-powershell (cdr pair) (car pair)))
(mapcar #'cdr (org-babel--get-vars params))))
;; helper functions
(defvar org-babel-powershell--lvl 0)
(defun org-babel-powershell--var-to-powershell (var &optional varn)
"Convert an elisp value to a powershell variable.
The elisp value, VAR, is converted to a string of powershell source code
specifying a var of the same value."
(if varn
(let ((org-babel-powershell--lvl 0) (lvar (listp var)) prefix)
(concat "$" (symbol-name varn) "=" (when lvar "\n")
(org-babel-powershell--var-to-powershell var)
";\n"))
(let ((prefix (make-string (* 2 org-babel-powershell--lvl) ?\ )))
(concat prefix
(if (listp var)
(let ((org-babel-powershell--lvl (1+ org-babel-powershell--lvl)))
(concat "[\n"
(mapconcat #'org-babel-powershell--var-to-powershell var "")
prefix "]"))
(format "${%s}" var))
(unless (zerop org-babel-powershell--lvl) ",\n")))))
(defvar org-babel-powershell-buffers '(:default . nil))
(defun org-babel-powershell-initiate-session (&optional session params)
"Return nil because sessions are not supported by powershell."
nil)
(defvar org-babel-powershell-preface nil)
(defun org-babel-powershell-evaluate (session ibody &optional result-type result-params)
"Pass BODY to the Powershell process in SESSION.
If RESULT-TYPE equals 'output then return a list of the outputs
of the statements in BODY, if RESULT-TYPE equals 'value then
return the value of the last statement in BODY, as elisp."
(when session (error "Sessions are not supported for Powershell"))
(let* ((body (concat org-babel-powershell-preface ibody))
(out-file (org-babel-temp-file "powershell-"))
(tmp-babel-file (org-babel-process-file-name
out-file 'noquote))
(in-file (org-babel-temp-file "powershell-"))
(command (format "%s -File '%s'" org-babel-powershell-command in-file)))
(with-temp-file in-file
(insert body))
(message "body: %s" body)
(message "in-file: %s" in-file)
(message "out-file: %s" out-file)
(message "command: %s" command)
(let ((results
(case result-type
(output
(with-temp-file out-file
(insert
(org-babel-eval org-babel-powershell-command body))
(buffer-string)))
(value
(message "evaliing now...")
(org-babel-eval command body)))))
(when results
(org-babel-result-cond result-params
(org-babel-eval-read-file out-file)
(org-babel-import-elisp-from-file out-file '(16)))))))
(provide 'ob-powershell)
;;; ob-powershell.el ends here
ls C:\Users

(org-babel-do-load-languages
 'org-babel-load-languages
 '((C . t)
   (clojure . t)
   (csharp . t)
   (ditaa . t)
   (dot . t)
   ;; (ipython . t)
   (http . t)
   (plantuml . t)
   (powershell . t)
   (python . t)
   (ruby . t)
   ;; (sh . t)
   ))
@fpvmorais
Copy link

fpvmorais commented Oct 2, 2019

Hi,

I'm using your code and added support for passing variables. I'm sure it's not perfect, my knowledge of elisp is not the greatest but it works for my use case. So have a go and tell me something.

Thanks

;;; ob-powershell.el --- org-babel functions for powershell evaluation

;; Authors: Chris Bilson
;; Keywords: literate programming, reproducible research
;; Homepage: http://orgmode.org

;;; Commentary:

;; Org-Babel support for evaluating powershell source code.


;;; Code:
(require 'ob)
(require 'ob-core)
(eval-when-compile (require 'cl-lib))

(declare-function org-babel--get-vars "ob" (params))

(defvar org-babel-tangle-lang-exts)
(add-to-list 'org-babel-tangle-lang-exts '("poweshell" . "ps1"))

(defvar org-babel-default-header-args:powershell '())

(defcustom org-babel-powershell-hline-to "None"
  "Replace hlines in incoming tables with this when translating to python."
  :group 'org-babel
  :version "24.4"
  :package-version '(Org . "8.0")
  :type 'string)

(defun org-babel-expand-body:powershell (body params)
  "Expand BODY according to PARAMS, return the expanded body."
  (let ((vars (org-babel--get-vars params))
	(print-level nil)
	(print-length nil))
    (if (null vars) (concat body "\n")
      (format "%s\n%s\n"
	      (mapconcat
	       (lambda (var)
		 (format "$%s = %s" (car var) (org-babel-powershell-var-to-powershell (cdr var))))
	       vars "\n")
	      body))))

(defun org-babel-powershell-var-to-powershell (var)
  "Convert an elisp value to a powershell variable.
Convert an elisp value, VAR, into a string of powershell source code
specifying a variable of the same value."
  (if (listp var)
      (concat "@(" (mapconcat #'org-babel-powershell-var-to-powershell var ", ") ")")
    (if (eq var 'hline)
	org-babel-powershell-hline-to
      (format
       (if (and (stringp var) (string-match "[\n\r]" var)) "\"\"%S\"\"" "%S")
       (if (stringp var) (substring-no-properties var) var)))))


(defun org-babel-execute:powershell (body params)
  "Execute a block of Powershell code with Babel.
This function is called by `org-babel-execute-src-block'."
  (let* ((in-file (org-babel-temp-file "powershell-" ".ps1"))
         (cmdline (or (cdr (assq :cmdline params))
                      "-NoLogo -NonInteractive"))
         (cmd (or (cdr (assq :cmd params))
                  "powershell")))
    (with-temp-file in-file
      (insert (org-babel-expand-body:powershell body params)))
    (message "cmd: %s" cmd)
    (message "cmdline: %s" cmdline)
    (message "in-file: %s" in-file)
    (message "body: %s" (org-babel-expand-body:powershell body params))
    (org-babel-eval
     (concat cmd " " cmdline
             " -File " (org-babel-process-file-name in-file))
     "")))

;; TODO: I think I *can* support sessions in powershell and really want to...
(defun org-babel-prep-session:powershell (session params)
  "Prepare SESSION according to the header arguments in PARAMS."
  (error "Sessions are not supported for Powershell"))

(defun org-babel-variable-assignments:powershell (params)
  "Return list of powershell statements assigning the block's variables."
  (mapcar
   (lambda (pair)
     (org-babel-powershell--var-to-powershell (cdr pair) (car pair)))
   (mapcar #'cdr (org-babel--get-vars params))))

;; helper functions

(defvar org-babel-powershell--lvl 0)

(defun org-babel-powershell--var-to-powershell (var &optional varn)
  "Convert an elisp value to a powershell variable.
The elisp value, VAR, is converted to a string of powershell source code
specifying a var of the same value."
  (if varn
      (let ((org-babel-powershell--lvl 0) (lvar (listp var)) prefix)
        (concat "$" (symbol-name varn) "=" (when lvar "\n")
                (org-babel-powershell--var-to-powershell var)
                ";\n"))
    (let ((prefix (make-string (* 2 org-babel-powershell--lvl) ?\ )))
      (concat prefix
              (if (listp var)
                  (let ((org-babel-powershell--lvl (1+ org-babel-powershell--lvl)))
                    (concat "[\n"
                            (mapconcat #'org-babel-powershell--var-to-powershell var "")
                            prefix "]"))
                (format "${%s}" var))
              (unless (zerop org-babel-powershell--lvl) ",\n")))))

(defvar org-babel-powershell-buffers '(:default . nil))

(defun org-babel-powershell-initiate-session (&optional session params)
  "Return nil because sessions are not supported by powershell."
  nil)

(defvar org-babel-powershell-preface nil)

(defun org-babel-powershell-evaluate (session ibody &optional result-type result-params)
  "Pass BODY to the Powershell process in SESSION.
If RESULT-TYPE equals 'output then return a list of the outputs
of the statements in BODY, if RESULT-TYPE equals 'value then
return the value of the last statement in BODY, as elisp."
  (when session (error "Sessions are not supported for Powershell"))
  (let* ((body (concat org-babel-powershell-preface ibody))
         (out-file (org-babel-temp-file "powershell-"))
         (tmp-babel-file (org-babel-process-file-name
                          out-file 'noquote))
         (in-file (org-babel-temp-file "powershell-"))
         (command (format "%s -File '%s'" org-babel-powershell-command in-file)))

    (with-temp-file in-file
      (insert body))

    (message "body: %s" body)
    (message "in-file: %s" in-file)
    (message "out-file: %s" out-file)
    (message "command: %s" command)

    (let ((results
           (case result-type
             (output
              (with-temp-file out-file
                (insert
                 (org-babel-eval org-babel-powershell-command body))
                (buffer-string)))
             (value
              (message "evaliing now...")
              (org-babel-eval command body)))))
      (when results
        (org-babel-result-cond result-params
          (org-babel-eval-read-file out-file)
          (org-babel-import-elisp-from-file out-file '(16)))))))

(provide 'ob-powershell)
;;; ob-powershell.el ends here

@priyadarshan
Copy link

priyadarshan commented Oct 22, 2019

I came here thanks to Babel with PowerShell on Reddit. I can confirm it works as intended (although perhaps a bit slow). Thank you for ob-powershell.el. You could consider adding it to Melpa, I am sure it would be useful to many,.

@cbilson
Copy link
Author

cbilson commented Jan 18, 2021

Not sure why I only now found your comments ... I actually came from the same reddit link :-)

I stopped using this a while ago because I got sick of emacs hanging on Windows and how much of a tax you pay using emacs on NTFS (things like magit are almost impossible to even have installed in emacs when you have large git repositories, for example.) I started using emacs from WSL and didn't bother trying to invoke powershell from it (you can sort of invoke Windows powershell from inside WSL, but I didn't really need to do this.)

Anyway, I switched back to using Windows for emacs again a few months ago: I found that if I build emacs myself and avoid some packages I would otherwise like to use (like magit) it ultimately doesn't hang and works better than through WSL most of the time, for me.

I just started using my version of the above gist a few weeks ago (I want to run dotnet script ... and it seemed like the easiest way to run it). I'll try with the changes you suggested, @fpvmorais. Thanks!

I also saw there is an ob-pwsh package on github, but wasn't really able to get that to work. This is kind of a yak-shave for me, so not committing lots of cycles to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment