;;; lc-org.el --- LeetCode CLI integration for org source blocks -*- lexical-binding: t; -*- ;; Usage: ;; In an org source block: ;; #+BEGIN_SRC python :lc-problem two-sum ;; class Solution: ;; def twoSum(self, nums, target): ;; ... ;; #+END_SRC ;; ;; M-x lc-org-submit — submit the block's code ;; M-x lc-org-run — run with test input ;; M-x lc-org-status — check last submission ;; ;; Header args: ;; :lc-problem SLUG — problem number or slug (required) ;; :lc-lang LANG — language (default: python3) ;; :lc-input INPUT — test input for `lc run` (defvar lc-org-bin "lc" "Path to the `lc` CLI binary. Set this if `lc` is not on PATH, e.g. \"uv run lc\" or \"/home/you/.local/bin/lc\".") (defvar lc-org-default-lang "python3" "Default language when :lc-lang is not specified in the source block.") (defun lc-org--block-params () "Extract lc parameters and code from the current org source block. Returns a plist (:problem :lang :input :code) or signals an error." (let* ((info (org-babel-get-src-block-info t)) (lang (nth 0 info)) (body (nth 1 info)) (params (nth 2 info)) (problem (cdr (assq :lc-problem params))) (lc-lang (or (cdr (assq :lc-lang params)) (cond ((string= lang "python") "python3") ((string= lang "python3") "python3") ((string= lang "cpp") "cpp") ((string= lang "c++") "cpp") ((string= lang "c") "c") ((string= lang "java") "java") ((string= lang "javascript") "javascript") ((string= lang "typescript") "typescript") ((string= lang "rust") "rust") ((string= lang "go") "golang") ((string= lang "golang") "golang") (t lc-org-default-lang)))) (input (cdr (assq :lc-input params)))) (unless problem (user-error "No :lc-problem header arg on this source block")) (list :problem (format "%s" problem) :lang lc-lang :input input :code body))) (defun lc-org--run-command (cmd) "Run CMD in a compilation buffer named *lc*." (let ((buf (get-buffer-create "*lc*"))) (with-current-buffer buf (read-only-mode -1) (erase-buffer) (insert "$ " cmd "\n\n")) (make-process :name "lc" :buffer buf :command (list shell-file-name shell-command-switch cmd) :sentinel (lambda (proc _event) (when (memq (process-status proc) '(exit signal)) (with-current-buffer (process-buffer proc) (goto-char (point-max)) (insert "\n--- finished (exit " (number-to-string (process-exit-code proc)) ") ---\n") (read-only-mode 1)) (display-buffer (process-buffer proc))))) (display-buffer buf))) (defvar lc-org-mode-map (let ((map (make-sparse-keymap)) (prefix (make-sparse-keymap))) (define-key prefix (kbd "s") #'lc-org-submit) (define-key prefix (kbd "r") #'lc-org-run) (define-key prefix (kbd "t") #'lc-org-status) (define-key prefix (kbd "p") #'lc-org-show-problem) (define-key prefix (kbd "d") #'lc-org-daily) (define-key map (kbd "C-c l") prefix) map) "Keymap for `lc-org-mode'.") ;;;###autoload (define-minor-mode lc-org-mode "Minor mode for LeetCode CLI integration in org source blocks. Keybindings under C-c l: s submit — submit current block r run — run with test input t status — check submission status p problem — show problem description d daily — today's daily challenge" :lighter " LC" :keymap lc-org-mode-map) ;;;###autoload (defun lc-org-submit () "Submit the current org source block to LeetCode." (interactive) (let* ((params (lc-org--block-params)) (problem (plist-get params :problem)) (lang (plist-get params :lang)) (code (plist-get params :code)) (tmpfile (make-temp-file "lc-org-" nil (concat "." (cond ((string= lang "python3") "py") ((string= lang "cpp") "cpp") ((string= lang "c") "c") ((string= lang "java") "java") ((string= lang "javascript") "js") ((string= lang "typescript") "ts") ((string= lang "rust") "rs") ((string= lang "golang") "go") (t "txt")))))) (with-temp-file tmpfile (insert code)) (lc-org--run-command (format "%s submit %s --lang %s --file %s" lc-org-bin (shell-quote-argument problem) (shell-quote-argument lang) (shell-quote-argument tmpfile))))) ;;;###autoload (defun lc-org-run () "Run the current org source block against test input. Input is taken from :lc-input header arg, or prompted interactively." (interactive) (let* ((params (lc-org--block-params)) (problem (plist-get params :problem)) (lang (plist-get params :lang)) (code (plist-get params :code)) (input (or (plist-get params :input) (read-string "Test input: "))) (tmpfile (make-temp-file "lc-org-" nil (concat "." (cond ((string= lang "python3") "py") ((string= lang "cpp") "cpp") ((string= lang "c") "c") ((string= lang "java") "java") ((string= lang "javascript") "js") ((string= lang "typescript") "ts") ((string= lang "rust") "rs") ((string= lang "golang") "go") (t "txt")))))) (with-temp-file tmpfile (insert code)) (lc-org--run-command (format "%s run %s --lang %s --file %s --input %s" lc-org-bin (shell-quote-argument problem) (shell-quote-argument lang) (shell-quote-argument tmpfile) (shell-quote-argument input))))) ;;;###autoload (defun lc-org-status (submission-id) "Check the status of a LeetCode submission by SUBMISSION-ID." (interactive "sSubmission ID: ") (lc-org--run-command (format "%s status %s" lc-org-bin (shell-quote-argument submission-id)))) ;;;###autoload (defun lc-org-show-problem () "Show the problem description for the :lc-problem on the current block." (interactive) (let* ((params (lc-org--block-params)) (problem (plist-get params :problem))) (lc-org--run-command (format "%s problems show %s" lc-org-bin (shell-quote-argument problem))))) ;;;###autoload (defun lc-org-daily () "Show today's daily challenge." (interactive) (lc-org--run-command (format "%s problems daily" lc-org-bin))) (provide 'lc-org) ;;; lc-org.el ends here