%s
\n" 616 | (concat "\n" (org-html-close-tag "br" nil info) "\n" 617 | "%s\n")) 618 | (org-export-data subtitle info)) 619 | ""))))) 620 | contents 621 | (format "%s>\n" (nth 1 (assq 'content (plist-get info :html-divs)))) 622 | )) 623 | (tempFile (make-temp-file "canvas-html-export" nil ".html" rawHtml))) 624 | (call-process "juice" nil "*juice-process*" nil "--css" org-canvas-html-css-file tempFile tempFile) 625 | (with-temp-buffer 626 | (insert-file-contents tempFile) 627 | (buffer-string)))) 628 | 629 | (defun org-canvas-html-inner-template (contents info) 630 | "Return body of document string after HTML conversion. 631 | CONTENTS is the transcoded contents string. INFO is a plist 632 | holding export options." 633 | (let* ((rawHtml 634 | (concat 635 | ;; Table of contents. 636 | (let ((depth (plist-get info :with-toc))) 637 | (when depth (org-html-toc depth info))) 638 | ;; Document contents. 639 | contents 640 | ;; Footnotes section. 641 | (org-html-footnote-section info))) 642 | (tempFile (make-temp-file "canvas-html-export" nil ".html" rawHtml))) 643 | (call-process "juice" nil "*juice-process*" nil "--css" org-canvas-html-css-file tempFile tempFile) 644 | (with-temp-buffer 645 | (insert-file-contents tempFile) 646 | (buffer-string)))) 647 | ;; Add the template functions:1 ends here 648 | 649 | ;; [[file:ox-canvashtml.org::*Add the export-to and export-as functions][Add the export-to and export-as functions:1]] 650 | ;;; End-user functions 651 | 652 | ;;;###autoload 653 | (defun org-canvas-html-export-as-html 654 | (&optional async subtreep visible-only body-only ext-plist) 655 | "Export current buffer to an HTML buffer. 656 | 657 | If narrowing is active in the current buffer, only export its 658 | narrowed part. 659 | 660 | If a region is active, export that region. 661 | 662 | A non-nil optional argument ASYNC means the process should happen 663 | asynchronously. The resulting buffer should be accessible 664 | through the `org-export-stack' interface. 665 | 666 | When optional argument SUBTREEP is non-nil, export the sub-tree 667 | at point, extracting information from the headline properties 668 | first. 669 | 670 | When optional argument VISIBLE-ONLY is non-nil, don't export 671 | contents of hidden elements. 672 | 673 | When optional argument BODY-ONLY is non-nil, only write code 674 | between \"\" and \"\" tags. 675 | 676 | EXT-PLIST, when provided, is a property list with external 677 | parameters overriding Org default settings, but still inferior to 678 | file-local settings. 679 | 680 | Export is done in a buffer named \"*Org HTML Export*\", which 681 | will be displayed when `org-export-show-temporary-export-buffer' 682 | is non-nil." 683 | (interactive) 684 | (org-export-to-buffer 'canvas-html "*Org HTML Export*" 685 | async subtreep visible-only body-only ext-plist 686 | (lambda () (set-auto-mode t))) 687 | ;; (save-excursion 688 | ;; (set-buffer (get-buffer "*Org HTML Export*")) 689 | ;; (call-process-region nil nil "python" t t (t nil) nil "-m" "premailer")) 690 | ) 691 | 692 | ;;;###autoload 693 | (defun org-canvas-html-export-to-html 694 | (&optional async subtreep visible-only body-only ext-plist) 695 | "Export current buffer to a HTML file. 696 | 697 | If narrowing is active in the current buffer, only export its 698 | narrowed part. 699 | 700 | If a region is active, export that region. 701 | 702 | A non-nil optional argument ASYNC means the process should happen 703 | asynchronously. The resulting file should be accessible through 704 | the `org-export-stack' interface. 705 | 706 | When optional argument SUBTREEP is non-nil, export the sub-tree 707 | at point, extracting information from the headline properties 708 | first. 709 | 710 | When optional argument VISIBLE-ONLY is non-nil, don't export 711 | contents of hidden elements. 712 | 713 | When optional argument BODY-ONLY is non-nil, only write code 714 | between \"\" and \"\" tags. 715 | 716 | EXT-PLIST, when provided, is a property list with external 717 | parameters overriding Org default settings, but still inferior to 718 | file-local settings. 719 | 720 | Return output file's name." 721 | (interactive) 722 | (let* ((extension (concat 723 | (when (> (length org-html-extension) 0) ".") 724 | (or (plist-get ext-plist :html-extension) 725 | org-html-extension 726 | "html"))) 727 | (file (org-export-output-file-name extension subtreep)) 728 | (org-export-coding-system org-html-coding-system)) 729 | (org-export-to-file 'canvas-html file 730 | async subtreep visible-only body-only ext-plist) 731 | ;; (call-process "juice" nil "*juice-process*" nil file file) 732 | ;;file 733 | )) 734 | ;; Add the export-to and export-as functions:1 ends here 735 | 736 | ;; [[file:ox-canvashtml.org::*Provide the library][Provide the library:1]] 737 | (provide 'ox-canvashtml) 738 | ;; Provide the library:1 ends here 739 | -------------------------------------------------------------------------------- /org-grading.el: -------------------------------------------------------------------------------- 1 | ;;; package -- Summary 2 | 3 | ;;; Commentary: A collection of functions to facilitate grading papers 4 | ;;; and assignments. It is currently somewhat inflexible and assumes a 5 | ;;; very specific workflow; I'd be interested to know whether it's of 6 | ;;; use to anyoneelse. 7 | 8 | ;;; Code: 9 | 10 | ;; require the dependencies 11 | (require 'org) ;; the source of all good! 12 | (require 'org-attach) ;; for attaching files to emails 13 | (require 'cl) ;; may not be necessary anymore in newer Emacsen 14 | (require 'ov) ;; for grade overlays 15 | 16 | 17 | ;; Helper Functions 18 | 19 | ;; I'm using hte namespace `o-g-' for these internal helper functions. 20 | ;; At some liater date should figure out and implement approved best 21 | ;; oractices. 22 | 23 | ;; CSV Parsers 24 | ;; Student information (name, email, etc) is exported from excel or blackboard in the form 25 | ;; of a CSV file. These two functions parse such files 26 | 27 | (defun o-g-parse-csv-file (file) 28 | "Transforms FILE into a list. 29 | Each element of the returned value is itself a list 30 | containing all the elements from one line of the file. 31 | This fn was stolen from somewhere on the web, and assumes 32 | that the file ocntains no header line at the beginning" 33 | (interactive 34 | (list (read-file-name "CSV file: "))) 35 | (let ((buf (find-file-noselect file)) 36 | (result nil)) 37 | (with-current-buffer buf 38 | (goto-char (point-min)) 39 | ;; (let ((header (buffer-substring-no-properties 40 | ;; (line-beginning-position) (line-end-position)))) 41 | ;; (push )) 42 | (while (not (eobp)) 43 | (let ((line (buffer-substring-no-properties 44 | (line-beginning-position) (line-end-position)))) 45 | ;; (let templist (split-string line ",") 46 | ;; ;;(print templist) 47 | ;; ;; (push (cons (car templist) (nth 1 templist) ) result) 48 | ;; ) 49 | (push (cons (nth 0 (split-string line ",")) (nth 1 (split-string line ","))) result) 50 | ) 51 | (forward-line 1))) 52 | (reverse result))) 53 | 54 | (defun o-g-parse-plist-csv-file (file) 55 | "Transforms csv FILE into a list of plists. 56 | Like `parse-csv-file' but each line of the original file is turned 57 | into a plist. Returns a list of plists. Assumes that the first line 58 | of the csv file is a header containing field names. Clumsily coded, 59 | but works." 60 | (interactive 61 | (list (read-file-name "CSV file: "))) 62 | (let ((buf (find-file-noselect file)) 63 | (result nil)) 64 | (with-current-buffer buf 65 | (goto-char (point-min)) 66 | (let ((header-props (split-string (buffer-substring-no-properties 67 | (line-beginning-position) (line-end-position)) ",")) 68 | ) 69 | ;;(message (format "header is: %s" header-props)) ;;(print header) 70 | (while (not (eobp)) 71 | (let ((line (split-string (buffer-substring-no-properties 72 | (line-beginning-position) (line-end-position)) ",")) 73 | (count 0) 74 | (new-plist '())) 75 | ;; ;;(print line) 76 | (while (< count (length line)) 77 | (print (nth count header-props)) 78 | (print (nth count line)) 79 | (setq new-plist (plist-put new-plist (intern (nth count header-props)) 80 | (if (not (equal (nth count line) "false")) 81 | (nth count line) 82 | (message (nth count line)) 83 | ""))) 84 | (setq count (1+ count))) 85 | (push new-plist result) 86 | (forward-line 1)))) 87 | (cdr (reverse result))))) 88 | 89 | ;; Element tree navigation 90 | 91 | (defun o-g-get-parent-headline () 92 | "Acquire the parent headline & return. Used by`org-grading-make-headlines' and `org-grading-attach'" 93 | (save-excursion 94 | (org-mark-subtree) 95 | (re-search-backward "^\\* ") 96 | (nth 4 (org-heading-components)))) 97 | 98 | ;; Minor mode definition. I'm not really using it right now, but it 99 | ;; might be a worthwhile improvement. 100 | (define-minor-mode org-grading-mode 101 | "a mode to get my grading in order" 102 | ;;:keymap (kbd "C-c C-x C-g" . (call-interactively (org-set-property "GRADE"))) 103 | :lighter " Mark" 104 | ) 105 | ;; refers to an obsolete function I can't remember 106 | (add-hook 'org-grading-mode-hook 107 | (lambda () 108 | (add-hook 'org-metareturn-hook 'mwp-insert-grade-template nil 'make-local 109 | ))) 110 | (add-hook 'org-grading-mode-hook 'org-contacts-setup-completion-at-point) 111 | 112 | ;; mail integration. Only tested with mu4e. 113 | (defun o-g-send-subtree-with-attachments () 114 | "org-mime-subtree and HTMLize" 115 | (interactive) 116 | (org-mark-subtree) 117 | (let ((attachments (o-g-attachment-list)) 118 | ;; (subject (mwp-org-get-parent-headline)) 119 | ) 120 | (save-excursion 121 | (org-grading-mime-org-subtree-htmlize attachments)) 122 | ;; (org-mime-send-subtree) 123 | ;; (insert "\nBest,\nMP.\n") 124 | ;; (message-goto-body) 125 | ;; (insert "Hello,\n\nAttached are the comments from your assignment.\n") 126 | ;; (org-mime-htmlize) 127 | ;; (message-goto-to) 128 | ;;(message-send-and-exit) 129 | )) 130 | 131 | 132 | ;; stolen from gnorb, but renamed to avoid conflicts 133 | (defun o-g-attachment-list (&optional id) 134 | "Get a list of files (absolute filenames) attached to the 135 | current heading, or the heading indicated by optional argument ID." 136 | (when (featurep 'org-attach) 137 | (let* ((attach-dir (save-excursion 138 | (when id 139 | (org-id-goto id)) 140 | (org-attach-dir t))) 141 | (files 142 | (mapcar 143 | (lambda (f) 144 | (expand-file-name f attach-dir)) 145 | (org-attach-file-list attach-dir)))) 146 | files))) 147 | 148 | 149 | 150 | ;; MAIN ORG-GRADING UTILITY FUNCTIONS 151 | 152 | ;; attaching files to subtreeds 153 | (defun org-grading-attach () 154 | "Interactively attach a file to a subtree. 155 | 156 | Assumes that the parent headline is the name of a subdirectory, 157 | and that the current headline is the name of a student. Speeds up file choice." 158 | (interactive) 159 | (if (file-exists-p o-g-get-parent-headline ) 160 | (org-attach-attach (read-file-name 161 | (concat "File for student " (nth 4 (org-heading-components)) ":") 162 | (o-g-get-parent-headline) )) 163 | (message "Warning: no such directory %s; not attaching file" o-g-get-parent-headline))) 164 | (defun org-grading-attach ()) 165 | 166 | ;; Used to create grading headlines for each assignment & student 167 | (defun org-grading-make-headings (assignments students) 168 | "Create a set of headlines for grading. 169 | 170 | ASSIGNMENTS is an alist in which the key is the assignment title, 171 | and the value is the grading template. STUDENTS is now assumed to 172 | be a plist, usually generated by `o-g-parse-plist-csv-file', whose 173 | first element is the student name, and whose second is the 174 | student email." 175 | (message "%s" assignments) 176 | (save-excursion 177 | (goto-char (point-max)) 178 | (message "students=%s" students) 179 | (mapcar (lambda (x) 180 | (let ((assignment (car x)) 181 | (template (cdr x))) 182 | (insert (format "\n* %s :ASSIGNMENT:" assignment)) 183 | (let (( afiles (if (file-exists-p assignment) (directory-files assignment nil ) nil))) 184 | (mapcar (lambda (stu) 185 | (let* ((fname (plist-get stu 'First)) 186 | (lname (plist-get stu 'Last)) 187 | (nname (or (unless (equal (plist-get stu 'Nickname) nil) (plist-get stu 'Nickname)) fname)) 188 | (email (plist-get stu 'Email)) 189 | (github (plist-get stu 'github)) 190 | ) 191 | ;;(message "pliste gets:%s %s %s %s" fname lname nname email) 192 | (insert (format "\n** %s %s" nname lname)) 193 | (org-todo 'todo) 194 | (insert template) 195 | (org-set-property "GRADE" "0") 196 | (org-set-property "CHITS" "0") 197 | (org-set-property "NICKNAME" nname) 198 | (org-set-property "FIRSTNAME" fname) 199 | (org-set-property "LASTNAME" lname) 200 | (org-set-property "MAIL_TO" email) 201 | (org-set-property "GITHUB" github) 202 | ;; (org-set-property "MAIL_CC" "matt.price@utoronto.ca") 203 | (org-set-property "MAIL_REPLY" "matt.price@utoronto.ca") 204 | (org-set-property "MAIL_SUBJECT" 205 | (format "Comments on %s Assignment (%s %s)" 206 | (mwp-org-get-parent-headline) nname lname )) 207 | ;; try to attach files, if possible 208 | (let* ((fullnamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" fname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles)) 209 | (nicknamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" nname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles))) 210 | ;;(message "fullnamefiles is: %s" fullnamefiles) 211 | (if afiles 212 | (if fullnamefiles 213 | (dolist (thisfile fullnamefiles) 214 | ;;(message "value of thisfile is: %s" thisfile) 215 | ;;(message "%s %s" (buffer-file-name) thisfile) 216 | ;;(message "value being passed is: %s"(concat (file-name-directory (buffer-file-name)) assignment "/" thisfile) ) 217 | (org-attach-attach (concat (file-name-directory (buffer-file-name)) assignment "/" thisfile) ) 218 | (message "Attached perfect match for %s" fname)) 219 | (dolist (thisfile nicknamefiles) 220 | (if t 221 | (progn 222 | (org-wattach-attach (concat (file-name-directory (buffer-file-name)) assignment "/" thisfile) ) 223 | (message "No perfect match; attached likely match for %s" nname))))) 224 | (message "No files match name of %s" nname) 225 | (message "warning: no directory %s, not attaching anything" assignment))) 226 | ;; (condition-case nil 227 | 228 | ;; (error (message "Unable to attach file belonging to student %s" nname ))) 229 | (save-excursion 230 | (org-mark-subtree) 231 | 232 | (org-cycle nil)) 233 | )) 234 | students)) ) ) 235 | assignments)) 236 | (org-cycle-hide-drawers 'all)) 237 | 238 | 239 | ;; stolen from xah, http://ergoemacs.org/emacs/elisp_read_file_content.html 240 | (defun o-g-read-lines (filePath) 241 | "Return a list of lines of a file at filePath." 242 | (with-temp-buffer 243 | (insert-file-contents filePath) 244 | (split-string (buffer-string) "\n" t))) 245 | 246 | ;; org make headings, but for github assignments 247 | (defun org-grading-make-headings-from-github (assignments students) 248 | "Create a set of headlines for grading. 249 | 250 | ASSIGNMENTS is an alist in which the key is the assignment title, 251 | and the value is itslef a plist with up to three elements. The 252 | first is the assignment base name, the second is a list of files 253 | to attach, and the third is the grading template. STUDENTS is now 254 | assumed to be a plist, usually generated by 255 | `o-g-parse-plist-csv-file'. Relevant field in the plist are 256 | First, Last, Nickname, Email, github. 257 | 258 | The main innovations vis-a-vis `org-grading-make-headings` are 259 | the structure of the the alist, and the means of attachment 260 | " 261 | (message "%s" assignments) 262 | (save-excursion 263 | (goto-char (point-max)) 264 | (message "students=%s" students) 265 | (mapcar (lambda (x) 266 | (let* ((title (car x)) 267 | (v (cdr x)) 268 | (template (plist-get v :template)) 269 | (basename (plist-get v :basename)) 270 | (filestoget (plist-get v :files)) 271 | (prs (if (plist-get v :prs) 272 | (o-g-read-lines (plist-get v :prs)) 273 | nil)) 274 | ) 275 | (insert (format "\n* %s :ASSIGNMENT:" title)) 276 | ;;(let (( afiles (directory-files (concat title ) nil )))) 277 | (mapcar (lambda (stu) 278 | (let* ((fname (plist-get stu 'First)) 279 | (lname (plist-get stu 'Last)) 280 | (nname (or (plist-get stu 'Nickname) fname)) 281 | (email (plist-get stu 'Email)) 282 | (github (plist-get stu 'github)) 283 | (afiles (ignore-errors (directory-files (concat title "/" basename "-" github )))) 284 | 285 | ) 286 | (message "afiles is: %s" afiles ) 287 | ;;(message "pliste gets:%s %s %s %s" fname lname nname email) 288 | (insert (format "\n** %s %s" (if (string= nname "") 289 | fname 290 | nname) lname)) 291 | (org-todo 'todo) 292 | (insert template) 293 | (org-set-property "GRADE" "0") 294 | (org-set-property "CHITS" "0") 295 | (org-set-property "NICKNAME" nname) 296 | (org-set-property "FIRSTNAME" fname) 297 | (org-set-property "LASTNAME" lname) 298 | (org-set-property "MAIL_TO" email) 299 | (org-set-property "GITHUB" github) 300 | (org-set-property "LOCAL_REPO" (concat title "/" basename "-" github "/" )) 301 | (if prs 302 | (mapcar (lambda (url) 303 | (message "inside lambda") 304 | (if (string-match github url) 305 | (progn 306 | (message "string matched") 307 | ;; one thought would be to add all comments PR's to this 308 | ;; but that would ocmplicate the logic for opening the PR URL 309 | ;; automatically 310 | ;; (org-set-property "COMMENTS_PR" 311 | ;; (concat (org-get-entry (point) "COMMENTS_PR") " " url)) 312 | (org-set-property "COMMENTS_PR" url) 313 | (insert (concat "\nPlease see detailed comments in your github repo: " url)) 314 | ))) 315 | prs) 316 | ) 317 | ;; (org-set-property "MAIL_CC" "matt.price@utoronto.ca") 318 | (org-set-property "MAIL_REPLY" "matt.price@utoronto.ca") 319 | (org-set-property "MAIL_SUBJECT" 320 | (format "Comments on %s Assignment (%s %s)" 321 | (mwp-org-get-parent-headline) nname lname )) 322 | ;; try to attach files, if possible 323 | ;; (let* ((fullnamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" fname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles)) 324 | ;; (nicknamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" nname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles))) 325 | ;; ;;(message "fullnamefiles is: %s" fullnamefiles) 326 | ;; (if afiles 327 | ;; (if fullnamefiles 328 | ;; (dolist (thisfile fullnamefiles) 329 | ;; ;;(message "value of thisfile is: %s" thisfile) 330 | ;; ;;(message "%s %s" (buffer-file-name) thisfile) 331 | ;; ;;(message "value being passed is: %s"(concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 332 | ;; (org-attach-attach (concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 333 | ;; (message "Attached perfect match for %s" name)) 334 | ;; (dolist (thisfile nicknamefiles) 335 | ;; (if t 336 | ;; (progn 337 | ;; (org-attach-attach (concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 338 | ;; (message "No perfect match; attached likely match for %s" nname))))) 339 | ;; (message "No files match name of %s" nname))) 340 | ;; (let* ((fullnamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" fname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles)) 341 | ;; (nicknamefiles (remove-if-not (lambda (f) (string-match (concat "\\\(" nname "\\\)\\\([^[:alnum:]]\\\)*" lname) f)) afiles))) 342 | ;; ;;(message "fullnamefiles is: %s" fullnamefiles) 343 | ;; (if afiles 344 | ;; (if fullnamefiles 345 | ;; (dolist (thisfile fullnamefiles) 346 | ;; ;;(message "value of thisfile is: %s" thisfile) 347 | ;; ;;(message "%s %s" (buffer-file-name) thisfile) 348 | ;; ;;(message "value being passed is: %s"(concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 349 | ;; (org-attach-attach (concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 350 | ;; (message "Attached perfect match for %s" name)) 351 | ;; (dolist (thisfile nicknamefiles) 352 | ;; (if t 353 | ;; (progn 354 | ;; (org-attach-attach (concat (file-name-directory (buffer-file-name)) title "/" thisfile) ) 355 | ;; (message "No perfect match; attached likely match for %s" nname))))) 356 | ;; (message "No files match name of %s" nname))) 357 | ;; (condition-case nil 358 | 359 | ;; (error (message "Unable to attach file belonging to student %s" nname ))) 360 | (save-excursion 361 | (org-mark-subtree) 362 | (org-cycle nil)) 363 | ))students) ) ) assignments))) 364 | ;; Mailing functions 365 | 366 | (defun org-grading-mail-all () 367 | (interactive) 368 | "Mail all subtrees marked 'READY' to student recipients." 369 | (message "Mailing all READY subtrees to students") 370 | (org-element-map (org-element-parse-buffer) 'headline 371 | (lambda (item) 372 | ;; (print (nth 0 (org-element-property :todo-keyword item))) 373 | (when (string= (org-element-property :todo-keyword item) "READY") 374 | (save-excursion 375 | (goto-char (org-element-property :begin item)) 376 | ;;(print "sending") 377 | ;;(print item) 378 | (save-excursion 379 | (forward-char) 380 | ;; (save-) 381 | (o-g-send-subtree-with-attachments) 382 | ;; added this line 383 | ;; (if (fboundp 'mu4e-compose-mode) 384 | ;; (mu4e-compose-mode)) 385 | ) 386 | (org-todo "SENT") 387 | )) 388 | ) 389 | ) 390 | (org-cycle-hide-drawers 'all)) 391 | 392 | (defun o-g-send-subtree-with-attachments () 393 | "org-mime-subtree and HTMLize" 394 | (interactive) 395 | ;;(org-mark-subtree) 396 | (message "starting ") 397 | (let ((attachments (mwp-org-attachment-list)) 398 | ;; (subject (mwp-org-get-parent-headline)) 399 | ) 400 | ;;(insert "Hello " (nth 4 org-heading-components) ",\n") 401 | ;;(org-mime-subtree) 402 | ;; (org-mime-send-subtree) 403 | ;; (org-mime-subtree) 404 | (org-grading-mime-org-subtree-htmlize) 405 | ;; (insert "\nBest,\nMP.\n") 406 | ;; (message-goto-body) 407 | ;; (insert "Hello,\n\nAttached are the comments from your assignment.\n") 408 | ;; (message "subject is" ) 409 | ;; (message subject) 410 | ;;(message-to) 411 | ;; (org-mime-htmlize) 412 | ;; (mu4e-compose-mode) 413 | ;; this comes from gnorb 414 | ;; I will reintroduce it if I want to reinstate questions. 415 | ;; (map-y-or-n-p 416 | ;; ;; (lambda (a) (format "Attach %s to outgoing message? " 417 | ;; ;; (file-name-nondirectory a))) 418 | ;; (lambda (a) 419 | ;; (mml-attach-file a (mm-default-file-encoding a) 420 | ;; nil "attachment")) 421 | ;; attachments 422 | ;; '("file" "files" "attach")) 423 | ;; (message "Attachments: %s" attachments) 424 | (dolist (a attachments) (message "Attachment: %s" a) (mml-attach-file a (mm-default-file-encoding a) nil "attachment")) 425 | (message-goto-to) 426 | )) 427 | 428 | 429 | (defun org-grading-mail-all-undone () 430 | (interactive) 431 | "Mail all subtrees marked 'TODO' to student recipients." 432 | (org-element-map (org-element-parse-buffer) 'headline 433 | (lambda (item) 434 | ;; (print (nth 0 (org-element-property :todo-keyword item))) 435 | (when (string= (org-element-property :todo-keyword item) "TODO") 436 | (save-excursion 437 | (goto-char (1+ (org-element-property :begin item)) ) 438 | ;;(print "sending") 439 | ;;(print item) 440 | (save-excursion 441 | (org-grading-send-missing-subtree) 442 | (message-send-and-exit)) 443 | (org-todo "TODO") 444 | )) 445 | ) 446 | )) 447 | 448 | ;; not currently used -- abandoned in favour of a definitions list 449 | (defun org-grading-insert-grade-template () 450 | "simply insert a short grading template after creation of level 2 headline. 451 | I'm actualy not using this right now, but keeping temporarily until I'm sure it won't " 452 | (let ((element (org-element-at-point))) 453 | (save-excursion 454 | (when (and (org-element-type element) 455 | (eq (org-element-property :level element) 2)) 456 | (insert " 457 | | Organization | | 458 | | Clarity of Thesis | | 459 | | Presentation of Evidence | | 460 | | Grammar and Spelling | | 461 | | Style | | 462 | | Citations | | 463 | | Further Comments | | 464 | | Grade | | 465 | 466 | "))))) 467 | 468 | (defun org-grading-send-subtree-with-attachments () 469 | "org-mime-subtree and HTMLize" 470 | (interactive) 471 | (org-mark-subtree) 472 | (let ((attachments (mwp-org-attachment-list)) 473 | (subject (mwp-org-get-parent-headline))) 474 | ;;(insert "Hello " (nth 4 org-heading-components) ",\n") 475 | (org-mime-subtree) 476 | (insert "\nBest,\nMP.\n") 477 | (message-goto-body) 478 | (insert "Hello,\n\nAttached are the comments from your assignment.\n\n") 479 | (insert "At this point I have marked all the papers I know about. If 480 | you have not received a grade for work that you have handed in, 481 | please contact me immediately and we can resolve the situation!.\n\n") 482 | ;; (message "subject is" ) 483 | ;; (message subject) 484 | ;;(message-to) 485 | (org-mime-htmlize) 486 | ;; this comes from gnorb 487 | ;; I will reintroduce it if I want to reinstate questions. 488 | ;; (map-y-or-n-p 489 | ;; ;; (lambda (a) (format "Attach %s to outgoing message? " 490 | ;; ;; (file-name-nondirectory a))) 491 | ;; (lambda (a) 492 | ;; (mml-attach-file a (mm-default-file-encoding a) 493 | ;; nil "attachment")) 494 | ;; attachments 495 | ;; '("file" "files" "attach")) 496 | ;; (message "Attachments: %s" attachments) 497 | (dolist (a attachments) (message "Attachment: %s" a) (mml-attach-file a (mm-default-file-encoding a) nil "attachment")) 498 | (message-goto-to) 499 | )) 500 | 501 | ;; doesn't seem to actually be used... 502 | (defun org-grading-send-missing-subtree () 503 | "org-mime-subtree and HTMLize" 504 | (interactive) 505 | (org-mark-subtree) 506 | (let ((attachments (mwp-org-attachment-list)) 507 | (subject (mwp-org-get-parent-headline))) 508 | ;;(insert "Hello " (nth 4 org-heading-components) ",\n") 509 | (org-mime-subtree) 510 | (insert "\nBest,\nMP.\n") 511 | (message-goto-body) 512 | (insert "Hello,\n\nI have not received a paper from you, and ma sending this email just to let you know.\n\n") 513 | (insert "At this point I have marked all the papers I know about. If 514 | you have not received a grade for work that you have handed in, 515 | please contact me immediately and we can resolve the situation!.\n\n") 516 | (org-mime-htmlize) 517 | ;; this comes from gnorb 518 | ;; I will reintroduce it if I want to reinstate questions. 519 | ;; (map-y-or-n-p 520 | ;; ;; (lambda (a) (format "Attach %s to outgoing message? " 521 | ;; ;; (file-name-nondirectory a))) 522 | ;; (lambda (a) 523 | ;; (mml-attach-file a (mm-default-file-encoding a) 524 | ;; nil "attachment")) 525 | ;; attachments 526 | ;; '("file" "files" "attach")) 527 | ;; (message "Attachments: %s" attachments) 528 | (dolist (a attachments) (message "Attachment: %s" a) (mml-attach-file a (mm-default-file-encoding a) nil "attachment")) 529 | (message-goto-to) 530 | )) 531 | 532 | ;; still imperfect, but good enough for me. 533 | (defun org-grading-overlay-headings () 534 | "Show grades at end of headlines that have a 'GRADE' property." 535 | (interactive) 536 | (require 'ov) 537 | 538 | (org-map-entries 539 | (lambda () 540 | (when (org-entry-get (point) "GRADE") 541 | (ov-clear (- (line-end-position) 1) 542 | (+ 0 (line-end-position))) 543 | (setq ov (make-overlay (- (line-end-position) 1) 544 | (+ 0 (line-end-position)))) 545 | (setq character (buffer-substring (- (line-end-position) 1) (line-end-position))) 546 | (overlay-put 547 | ov 'display 548 | (format "%s GRADE: %s CHITS: %s" character (org-entry-get (point) "GRADE") (org-entry-get (point) "CHITS"))) 549 | (overlay-put ov 'name "grading") 550 | (message "%s" (overlay-get ov "name"))))) 551 | ) 552 | 553 | (defun org-grading-clear-overlays () 554 | "if the overlays become annoying at any point" 555 | (ov-clear) 556 | 557 | ) 558 | 559 | (defun org-grading-set-grade (grade) 560 | "set grade property at point and regenerate overlays" 561 | (interactive "sGrade:") 562 | (org-set-property "GRADE" grade) 563 | (org-grading-clear-overlays) 564 | (org-grading-overlay-headings) ) 565 | 566 | 567 | (defun org-grading-set-all-grades () 568 | "set grade property for all headings on basis of \"- Grade :: \" line. 569 | 570 | Use with caution." 571 | (interactive) 572 | (save-excursion 573 | (goto-char (point-min)) 574 | (while (re-search-forward "- Grade :: \\(.+\\)" nil t ) 575 | (org-set-property "GRADE" (match-string 1)) 576 | ;; (save-excursion 577 | ;; (org-back-to-heading) 578 | ;; (org-set-property) 579 | ;; (org-element-at-point)) 580 | )) 581 | (org-grading-overlay-headings) 582 | 583 | ) 584 | 585 | (defun org-grading-set-all-grades-boolean () 586 | "set grade property for all headings on basis of \"- Grade :: \" line. 587 | 588 | Use with caution." 589 | (interactive) 590 | (save-excursion 591 | (goto-char (point-min)) 592 | (while (re-search-forward "- \\(.*\\)Grade\\(.*\\) :: \\(.+\\)" nil t ) 593 | (let ((grade (match-string 3))) 594 | (if (string-match "pass" grade) 595 | (progn (message grade) 596 | (org-set-property "GRADE" "1")) 597 | )) 598 | 599 | ;;(org-set-property "GRADE" (match-string 1)) 600 | ;; (save-excursion 601 | ;; (org-back-to-heading) 602 | ;; (org-set-property) 603 | ;; (org-element-at-point)) 604 | )) 605 | (org-grading-overlay-headings) 606 | ;;(org-grading-overlay-headings) 607 | 608 | ) 609 | 610 | (defun org-grading-generate-tables () 611 | "Generate a *grade report* buffer with a summary of the graded assignments 612 | Simultaneously write results to results.csv in current directory." 613 | (interactive) 614 | (setq assignments '()) 615 | (setq students '()) 616 | 617 | ;;get assignments 618 | (let ((org-use-tag-inheritance nil)) 619 | (org-map-entries 620 | (lambda () 621 | (add-to-list 'assignments (nth 4 (org-heading-components)) t)) 622 | "ASSIGNMENT")) 623 | 624 | ;; get student names as list of cons cells 625 | (let ((org-use-property-inheritance nil)) 626 | (org-map-entries 627 | (lambda () 628 | (add-to-list 'students (cons (nth 4 (org-heading-components)) '()) t)) 629 | "MAIL_TO={utoronto.ca}")) 630 | ;;loop over entries 631 | ;; this should be improved, returning a plist to be looped over 632 | (dolist (assignment assignments) 633 | (save-excursion 634 | ;; jump to assignment 635 | (org-open-link-from-string (format "[[%s]]" assignment)) 636 | ;; map over entries 637 | (org-map-entries 638 | (lambda () 639 | (let* ((student (car (assoc (nth 4 (org-heading-components)) students)))) 640 | (when student 641 | (setf (cdr (assoc student students)) 642 | (append (cdr (assoc student students)) 643 | (list (org-entry-get (point) "GRADE"))))))) 644 | nil 'tree))) 645 | 646 | (setq gradebook 647 | (append (list (append '("Student") assignments) 648 | 'hline) 649 | students)) 650 | 651 | (write-region (orgtbl-to-csv gradebook nil) nil "results3.csv") 652 | 653 | 654 | ;; I would like to put the gradebook IN the buffer but I can't figure out 655 | ;; a wayt odo it without killing 656 | ;; (org-open-ling-from-string "[[#gradebook]]") 657 | ;;(let ((first-child (car (org-element-contents (org-element-at-point))))) (when (eq ))) 658 | (let ((this-buffer-name (buffer-name))) 659 | (switch-to-buffer-other-window "*grade report*") 660 | (erase-buffer) 661 | (org-mode) 662 | 663 | (insert (orgtbl-to-orgtbl gradebook nil)) 664 | (pop-to-buffer this-buffer-name)) 665 | ;;(pop-to-buffer nil) 666 | ) 667 | 668 | ;; helper function to set grades easily. Unfinished. 669 | (defun org-grading-pass () 670 | "set the current tree to pass" 671 | 672 | (interactive) 673 | (org-set-property "GRADE" "1") 674 | ;;(ov-clear) 675 | (org-grading-overlay-headings) 676 | ) 677 | 678 | (defun org-grading-chit () 679 | "set the current tree to one chit" 680 | 681 | (interactive) 682 | (org-set-property "CHITS" "1") 683 | (ov-clear) 684 | (org-grading-overlay-headings) 685 | ) 686 | 687 | ;; helper functions for github repos 688 | (defun o-g-open-student-repo () 689 | (interactive) 690 | (find-file-other-window (org-entry-get (point) "LOCAL_REPO" ))) 691 | (defun o-g-open-attachment-or-repo () 692 | (interactive) 693 | (let* ((attach-dir (org-attach-dir t)) 694 | (files (org-attach-file-list attach-dir))) 695 | (if (> (length files) 0 ) 696 | (org-attach-open) 697 | (o-g-open-student-repo) 698 | ))) 699 | 700 | 701 | 702 | ;; more helpers 703 | (defun org-grading-mime-org-subtree-htmlize (attachments) 704 | "Create an email buffer of the current subtree. 705 | The buffer will contain both html and in org formats as mime 706 | alternatives. 707 | 708 | The following headline properties can determine the headers. 709 | * subtree heading 710 | :PROPERTIES: 711 | :MAIL_SUBJECT: mail title 712 | :MAIL_TO: person1@gmail.com 713 | :MAIL_CC: person2@gmail.com 714 | :MAIL_BCC: person3@gmail.com 715 | :END: 716 | 717 | The cursor is left in the TO field." 718 | (interactive) 719 | (save-excursion 720 | ;; (funcall org-mime-up-subtree-heading) 721 | (cl-flet ((mp (p) (org-entry-get nil p org-mime-use-property-inheritance))) 722 | (let* ((file (buffer-file-name (current-buffer))) 723 | (subject (or (mp "MAIL_SUBJECT") (nth 4 (org-heading-components)))) 724 | (to (mp "MAIL_TO")) 725 | (cc (mp "MAIL_CC")) 726 | (bcc (mp "MAIL_BCC")) 727 | (addressee (or (mp "NICKNAME") (mp "FIRSTNAME") ) ) 728 | ;; Thanks to Matt Price for improving handling of cc & bcc headers 729 | (other-headers (cond 730 | ((and cc bcc) `((cc . ,cc) (bcc . ,bcc))) 731 | (cc `((cc . ,cc))) 732 | (bcc `((bcc . ,bcc))) 733 | (t nil))) 734 | (subtree-opts (when (fboundp 'org-export--get-subtree-options) 735 | (org-export--get-subtree-options))) 736 | (org-export-show-temporary-export-buffer nil) 737 | (org-major-version (string-to-number 738 | (car (split-string (org-release) "\\.")))) 739 | (org-buf (save-restriction 740 | (org-narrow-to-subtree) 741 | (let ((org-export-preserve-breaks org-mime-preserve-breaks) 742 | ) 743 | (cond 744 | ((= 8 org-major-version) 745 | (org-org-export-as-org 746 | nil t nil 747 | (or org-mime-export-options subtree-opts))) 748 | ((= 9 org-major-version) 749 | (org-org-export-as-org 750 | nil t nil t 751 | (or org-mime-export-options subtree-opts))))))) 752 | (html-buf (save-restriction 753 | (org-narrow-to-subtree) 754 | (org-html-export-as-html 755 | nil t nil t 756 | (or org-mime-export-options subtree-opts)))) 757 | ;; I wrap these bodies in export blocks because in org-mime-compose 758 | ;; they get exported again. This makes each block conditionally 759 | ;; exposed depending on the backend. 760 | (org-body (prog1 761 | (with-current-buffer org-buf 762 | ;; (format "#+BEGIN_EXPORT org\n%s\n#+END_EXPORT" 763 | ;; (buffer-string)) 764 | (buffer-string)) 765 | (kill-buffer org-buf))) 766 | (html-body (prog1 767 | (with-current-buffer html-buf 768 | (format "#+BEGIN_EXPORT html\n%s\n#+END_EXPORT" 769 | (buffer-string)) 770 | ;; (buffer-string) 771 | ) 772 | (kill-buffer html-buf))) 773 | ;; (body (concat org-body "\n" html-body)) 774 | (body org-body)) 775 | (save-restriction 776 | (org-narrow-to-subtree) 777 | (org-grading-mime-compose body file to subject other-headers 778 | (or org-mime-export-options subtree-opts) 779 | addressee)) 780 | (if (eq org-mime-library 'mu4e) 781 | (advice-add 'mu4e~switch-back-to-mu4e-buffer :after 782 | `(lambda () 783 | (switch-to-buffer (get-buffer ,(buffer-name) )) 784 | (advice-remove 'mu4e~switch-back-to-mu4e-buffer "om-temp-advice")) 785 | '((name . "om-temp-advice")))) 786 | (dolist (a attachments) (mml-attach-file a (mm-default-file-encoding a) nil "attachment")) 787 | 788 | (message-goto-to) 789 | (message-send-and-exit) 790 | )))) 791 | 792 | (defun org-grading-mime-compose (body file &optional to subject headers opts addressee) 793 | "Create mail BODY in FILE with TO, SUBJECT, HEADERS and OPTS." 794 | (when org-mime-debug (message "org-mime-compose called => %s %s" file opts)) 795 | (setq body (format "Hello%s, \n\nAttached are the comments from your assignment.\n%s\nBest,\nMP.\n----------\n" (if addressee (concat " " addressee) "") (replace-regexp-in-string "\\`\\(\\*\\)+.*$" "" body))) 796 | (let* ((fmt 'html) 797 | ;; we don't want to convert org file links to html 798 | (org-html-link-org-files-as-html nil) 799 | ;; These are file links in the file that are not images. 800 | (files 801 | (if (fboundp 'org-element-map) 802 | (org-element-map (org-element-parse-buffer) 'link 803 | (lambda (link) 804 | (when (and (string= (org-element-property :type link) "file") 805 | (not (string-match 806 | (cdr (assoc "file" org-html-inline-image-rules)) 807 | (org-element-property :path link)))) 808 | (org-element-property :path link)))) 809 | (message "Warning: org-element-map is not available. File links will not be attached.") 810 | '()))) 811 | (unless (featurep 'message) 812 | (require 'message)) 813 | (cl-case org-mime-library 814 | (mu4e 815 | (mu4e~compose-mail to subject headers nil)) 816 | (t 817 | (message-mail to subject headers nil))) 818 | (message-goto-body) 819 | (cl-labels ((bhook (body fmt) 820 | (let ((hook 'org-mime-pre-html-hook)) 821 | (if (> (eval `(length ,hook)) 0) 822 | (with-temp-buffer 823 | (insert body) 824 | (goto-char (point-min)) 825 | (eval `(run-hooks ',hook)) 826 | (buffer-string)) 827 | body)))) 828 | (let* ((org-link-file-path-type 'absolute) 829 | (org-export-preserve-breaks org-mime-preserve-breaks) 830 | (plain (org-mime--export-string body 'org)) 831 | ;; this makes the html self-containing. 832 | (org-html-htmlize-output-type 'inline-css) 833 | ;; this is an older variable that does not exist in org 9 834 | (org-export-htmlize-output-type 'inline-css) 835 | (html-and-images 836 | (org-mime-replace-images 837 | (org-mime--export-string (bhook body 'html) 'html opts) 838 | file)) 839 | (images (cdr html-and-images)) 840 | (html (org-mime-apply-html-hook (car html-and-images)))) 841 | ;; If there are files that were attached, we should remove the links, 842 | ;; and mark them as attachments. The links don't work in the html file. 843 | (mapc (lambda (f) 844 | (setq html (replace-regexp-in-string 845 | (format "%s" 846 | (regexp-quote f) (regexp-quote f)) 847 | (format "%s (attached)" (file-name-nondirectory f)) 848 | html))) 849 | files) 850 | (insert (org-mime-multipart plain html) 851 | (mapconcat 'identity images "\n")) 852 | ;; Attach any residual files 853 | (mapc (lambda (f) 854 | (when org-mime-debug (message "attaching: %s" f)) 855 | (mml-attach-file f)) 856 | files))))) 857 | 858 | (provide 'org-grading) 859 | ;;; org-grading ends here 860 | -------------------------------------------------------------------------------- /ox-canvashtml.org: -------------------------------------------------------------------------------- 1 | #+PROPERTY: header-args :tangle yes :comments link 2 | * Some background about Quercus CSS 3 | :PROPERTIES: 4 | :header-args: :tangle no 5 | :END: 6 | 7 | Here's a short list of classes & some smaple code to give a bit of a sense of what is possible. 8 | 9 | - there's an ~element toggler~ script that allows elements to toggle display of some other element on click by virtue of ~aria~ attributes. The code looks like this: 10 | 11 | #+begin_quote 12 | aria-label="Toggler toggle list visibility" aria-expanded="true" aria-controls="group_n" 13 | #+end_quote 14 | 15 | Here's some actual (bloated) code: 16 | #+begin_src html :tangle no 17 |