diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | doc/developer-release.txt | 39 | ||||
-rw-r--r-- | doc/rt-liber.texinfo | 99 | ||||
-rw-r--r-- | rt-liber.info | 209 | ||||
-rw-r--r-- | rt-liberation-gnus.el | 12 | ||||
-rw-r--r-- | rt-liberation-multi.el | 2 | ||||
-rw-r--r-- | rt-liberation-report.el | 2 | ||||
-rw-r--r-- | rt-liberation-rest.el | 40 | ||||
-rw-r--r-- | rt-liberation-storage.el | 2 | ||||
-rw-r--r-- | rt-liberation-update.el | 2 | ||||
-rw-r--r-- | rt-liberation.el | 907 |
12 files changed, 876 insertions, 449 deletions
@@ -34,3 +34,5 @@ all: $(TARGET) clean: -rm -f *~ *.elc + +neat: all clean @@ -1,6 +1,13 @@ -New in version 1.0: +New in version ... + + * Support getting REST credentials from auth-source. + + +New in versions 1.0 to 1.31: * Upgrade to GPLv3 (with the kind permission of the people from Best Practical). * Remove the CLI interface. + + * Make sure the manual appears in ELPA. diff --git a/doc/developer-release.txt b/doc/developer-release.txt index e976f0b..e15282c 100644 --- a/doc/developer-release.txt +++ b/doc/developer-release.txt @@ -1,18 +1,41 @@ -*- outline -*- -* Notes for preparing a release for rt-liberation +This is an outline of how to make a release for rt-liberation via GNU +ELPA. +* compile +Check for and correct compile-time errors and warnings. -** Version +* version In order for the ELPA system to trigger a release the version number in the comment header of rt-liberation.el must be incremented. +* Good News +Update the NEWS file to tell all of the people the Good News. -** Info -After each manual update the info file needs to be copied into the -root of the project so that ELPA can pick it up. +* documentation +Update the manual, compile it, and update the copy of the info file in +the root directory so that ELPA can install it. +Compile an HTML version of the manual and update it on the Website: -** ELPA -Updates to the Savannah repository that have been tested can be pushed -to the ELPA repository at externals/rt-liberation. + $ cvs commit -m "update manual" rt-liber.html + + $ cvs commit -m "update website" index.html + +* push +Push these updates to the git repo. + +* tag +Tag the release with the ELPA version number: + + $ git tag -a 2.00 -m "2.00" + +Then push that tag to the VCS: + + $ git push --tags origin "2.00" + +* ELPA +Push the changes to externals/rt-liberation on elpa.git with: + + $ git push elpa elpa:refs/heads/externals/rt-liberation diff --git a/doc/rt-liber.texinfo b/doc/rt-liber.texinfo index b8cb1f5..bc2a095 100644 --- a/doc/rt-liber.texinfo +++ b/doc/rt-liber.texinfo @@ -48,7 +48,8 @@ This is the Manual for the rt-liberation system @menu * Introduction:: Introduction to rt-liberation. -* Installation:: Setup rt-liberation to work on the system. +* Installation:: Install rt-liberation on the system. +* Configuration:: Setup rt-liberation to work on the system. Using rt-liberation * Queries:: Retrieve particular tickets from the server. @@ -117,10 +118,12 @@ operations on the tickets. @chapter Installation @cindex installation -rt-liberation is available via GNU ELPA. +rt-liberation is available via GNU ELPA. Invoke @kbd{M-x +list-packages} and choose to install the rt-liberation package. -If you install rt-liberation manually instead you'll need to tell -Emacs where to find it, and tell Emacs to load the package: +If you install rt-liberation manually, by copying the code to your +machine, instead you'll need to tell Emacs where to find it, and then +tell Emacs to load the package: @lisp (add-to-list 'load-path "/PATH/TO/rt-liberation/") @@ -131,6 +134,11 @@ Emacs where to find it, and tell Emacs to load the package: @end lisp +@c -------------------------------------------------- +@node Configuration +@chapter Configuration +@cindex configuration + rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber file, or similar. @@ -140,6 +148,25 @@ Tell rt-liberation where to find the RT server's REST interface: (setq rt-liber-rest-url "rt.example.org") @end lisp +In order to authenticate with the RT server instance you need to +provide credentials. rt-liberation looks for these in the variables +@var{rt-liber-rest-username} and @var{rt-liber-rest-password}. You can +set these directly: + +@lisp +(setq rt-liber-rest-username "someuser" + rt-liber-rest-password "somepassword") +@end lisp + +You can also leave these values unset (@code{nil}), in which case +rt-liberation will look for the credentials in a Netrc file via the +auth-source library (see: @xref{Top,,, auth, Emacs auth-source}), +under the machine name "rt-liberation": + +@example +machine rt-liberation login someuser password somepassword +@end example + rt-liberation can issue a command to ``take'' a ticket (that is, assign it to yourself). For this the variable @var{rt-liber-username} must be set: @@ -148,9 +175,9 @@ must be set: (setq rt-liber-username "someuser") @end lisp -rt-liberation can also launch a Web browser to visit a ticket. For -that to work the base URL needs to be set in -@var{rt-liber-base-url}. For example: +rt-liberation can launch a Web browser to visit a ticket. For that to +work the base URL needs to be set in @var{rt-liber-base-url}. For +example: (setq rt-liber-base-url "https://rt.foo.org/") @@ -603,10 +630,8 @@ buffers will be created displaying the query results and named @chapter Ticket Viewer @cindex ticket viewer -The ticket viewer is an interface for viewing the contents of a -ticket. It provides font-locking to make reading the contents easier -via @var{rt-liber-viewer-font-lock-keywords} and a number of -key-bindings. +The ticket viewer is an interface for viewing the contents of a ticket +and for sending answers. The ticket viewer provides key-bindings to help compose emails to send to the RT email interface. The key-bindings for composing email @@ -615,34 +640,26 @@ them depends on the email-backend system you have installed into rt-liberation. @file{rt-liberation-gnus.el} provides integration with Gnus, @xref{Gnus Integration}. -Setting @var{rt-liber-jump-to-latest} to `t' will cause the viewer to -automatically scroll to the latest comment in a ticket when that -ticket is visited. By default @var{rt-liber-jump-to-latest} is set to -`nil'. - -When in the ticket viewer buffer, invoking -@dfn{rt-liber-viewer-take-ticket} will ``take'' the ticket. - @table @kbd @item q @kindex q (ticket viewer) -@findex rt-liber-viewer-mode-quit +@findex rt-liber-viewer2-mode-quit Bury the ticket viewer buffer. @item n @kindex n (ticket viewer) -@findex rt-liber-next-section-in-viewer -Move point to the next section in ticket. +@findex rt-liber-viewer2-next-section-in +Move to the next section in ticket. @item N @kindex N (ticket viewer) -@findex rt-liber-jump-to-latest-correspondence -Move point to the newest correspondence section, if any. +@findex rt-liber-viewer2-last-section-in +Move to the last section. @item p @kindex p (ticket viewer) -@findex rt-liber-previous-section-in-viewer +@findex rt-liber-viewer2-previous-section-in Move point to the previous section in ticket. @item V @@ -650,43 +667,15 @@ Move point to the previous section in ticket. @findex rt-liber-viewer-visit-in-browser Visit the current ticket in a Web browser. -@item m -@kindex m (ticket viewer) -@findex rt-liber-viewer-answer -Compose an answer to the current ticket. - @item M @kindex M (ticket viewer) -@findex rt-liber-viewer-answer-this +@findex rt-liber-viewer2-answer Compose an answer to the current ticket. The content section around point will be inserted into the email body and commented out. -@item t -@kindex t (ticket viewer) -@findex rt-liber-viewer-answer-provisionally -Compose a provisional answer to the current ticket. - -@item T -@kindex t (ticket viewer) -@findex rt-liber-viewer-answer-provisionally-this -Compose a provisional answer to the current ticket. The content -section around point will be inserted into the email body and -commented out. - -@item F -@kindex F (ticket viewer) -@findex rt-liber-viewer-answer-verbatim-this -Compose an answer to the current ticket. The content section around -point will be inserted into the email body verbatim. - -@item c -@kindex c (ticket viewer) -@findex rt-liber-viewer-comment -Compose a comment for the current ticket. - @item C @kindex C (ticket viewer) -@findex rt-liber-viewer-comment-this +@findex rt-liber-viewer2-comment Comment on the ticket using the current context @item g diff --git a/rt-liber.info b/rt-liber.info index f712b66..c2df1d8 100644 --- a/rt-liber.info +++ b/rt-liber.info @@ -36,7 +36,8 @@ Foundation, Inc. * Menu: * Introduction:: Introduction to rt-liberation. -* Installation:: Setup rt-liberation to work on the system. +* Installation:: Install rt-liberation on the system. +* Configuration:: Setup rt-liberation to work on the system. Using rt-liberation * Queries:: Retrieve particular tickets from the server. @@ -95,42 +96,64 @@ browsing the resulting tickets, viewing the tickets' contents and performing operations on the tickets. -File: rt-liber.info, Node: Installation, Next: Queries, Prev: Introduction, Up: Top +File: rt-liber.info, Node: Installation, Next: Configuration, Prev: Introduction, Up: Top 2 Installation ************** -rt-liberation is available via GNU ELPA. +rt-liberation is available via GNU ELPA. Invoke 'M-x list-packages' and +choose to install the rt-liberation package. - If you install rt-liberation manually instead you'll need to tell -Emacs where to find it, and tell Emacs to load the package: + If you install rt-liberation manually, by copying the code to your +machine, instead you'll need to tell Emacs where to find it, and then +tell Emacs to load the package: (add-to-list 'load-path "/PATH/TO/rt-liberation/") (require 'rt-liberation) - rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber + +File: rt-liber.info, Node: Configuration, Next: Queries, Prev: Installation, Up: Top + +3 Configuration +*************** + +rt-liberation needs to be configured in your ~/.emacs, an ~/.rt-liber file, or similar. Tell rt-liberation where to find the RT server's REST interface: (setq rt-liber-rest-url "rt.example.org") + In order to authenticate with the RT server instance you need to +provide credentials. rt-liberation looks for these in the variables +RT-LIBER-REST-USERNAME and RT-LIBER-REST-PASSWORD. You can set these +directly: + + (setq rt-liber-rest-username "someuser" + rt-liber-rest-password "somepassword") + + You can also leave these values unset ('nil'), in which case +rt-liberation will look for the credentials in a Netrc file via the +auth-source library (see: *Note (auth)Top::), under the machine name +"rt-liberation": + + machine rt-liberation login someuser password somepassword + rt-liberation can issue a command to "take" a ticket (that is, assign it to yourself). For this the variable RT-LIBER-USERNAME must be set: (setq rt-liber-username "someuser") - rt-liberation can also launch a Web browser to visit a ticket. For -that to work the base URL needs to be set in RT-LIBER-BASE-URL. For -example: + rt-liberation can launch a Web browser to visit a ticket. For that +to work the base URL needs to be set in RT-LIBER-BASE-URL. For example: (setq rt-liber-base-url "https://rt.foo.org/") -File: rt-liber.info, Node: Queries, Next: Ticket Browser, Prev: Installation, Up: Top +File: rt-liber.info, Node: Queries, Next: Ticket Browser, Prev: Configuration, Up: Top -3 Queries +4 Queries ********* A typical RT server is meant to manage a large amount of tickets. Much @@ -148,7 +171,7 @@ RT's TicketSQL language. File: rt-liber.info, Node: Query Compiler, Next: Query Language, Up: Queries -3.1 Query Compiler +4.1 Query Compiler ================== In order to browse and view tickets a list of needs to be requested from @@ -164,7 +187,7 @@ number of TicketSQL tokens. File: rt-liber.info, Node: Query Language, Prev: Query Compiler, Up: Queries -3.2 Query Language +4.2 Query Language ================== rt-liberation's Sexp-based query language covers a portion of the @@ -232,7 +255,7 @@ in function calls: File: rt-liber.info, Node: Ticket Browser, Next: Ticket Viewer, Prev: Queries, Up: Top -4 Ticket Browser +5 Ticket Browser **************** The ticket browser is a special buffer which provides a convenient @@ -324,7 +347,7 @@ string, the following is equivalent: File: rt-liber.info, Node: Ticket Browser Display, Next: Ticket Browser Sorting, Up: Ticket Browser -4.1 Ticket Browser Display +5.1 Ticket Browser Display ========================== The ticket browser displays the tickets in the browser by calling @@ -384,7 +407,7 @@ RT-LIBER-BROWSER-PRIORITY-CUTOFF File: rt-liber.info, Node: Ticket Browser Sorting, Next: Ticket Browser Filtering, Prev: Ticket Browser Display, Up: Ticket Browser -4.2 Ticket Browser Sorting +5.2 Ticket Browser Sorting ========================== The tickets in the browser are displayed by default in reverse @@ -431,7 +454,7 @@ functions to perform comparisons between ticket objects: File: rt-liber.info, Node: Ticket Browser Filtering, Next: Multiple Ticket Browsers, Prev: Ticket Browser Sorting, Up: Ticket Browser -4.3 Ticket Browser Filtering +5.3 Ticket Browser Filtering ============================ The Ticket Browser can also filter out (that is, not display) certain @@ -469,7 +492,7 @@ ticket is to be filtered. File: rt-liber.info, Node: Multiple Ticket Browsers, Prev: Ticket Browser Filtering, Up: Ticket Browser -4.4 Multiple Ticket Browsers +5.4 Multiple Ticket Browsers ============================ It is sometimes useful to rename the ticket browser buffer to something @@ -503,12 +526,11 @@ supervisor*" and "*new tickets*" respectively: File: rt-liber.info, Node: Ticket Viewer, Next: Gnus Integration, Prev: Ticket Browser, Up: Top -5 Ticket Viewer +6 Ticket Viewer *************** -The ticket viewer is an interface for viewing the contents of a ticket. -It provides font-locking to make reading the contents easier via -RT-LIBER-VIEWER-FONT-LOCK-KEYWORDS and a number of key-bindings. +The ticket viewer is an interface for viewing the contents of a ticket +and for sending answers. The ticket viewer provides key-bindings to help compose emails to send to the RT email interface. The key-bindings for composing email @@ -517,21 +539,14 @@ depends on the email-backend system you have installed into rt-liberation. 'rt-liberation-gnus.el' provides integration with Gnus, *Note Gnus Integration::. - Setting RT-LIBER-JUMP-TO-LATEST to 't' will cause the viewer to -automatically scroll to the latest comment in a ticket when that ticket -is visited. By default RT-LIBER-JUMP-TO-LATEST is set to 'nil'. - - When in the ticket viewer buffer, invoking -"rt-liber-viewer-take-ticket" will "take" the ticket. - 'q' Bury the ticket viewer buffer. 'n' - Move point to the next section in ticket. + Move to the next section in ticket. 'N' - Move point to the newest correspondence section, if any. + Move to the last section. 'p' Move point to the previous section in ticket. @@ -539,29 +554,11 @@ is visited. By default RT-LIBER-JUMP-TO-LATEST is set to 'nil'. 'V' Visit the current ticket in a Web browser. -'m' - Compose an answer to the current ticket. - 'M' Compose an answer to the current ticket. The content section around point will be inserted into the email body and commented out. -'t' - Compose a provisional answer to the current ticket. - -'T' - Compose a provisional answer to the current ticket. The content - section around point will be inserted into the email body and - commented out. - -'F' - Compose an answer to the current ticket. The content section - around point will be inserted into the email body verbatim. - -'c' - Compose a comment for the current ticket. - 'C' Comment on the ticket using the current context @@ -580,7 +577,7 @@ is visited. By default RT-LIBER-JUMP-TO-LATEST is set to 'nil'. File: rt-liber.info, Node: Gnus Integration, Next: Tracking Updates, Prev: Ticket Viewer, Up: Top -6 Gnus Integration +7 Gnus Integration ****************** The file 'rt-liberation-gnus.el' implements integration with Gnus for @@ -635,7 +632,7 @@ the Viewer will be able to call into it, *Note Ticket Viewer::. File: rt-liber.info, Node: Tracking Updates, Next: Batch Operations, Prev: Gnus Integration, Up: Top -7 Tracking Updates +8 Tracking Updates ****************** The functions in 'rt-liberation-update.el' help keep up with updates to @@ -664,7 +661,7 @@ watch for updates. For example: File: rt-liber.info, Node: Batch Operations, Next: Local Storage, Prev: Tracking Updates, Up: Top -8 Batch Operations +9 Batch Operations ****************** The extension 'rt-liberation-multi.el' implements performing batch @@ -697,8 +694,8 @@ through all of the marked tickets. File: rt-liber.info, Node: Local Storage, Next: Copying, Prev: Batch Operations, Up: Top -9 Local Storage -*************** +10 Local Storage +**************** 'rt-liberation-storage.el' implements associating arbitrary ancillary data with tickets. The data is stored locally and is not sent to the RT @@ -730,7 +727,7 @@ can be extended to associate any arbitrary data with any ticket. File: rt-liber.info, Node: Copying, Next: The GNU FDL, Prev: Local Storage, Up: Top -10 The GNU General Public License. +11 The GNU General Public License. ********************************** Version 3, 29 June 2007 @@ -1446,7 +1443,7 @@ please read <https://www.gnu.org/licenses/why-not-lgpl.html>. File: rt-liber.info, Node: The GNU FDL, Next: Concept Index, Prev: Copying, Up: Top -11 GNU Free Documentation License +12 GNU Free Documentation License ********************************* Version 1.2, November 2002 @@ -1841,7 +1838,7 @@ File: rt-liber.info, Node: The GNU FDL, Next: Concept Index, Prev: Copying, choose any version ever published (not as a draft) by the Free Software Foundation. -11.1 ADDENDUM: How to use this License for your documents +12.1 ADDENDUM: How to use this License for your documents ========================================================= To use this License in a document you have written, include a copy of @@ -1882,6 +1879,7 @@ Concept Index * Menu: * Batch Operations: Batch Operations. (line 6) +* configuration: Configuration. (line 6) * FDL, GNU Free Documentation License: The GNU FDL. (line 6) * Gnus Integration: Gnus Integration. (line 6) * installation: Installation. (line 6) @@ -1912,7 +1910,7 @@ Function Index * Menu: * revert-buffer: Ticket Browser. (line 48) -* revert-buffer <1>: Ticket Viewer. (line 66) +* revert-buffer <1>: Ticket Viewer. (line 40) * rt-liber-browse-query: Ticket Browser. (line 12) * rt-liber-browser-ancillary-text: Local Storage. (line 22) * rt-liber-browser-assign: Ticket Browser. (line 62) @@ -1925,7 +1923,6 @@ Function Index * rt-liber-browser-resolve: Ticket Browser. (line 65) * rt-liber-browser-take-ticket-at-point: Ticket Browser. (line 71) * rt-liber-display-ticket-at-point: Ticket Browser. (line 45) -* rt-liber-jump-to-latest-correspondence: Ticket Viewer. (line 31) * rt-liber-lex-lessthan-p: Ticket Browser Sorting. (line 15) * rt-liber-mark-ticket-at-point: Batch Operations. (line 18) @@ -1934,27 +1931,23 @@ Function Index * rt-liber-multi-flag-as-spam-and-delete: Batch Operations. (line 30) * rt-liber-multi-set-status-open: Batch Operations. (line 21) * rt-liber-multi-set-status-resolved: Batch Operations. (line 24) -* rt-liber-next-section-in-viewer: Ticket Viewer. (line 28) * rt-liber-next-ticket-in-browser: Ticket Browser. (line 39) -* rt-liber-previous-section-in-viewer: Ticket Viewer. (line 34) * rt-liber-previous-ticket-in-browser: Ticket Browser. (line 42) * rt-liber-time-lessthan-p: Ticket Browser Sorting. (line 30) * rt-liber-update: Tracking Updates. (line 17) -* rt-liber-viewer-answer: Ticket Viewer. (line 40) -* rt-liber-viewer-answer-provisionally: Ticket Viewer. (line 48) -* rt-liber-viewer-answer-provisionally-this: Ticket Viewer. (line 51) -* rt-liber-viewer-answer-this: Ticket Viewer. (line 43) -* rt-liber-viewer-answer-verbatim-this: Ticket Viewer. (line 56) -* rt-liber-viewer-comment: Ticket Viewer. (line 60) -* rt-liber-viewer-comment-this: Ticket Viewer. (line 63) -* rt-liber-viewer-mode-quit: Ticket Viewer. (line 25) -* rt-liber-viewer-show-ticket-browser: Ticket Viewer. (line 75) -* rt-liber-viewer-visit-in-browser: Ticket Viewer. (line 37) +* rt-liber-viewer-show-ticket-browser: Ticket Viewer. (line 49) +* rt-liber-viewer-visit-in-browser: Ticket Viewer. (line 29) +* rt-liber-viewer2-answer: Ticket Viewer. (line 32) +* rt-liber-viewer2-comment: Ticket Viewer. (line 37) +* rt-liber-viewer2-last-section-in: Ticket Viewer. (line 23) +* rt-liber-viewer2-mode-quit: Ticket Viewer. (line 17) +* rt-liber-viewer2-next-section-in: Ticket Viewer. (line 20) +* rt-liber-viewer2-previous-section-in: Ticket Viewer. (line 26) * scroll-down: Ticket Browser. (line 77) -* scroll-down <1>: Ticket Viewer. (line 72) +* scroll-down <1>: Ticket Viewer. (line 46) * scroll-up: Ticket Browser. (line 74) -* scroll-up <1>: Ticket Viewer. (line 69) +* scroll-up <1>: Ticket Viewer. (line 43) File: rt-liber.info, Node: Variable Index, Next: Keybinding Index, Prev: Function Index, Up: Top @@ -1980,63 +1973,59 @@ Keybinding Index * a (ticket browser): Ticket Browser. (line 62) * A (ticket browser): Local Storage. (line 22) -* c (ticket viewer): Ticket Viewer. (line 60) -* C (ticket viewer): Ticket Viewer. (line 63) +* C (ticket viewer): Ticket Viewer. (line 37) * DEL (ticket browser): Ticket Browser. (line 77) -* DEL (ticket viewer): Ticket Viewer. (line 72) -* F (ticket viewer): Ticket Viewer. (line 56) +* DEL (ticket viewer): Ticket Viewer. (line 46) * g (ticket browser): Ticket Browser. (line 48) * G (ticket browser): Ticket Browser. (line 51) -* g (ticket viewer): Ticket Viewer. (line 66) -* h (ticket viewer): Ticket Viewer. (line 75) +* g (ticket viewer): Ticket Viewer. (line 40) +* h (ticket viewer): Ticket Viewer. (line 49) * m (ticket browser): Ticket Browser. (line 80) * M (ticket browser): Batch Operations. (line 18) -* m (ticket viewer): Ticket Viewer. (line 40) -* M (ticket viewer): Ticket Viewer. (line 43) +* M (ticket viewer): Ticket Viewer. (line 32) * n (ticket browser): Ticket Browser. (line 39) -* n (ticket viewer): Ticket Viewer. (line 28) -* N (ticket viewer): Ticket Viewer. (line 31) +* n (ticket viewer): Ticket Viewer. (line 20) +* N (ticket viewer): Ticket Viewer. (line 23) * o (ticket browser): Ticket Browser. (line 68) * p (ticket browser): Ticket Browser. (line 42) * P (ticket browser): Ticket Browser. (line 83) -* p (ticket viewer): Ticket Viewer. (line 34) +* p (ticket viewer): Ticket Viewer. (line 26) * q (ticket browser): Ticket Browser. (line 36) -* q (ticket viewer): Ticket Viewer. (line 25) +* q (ticket viewer): Ticket Viewer. (line 17) * r (ticket browser): Ticket Browser. (line 65) * RET (ticket browser): Ticket Browser. (line 45) * s (ticket browser): Ticket Browser. (line 55) * S (ticket browser): Ticket Browser. (line 58) * SPC (ticket browser): Ticket Browser. (line 74) -* SPC (ticket viewer): Ticket Viewer. (line 69) +* SPC (ticket viewer): Ticket Viewer. (line 43) * t (ticket browser): Ticket Browser. (line 71) -* t (ticket viewer): Ticket Viewer. (line 48) -* t (ticket viewer) <1>: Ticket Viewer. (line 51) -* V (ticket viewer): Ticket Viewer. (line 37) +* V (ticket viewer): Ticket Viewer. (line 29) Tag Table: Node: Top680 -Node: Introduction2956 -Node: Installation3563 -Node: Queries4542 -Node: Query Compiler5104 -Node: Query Language5745 -Node: Ticket Browser8184 -Node: Ticket Browser Display10656 -Node: Ticket Browser Sorting12647 -Node: Ticket Browser Filtering14597 -Node: Multiple Ticket Browsers16140 -Node: Ticket Viewer17309 -Node: Gnus Integration19481 -Node: Tracking Updates21769 -Node: Batch Operations22846 -Node: Local Storage24026 -Node: Copying25104 -Node: The GNU FDL62653 -Node: Concept Index85045 -Node: Function Index86626 -Node: Variable Index89976 -Node: Keybinding Index90352 +Node: Introduction3025 +Node: Installation3632 +Node: Configuration4139 +Node: Queries5451 +Node: Query Compiler6014 +Node: Query Language6655 +Node: Ticket Browser9094 +Node: Ticket Browser Display11566 +Node: Ticket Browser Sorting13557 +Node: Ticket Browser Filtering15507 +Node: Multiple Ticket Browsers17050 +Node: Ticket Viewer18219 +Node: Gnus Integration19478 +Node: Tracking Updates21766 +Node: Batch Operations22843 +Node: Local Storage24023 +Node: Copying25103 +Node: The GNU FDL62652 +Node: Concept Index85044 +Node: Function Index86698 +Node: Variable Index89683 +Node: Keybinding Index90059 End Tag Table diff --git a/rt-liberation-gnus.el b/rt-liberation-gnus.el index eb0b262..789e482 100644 --- a/rt-liberation-gnus.el +++ b/rt-liberation-gnus.el @@ -1,4 +1,4 @@ -;;; rt-liberation-gnus.el --- Gnus integration for rt-liberation +;;; rt-liberation-gnus.el --- Gnus integration for rt-liberation -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2014 Free Software Foundation, Inc. ;; @@ -32,16 +32,6 @@ :prefix "rt-liber-gnus-" :group 'rt-liber-gnus) -(defcustom rt-liber-gnus-comment-address "no comment address set" - "*Email address for adding a comment." - :type 'string - :group 'rt-liber-gnus) - -(defcustom rt-liber-gnus-address "no reply address set" - "*Email address for replying to requestor." - :type 'string - :group 'rt-liber-gnus) - (defcustom rt-liber-gnus-subject-name "no subject name set" "*Subject name to be included in email header." :type 'string diff --git a/rt-liberation-multi.el b/rt-liberation-multi.el index 8e4665a..d4a8602 100644 --- a/rt-liberation-multi.el +++ b/rt-liberation-multi.el @@ -1,4 +1,4 @@ -;;; rt-liberation-multi.el --- Emacs interface to RT +;;; rt-liberation-multi.el --- Emacs interface to RT -*- lexical-binding: t; -*- ;; Copyright (C) 2010, 2014 Free Software Foundation, Inc. ;; diff --git a/rt-liberation-report.el b/rt-liberation-report.el index 11d20c2..a60ae5b 100644 --- a/rt-liberation-report.el +++ b/rt-liberation-report.el @@ -1,4 +1,4 @@ -;;; rt-liberation-report.el --- Emacs interface to RT +;;; rt-liberation-report.el --- Emacs interface to RT -*- lexical-binding: t; -*- ;; Copyright (C) 2015 Free Software Foundation, Inc. ;; diff --git a/rt-liberation-rest.el b/rt-liberation-rest.el index 99f6ce9..a7eb076 100644 --- a/rt-liberation-rest.el +++ b/rt-liberation-rest.el @@ -1,4 +1,4 @@ -;;; rt-liberation-rest.el --- Interface to the RT REST API +;;; rt-liberation-rest.el --- Interface to the RT REST API -*- lexical-binding: t; -*- ;; Copyright (C) 2014-2015 Free Software Foundation, Inc. ;; @@ -31,8 +31,12 @@ (require 'url) (require 'url-util) +(require 'auth-source) +;;; ------------------------------------------------------------------ +;;; variables and constants +;;; ------------------------------------------------------------------ (defvar rt-liber-rest-debug-buffer-name "*rt-liber-rest debug log*" "Buffer name of debug capture.") @@ -45,16 +49,19 @@ (defvar rt-liber-rest-url "" "URL of RT installation.") -(defvar rt-liber-rest-username "" +(defvar rt-liber-rest-username nil "Username of RT account.") -(defvar rt-liber-rest-password "" +(defvar rt-liber-rest-password nil "Password of RT account.") (defvar rt-liber-rest-verbose-p t "If non-nil, be verbose about what's happening.") +;;; ------------------------------------------------------------------ +;;; functions +;;; ------------------------------------------------------------------ (defun rt-liber-rest-write-debug (str) "Write to debug buffer." (when (not (stringp str)) @@ -65,6 +72,21 @@ (goto-char (point-max)) (insert str)))) +(defun rt-liber-rest-auth () + "Try to get the REST credentials." + (if (and (stringp rt-liber-rest-username) + (stringp rt-liber-rest-password) + (< 0 (length rt-liber-rest-username)) + (< 0 (length rt-liber-rest-password))) + t + (message "rt-liber: no REST credentials set, so attempting auth-source") + (let ((auth-source-found-p + (auth-source-search :host "rt-liberation" :require '(:user :secret) :create nil))) + (when (not auth-source-found-p) + (error "no auth-source found for login")) + (setq rt-liber-rest-password (funcall (plist-get (nth 0 auth-source-found-p) :secret)) + rt-liber-rest-username (plist-get (nth 0 auth-source-found-p) :user))))) + (defun rt-liber-rest-search-string (scheme url username password query) "Return the search query string." (let ((user (url-encode-url username)) @@ -124,11 +146,11 @@ str) (setq str (decode-coding-string - (with-current-buffer response - (buffer-substring-no-properties (point-min) - (point-max))) - 'utf-8)) - + (with-current-buffer response + (buffer-substring-no-properties (point-min) + (point-max))) + 'utf-8)) + (message "done") (rt-liber-rest-write-debug (format "outgoing rest call -->\n%s\n<-- incoming\n%s\n" url str)) str))) @@ -138,6 +160,7 @@ (when (or (not (stringp op)) (not (stringp query-string))) (error "bad arguments")) + (rt-liber-rest-auth) (cond ((string= op "ls") (rt-liber-rest-call (rt-liber-rest-search-string rt-liber-rest-scheme @@ -215,6 +238,7 @@ "Run edit comment to set FIELD to VALUE." (message "started edit command at %s..." (current-time-string)) (message "ticket #%s, %s <- %s" ticket-id field value) + (rt-liber-rest-auth) (let ((request-data (format "content=%s: %s" (url-hexify-string field) diff --git a/rt-liberation-storage.el b/rt-liberation-storage.el index df82ce1..f8e6884 100644 --- a/rt-liberation-storage.el +++ b/rt-liberation-storage.el @@ -1,4 +1,4 @@ -;;; rt-liberation-storage.el --- Storage backend for rt-liberation +;;; rt-liberation-storage.el --- Storage backend for rt-liberation -*- lexical-binding: t; -*- ;; Copyright (C) 2010 Free Software Foundation, Inc. ;; diff --git a/rt-liberation-update.el b/rt-liberation-update.el index bdd0f75..17ddba3 100644 --- a/rt-liberation-update.el +++ b/rt-liberation-update.el @@ -1,4 +1,4 @@ -;;; rt-liberation-update.el --- check updated tickets +;;; rt-liberation-update.el --- check updated tickets -*- lexical-binding: t; -*- ;; Copyright (C) 2009 Free Software Foundation, Inc. ;; diff --git a/rt-liberation.el b/rt-liberation.el index f30e048..4ee51c9 100644 --- a/rt-liberation.el +++ b/rt-liberation.el @@ -1,11 +1,11 @@ -;;; rt-liberation.el --- Emacs interface to RT +;;; rt-liberation.el --- Emacs interface to RT -*- lexical-binding: t; -*- ;; Copyright (C) 2008-2020 Free Software Foundation, Inc. ;; Author: Yoni Rabkin <yrk@gnu.org> ;; Authors: Aaron S. Hawley <aaron.s.hawley@gmail.com>, John Sullivan <johnsu01@wjsullivan.net> ;; Maintainer: Yoni Rabkin <yrk@gnu.org> -;; Version: 1.31 +;; Version: 2.01 ;; Keywords: rt, tickets ;; Package-Type: multi ;; url: http://www.nongnu.org/rtliber/ @@ -38,13 +38,16 @@ ;;; Code: - (require 'browse-url) (require 'time-date) (require 'cl-lib) (require 'rt-liberation-rest) +(declare-function rt-liber-get-ancillary-text "rt-liberation-storage.el") +(declare-function rt-liber-ticket-marked-p "rt-liberation-multi.el") +(declare-function rt-liber-set-ancillary-text "rt-liberation-storage.el") + (defgroup rt-liber nil "*rt-liberation, the Emacs interface to RT" @@ -56,6 +59,24 @@ :type 'string :group 'rt-liber) +(defvar rt-liber-viewer-section-header-regexp + "^# [0-9]+/[0-9]+ (id/[0-9]+/total)") + +(defvar rt-liber-viewer-section-field-regexp + "^\\(.+\\): \\(.+\\)$") + +(defconst rt-liber-viewer-font-lock-keywords + (let ((header-regexp (regexp-opt '("id: " "Ticket: " "TimeTaken: " + "Type: " "Field: " "OldValue: " + "NewValue: " "Data: " + "Description: " "Created: " + "Creator: " "Attachments: ") + t))) + (list + (list (concat "^" header-regexp ".*$") 0 + 'font-lock-comment-face))) + "Expressions to font-lock for RT ticket viewer.") + (defvar rt-liber-created-string "Created" "String representation of \"created\" query tag.") @@ -112,7 +133,6 @@ (defvar rt-liber-browser-default-filter-function 'rt-liber-default-filter-f "Default filtering function. - This is a function which accepts the ticket alist as a single argument and returns nil if the ticket needs to be filtered out, dropped or ignored (however you wish to put it.), otherwise the @@ -163,17 +183,6 @@ function returns a truth value.") (t (:background "Black"))) "Face for high priority tickets in browser buffer.") -(defconst rt-liber-viewer-font-lock-keywords - (let ((header-regexp (regexp-opt '("id: " "Ticket: " "TimeTaken: " - "Type: " "Field: " "OldValue: " - "NewValue: " "Data: " - "Description: " "Created: " - "Creator: " "Attachments: ") t))) - (list - (list (concat "^" header-regexp ".*$") 0 - 'font-lock-comment-face))) - "Expressions to font-lock for RT ticket viewer.") - (defvar rt-liber-browser-do-refresh t "When t, run `rt-liber-browser-refresh' otherwise disable it.") @@ -193,7 +202,6 @@ server.") (status . "Status") (priority . "Priority")) "Mapping between field symbols and RT field strings. - The field symbols provide the programmer with a consistent way of referring to RT fields.") @@ -203,14 +211,12 @@ referring to RT fields.") (open . "open") (new . "new")) "Mapping between status symbols and status strings. - The status symbols provide the programmer with a consistent way of referring to certain statuses. The status strings are the server specific strings.") (defvar rt-liber-debug-log-enable nil "If t then enable logging of communication to a buffer. - Careful! This might create a sizable buffer.") (defvar rt-liber-debug-log-buffer-name "*rt-liber debug log*" @@ -218,19 +224,35 @@ Careful! This might create a sizable buffer.") (defvar rt-liber-ticket-local nil "Buffer local storage for a ticket. - This variable is made buffer local for the ticket history") (defvar rt-liber-assoc-browser nil "Browser associated with a ticket history. - This variable is made buffer local for the ticket history") +(defcustom rt-liber-gnus-comment-address "no comment address set" + "*Email address for adding a comment." + :type 'string + :group 'rt-liber-gnus) + +(defcustom rt-liber-gnus-address "no reply address set" + "*Email address for replying to requestor." + :type 'string + :group 'rt-liber-gnus) + +(defvar rt-liber-display-ticket-at-point-f 'rt-liber-viewer2-display-ticket-at-point + "Function for displaying ticket at point in the browser.") + +(defvar rt-liber-viewer2-section-regexp "^Ticket [0-9]+ by " + "Regular expression to match a section in the viewer.") + +(defvar rt-liber-viewer2-recenter 4 + "Argument passed to `recenter' in the viewer.") + ;;; -------------------------------------------------------- ;;; Debug log ;;; -------------------------------------------------------- - (defun rt-liber-debug-log-write (str) "Write STR to debug log." (when (not (stringp str)) @@ -244,7 +266,6 @@ This variable is made buffer local for the ticket history") ;;; -------------------------------------------------------- ;;; TicketSQL compiler ;;; -------------------------------------------------------- - (defun rt-liber-bool-p (sym) "Return t if SYM is a boolean operator, otherwise nil." (member sym '(and or))) @@ -352,7 +373,6 @@ AFTER date after predicate." ;;; -------------------------------------------------------- ;;; Parse Answer ;;; -------------------------------------------------------- - (defun rt-liber-parse-answer (answer-string parser-f) "Operate on ANSWER-STRING with PARSER-F." (with-temp-buffer @@ -367,7 +387,6 @@ AFTER date after predicate." ;;; -------------------------------------------------------- ;;; Ticket list retriever ;;; -------------------------------------------------------- - (put 'rt-liber-no-result-from-query-error 'error-conditions '(error rt-liber-errors rt-liber-no-result-from-query-error)) @@ -435,7 +454,6 @@ AFTER date after predicate." ;;; -------------------------------------------------------- ;;; Ticket utilities ;;; -------------------------------------------------------- - (defun rt-liber-ticket-days-old (ticket-alist) "Return the age of the ticket in positive days." (days-between (format-time-string "%Y-%m-%dT%T%z" (current-time)) @@ -445,26 +463,6 @@ AFTER date after predicate." (<= rt-liber-ticket-old-threshold (rt-liber-ticket-days-old ticket-alist))) - -;;; -------------------------------------------------------- -;;; Ticket viewer -;;; -------------------------------------------------------- - -(defun rt-liber-jump-to-latest-correspondence () - "Move point to the newest correspondence section." - (interactive) - (let (latest-point) - (save-excursion - (goto-char (point-max)) - (when (re-search-backward rt-liber-correspondence-regexp - (point-min) t) - (setq latest-point (point)))) - (if latest-point - (progn - (goto-char latest-point) - (rt-liber-next-section-in-viewer)) - (message "no correspondence found")))) - (defun rt-liber-ticket-id-only (ticket-alist) "Return numerical portion of ticket number from TICKET-ALIST." (if ticket-alist @@ -480,11 +478,6 @@ AFTER date after predicate." nil)) nil)) -(defun rt-liber-get-field-string (field-symbol) - (when (not field-symbol) - (error "null field symbol")) - (cdr (assoc field-symbol rt-liber-field-dictionary))) - (defun rt-liber-ticket-owner-only (ticket-alist) "Return the string value of the ticket owner." (when (not ticket-alist) @@ -492,202 +485,15 @@ AFTER date after predicate." (cdr (assoc (rt-liber-get-field-string 'owner) ticket-alist))) -(defun rt-liber-viewer-visit-in-browser () - "Visit this ticket in the RT Web interface." - (interactive) - (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local))) - (if id - (browse-url - (concat rt-liber-base-url "Ticket/Display.html?id=" id)) - (error "no ticket currently in view")))) - -(defun rt-liber-viewer-mode-quit () - "Bury the ticket viewer." - (interactive) - (bury-buffer)) - -(defun rt-liber-viewer-show-ticket-browser () - "Return to the ticket browser buffer." - (interactive) - (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local))) - (if id - (let ((target-buffer - (if rt-liber-assoc-browser - (buffer-name rt-liber-assoc-browser) - (buffer-name rt-liber-browser-buffer-name)))) - (if target-buffer - (switch-to-buffer target-buffer) - (error "associated ticket browser buffer no longer exists")) - (rt-liber-browser-move-point-to-ticket id)) - (error "no ticket currently in view")))) - -(defun rt-liber-next-section-in-viewer () - "Move point to next section." - (interactive) - (forward-line 1) - (when (not (re-search-forward rt-liber-content-regexp (point-max) t)) - (message "no next section")) - (goto-char (point-at-bol))) - -(defun rt-liber-previous-section-in-viewer () - "Move point to previous section." - (interactive) - (forward-line -1) - (when (not (re-search-backward rt-liber-content-regexp (point-min) t)) - (message "no previous section")) - (goto-char (point-at-bol))) - -(defconst rt-liber-viewer-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "q") 'rt-liber-viewer-mode-quit) - (define-key map (kbd "n") 'rt-liber-next-section-in-viewer) - (define-key map (kbd "N") 'rt-liber-jump-to-latest-correspondence) - (define-key map (kbd "p") 'rt-liber-previous-section-in-viewer) - (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser) - (define-key map (kbd "m") 'rt-liber-viewer-answer) - (define-key map (kbd "M") 'rt-liber-viewer-answer-this) - (define-key map (kbd "t") 'rt-liber-viewer-answer-provisionally) - (define-key map (kbd "T") 'rt-liber-viewer-answer-provisionally-this) - (define-key map (kbd "F") 'rt-liber-viewer-answer-verbatim-this) - (define-key map (kbd "c") 'rt-liber-viewer-comment) - (define-key map (kbd "C") 'rt-liber-viewer-comment-this) - (define-key map (kbd "g") 'revert-buffer) - (define-key map (kbd "SPC") 'scroll-up) - (define-key map (kbd "DEL") 'scroll-down) - (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser) - map) - "Key map for ticket viewer.") - -(define-derived-mode rt-liber-viewer-mode nil - "RT Liberation Viewer" - "Major Mode for viewing RT tickets. -\\{rt-liber-viewer-mode-map}" - (set - (make-local-variable 'font-lock-defaults) - '((rt-liber-viewer-font-lock-keywords))) - (set (make-local-variable 'revert-buffer-function) - #'rt-liber-refresh-ticket-history) - (set (make-local-variable 'buffer-stale-function) - (lambda (&optional _noconfirm) 'slow)) - (when rt-liber-jump-to-latest - (rt-liber-jump-to-latest-correspondence)) - (run-hooks 'rt-liber-viewer-hook)) - -(defun rt-liber-display-ticket-history (ticket-alist &optional assoc-browser) - "Display history for ticket. - -TICKET-ALIST alist of ticket data. -ASSOC-BROWSER if non-nil should be a ticket browser." - (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist)) - (contents (rt-liber-rest-run-ticket-history-base-query ticket-id)) - (new-ticket-buffer (get-buffer-create - (concat "*RT Ticket #" ticket-id "*")))) - (with-current-buffer new-ticket-buffer - (let ((inhibit-read-only t)) - (erase-buffer) - (insert contents) - (goto-char (point-min)) - (rt-liber-viewer-mode) - (set - (make-local-variable 'rt-liber-ticket-local) - ticket-alist) - (when assoc-browser - (set - (make-local-variable 'rt-liber-assoc-browser) - assoc-browser)) - (set-buffer-modified-p nil) - (setq buffer-read-only t))) - (switch-to-buffer new-ticket-buffer))) - -(defun rt-liber-refresh-ticket-history (&optional _ignore-auto _noconfirm) - (interactive) - (if rt-liber-ticket-local - (rt-liber-display-ticket-history rt-liber-ticket-local - rt-liber-assoc-browser) - (error "not viewing a ticket"))) - -;; wrapper functions around specific functions provided by a backend - -(declare-function - rt-liber-gnus-compose-reply-to-requestor - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-reply-to-requestor-to-this - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-reply-to-requestor-verbatim-this - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-provisional - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-provisional-to-this - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-comment - "rt-liberation-gnus.el") -(declare-function - rt-liber-gnus-compose-comment-this - "rt-liberation-gnus.el") - -(defun rt-liber-viewer-answer () - "Answer the ticket." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-reply-to-requestor)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-answer-this () - "Answer the ticket using the current context." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-reply-to-requestor-to-this)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-answer-verbatim-this () - "Answer the ticket using the current context verbatim." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-reply-to-requestor-verbatim-this)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-answer-provisionally () - "Provisionally answer the ticket." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-provisional)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-answer-provisionally-this () - "Provisionally answer the ticket using the current context." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-provisional-to-this)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-comment () - "Comment on the ticket." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-comment)) - (t (error "no function defined")))) - -(defun rt-liber-viewer-comment-this () - "Comment on the ticket using the current context." - (interactive) - (cond ((featurep 'rt-liberation-gnus) - (rt-liber-gnus-compose-comment-this)) - (t (error "no function defined")))) +(defun rt-liber-get-field-string (field-symbol) + (when (not field-symbol) + (error "null field symbol")) + (cdr (assoc field-symbol rt-liber-field-dictionary))) ;;; -------------------------------------------------------- ;;; Ticket browser ;;; -------------------------------------------------------- - -(declare-function - rt-liber-get-ancillary-text - "rt-liberation-storage.el") - ;; accept a ticket-alist object and return an alist mapping ticket ;; properties to format characters for use in `rt-liber-format'. (defun rt-liber-format-function (ticket-alist) @@ -768,9 +574,6 @@ The ticket's priority is compared to the variable '(face font-lock-comment-face))) (newline)) -(declare-function rt-liber-ticket-marked-p - "rt-liberation-multi.el") - (defun rt-liber-ticketlist-browser-redraw (ticketlist &optional query) "Display TICKETLIST. Optionally display QUERY as well." (erase-buffer) @@ -861,6 +664,11 @@ If POINT is nil then called on (point)." (let ((ticket-alist (get-text-property (point) 'rt-ticket))) (rt-liber-display-ticket-history ticket-alist (current-buffer)))) +(defun rt-liber-ticket-at-point () + "Display the contents of the ticket at point." + (interactive) + (funcall rt-liber-display-ticket-at-point-f)) + (defun rt-liber-browser-search (id) "Return point where ticket with ID is displayed or nil." (let ((p nil)) @@ -895,7 +703,6 @@ If POINT is nil then called on (point)." ;;; -------------------------------------------------------- ;;; Ticket browser sorting ;;; -------------------------------------------------------- - (defun rt-liber-lex-lessthan-p (a b field) "Return t if A is lexicographically less than B in FIELD." (let ((field-a (cdr (assoc field a))) @@ -938,7 +745,6 @@ If POINT is nil then called on (point)." ;;; -------------------------------------------------------- ;;; Ticket browser filtering ;;; -------------------------------------------------------- - ;; See the fine manual for example code. (defun rt-liber-default-filter-f (_ticket) @@ -952,7 +758,6 @@ and as such always return t." ;;; -------------------------------------------------------- ;;; Entry points ;;; -------------------------------------------------------- - (defun rt-liber-browse-query (query &optional new) "Run QUERY against the server and launch the browser. @@ -995,7 +800,6 @@ returned as no associated text properties." ;;; -------------------------------------------------------- ;;; Major mode definitions ;;; -------------------------------------------------------- - (defun rt-liber-browser-mode-quit () "Bury the ticket browser." (interactive) @@ -1006,7 +810,7 @@ returned as no associated text properties." (define-key map (kbd "q") 'rt-liber-browser-mode-quit) (define-key map (kbd "n") 'rt-liber-next-ticket-in-browser) (define-key map (kbd "p") 'rt-liber-previous-ticket-in-browser) - (define-key map (kbd "RET") 'rt-liber-display-ticket-at-point) + (define-key map (kbd "RET") 'rt-liber-ticket-at-point) (define-key map (kbd "g") 'revert-buffer) (define-key map (kbd "G") 'rt-liber-browser-refresh-and-return) (define-key map (kbd "a") 'rt-liber-browser-assign) @@ -1081,8 +885,6 @@ returned as no associated text properties." (switch-to-buffer rt-liber-browser-buffer) (setq buffer-read-only t)) -(declare-function rt-liber-set-ancillary-text "rt-liberation-storage.el") - (defun rt-liber-browser-ancillary-text () "Wrapper function around storage backend." (interactive) @@ -1096,7 +898,6 @@ returned as no associated text properties." ;;; -------------------------------------------------------- ;;; Command module ;;; -------------------------------------------------------- - (defun rt-liber-command-get-dictionary-value (sym dic) "Utility function for retrieving alist values." (let ((value (cdr (assoc sym dic)))) @@ -1210,6 +1011,608 @@ returned as no associated text properties." (rt-liber-browser-assign rt-liber-username)) +;;; -------------------------------------------------------- +;;; Viewer +;;; -------------------------------------------------------- +(defun rt-liber-display-ticket-history (ticket-alist &optional assoc-browser) + "Display history for ticket. +TICKET-ALIST alist of ticket data. +ASSOC-BROWSER if non-nil should be a ticket browser." + (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist)) + (contents (rt-liber-rest-run-ticket-history-base-query ticket-id)) + (new-ticket-buffer (get-buffer-create + (concat "*RT Ticket #" ticket-id "*")))) + (with-current-buffer new-ticket-buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (insert contents) + (goto-char (point-min)) + (rt-liber-viewer-mode) + (set + (make-local-variable 'rt-liber-ticket-local) + ticket-alist) + (when assoc-browser + (set + (make-local-variable 'rt-liber-assoc-browser) + assoc-browser)) + (set-buffer-modified-p nil) + (setq buffer-read-only t))) + (switch-to-buffer new-ticket-buffer))) + + +;;; ------------------------------------------------------------------ +;;; viewer mode functions +;;; ------------------------------------------------------------------ +(defun rt-liber-refresh-ticket-history (&optional _ignore-auto _noconfirm) + (interactive) + (if rt-liber-ticket-local + (rt-liber-display-ticket-history rt-liber-ticket-local + rt-liber-assoc-browser) + (error "not viewing a ticket"))) + +(defun rt-liber-jump-to-latest-correspondence () + "Move point to the newest correspondence section." + (interactive) + (let (latest-point) + (save-excursion + (goto-char (point-max)) + (when (re-search-backward rt-liber-correspondence-regexp + (point-min) t) + (setq latest-point (point)))) + (if latest-point + (progn + (goto-char latest-point) + (rt-liber-next-section-in-viewer)) + (message "no correspondence found")))) + +(defun rt-liber-viewer-visit-in-browser () + "Visit this ticket in the RT Web interface." + (interactive) + (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local))) + (if id + (browse-url + (concat rt-liber-base-url "Ticket/Display.html?id=" id)) + (error "no ticket currently in view")))) + +(defun rt-liber-viewer-mode-quit () + "Bury the ticket viewer." + (interactive) + (bury-buffer)) + +(defun rt-liber-viewer-show-ticket-browser () + "Return to the ticket browser buffer." + (interactive) + (let ((id (rt-liber-ticket-id-only rt-liber-ticket-local))) + (if id + (let ((target-buffer + (if rt-liber-assoc-browser + (buffer-name rt-liber-assoc-browser) + (buffer-name rt-liber-browser-buffer-name)))) + (if target-buffer + (switch-to-buffer target-buffer) + (error "associated ticket browser buffer no longer exists")) + (rt-liber-browser-move-point-to-ticket id)) + (error "no ticket currently in view")))) + +(defun rt-liber-next-section-in-viewer () + "Move point to next section." + (interactive) + (forward-line 1) + (when (not (re-search-forward rt-liber-content-regexp (point-max) t)) + (message "no next section")) + (goto-char (point-at-bol))) + +(defun rt-liber-previous-section-in-viewer () + "Move point to previous section." + (interactive) + (forward-line -1) + (when (not (re-search-backward rt-liber-content-regexp (point-min) t)) + (message "no previous section")) + (goto-char (point-at-bol))) + +(defconst rt-liber-viewer-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "q") 'rt-liber-viewer-mode-quit) + (define-key map (kbd "n") 'rt-liber-next-section-in-viewer) + (define-key map (kbd "N") 'rt-liber-jump-to-latest-correspondence) + (define-key map (kbd "p") 'rt-liber-previous-section-in-viewer) + (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser) + (define-key map (kbd "m") 'rt-liber-viewer-answer) + (define-key map (kbd "M") 'rt-liber-viewer-answer-this) + (define-key map (kbd "t") 'rt-liber-viewer-answer-provisionally) + (define-key map (kbd "T") 'rt-liber-viewer-answer-provisionally-this) + (define-key map (kbd "F") 'rt-liber-viewer-answer-verbatim-this) + (define-key map (kbd "c") 'rt-liber-viewer-comment) + (define-key map (kbd "C") 'rt-liber-viewer-comment-this) + (define-key map (kbd "g") 'revert-buffer) + (define-key map (kbd "SPC") 'scroll-up) + (define-key map (kbd "DEL") 'scroll-down) + (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser) + map) + "Key map for ticket viewer.") + +(define-derived-mode rt-liber-viewer-mode nil + "RT Liberation Viewer" + "Major Mode for viewing RT tickets. +\\{rt-liber-viewer-mode-map}" + (set + (make-local-variable 'font-lock-defaults) + '((rt-liber-viewer-font-lock-keywords))) + (set (make-local-variable 'revert-buffer-function) + #'rt-liber-refresh-ticket-history) + (set (make-local-variable 'buffer-stale-function) + (lambda (&optional _noconfirm) 'slow)) + (when rt-liber-jump-to-latest + (rt-liber-jump-to-latest-correspondence)) + (run-hooks 'rt-liber-viewer-hook)) + +;; wrapper functions around specific functions provided by a backend +(declare-function + rt-liber-gnus-compose + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-reply-to-requestor + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-reply-to-requestor-to-this + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-reply-to-requestor-verbatim-this + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-provisional + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-provisional-to-this + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-comment + "rt-liberation-gnus.el") +(declare-function + rt-liber-gnus-compose-comment-this + "rt-liberation-gnus.el") + +(defun rt-liber-viewer-answer () + "Answer the ticket." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-reply-to-requestor)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-answer-this () + "Answer the ticket using the current context." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-reply-to-requestor-to-this)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-answer-verbatim-this () + "Answer the ticket using the current context verbatim." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-reply-to-requestor-verbatim-this)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-answer-provisionally () + "Provisionally answer the ticket." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-provisional)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-answer-provisionally-this () + "Provisionally answer the ticket using the current context." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-provisional-to-this)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-comment () + "Comment on the ticket." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-comment)) + (t (error "no function defined")))) + +(defun rt-liber-viewer-comment-this () + "Comment on the ticket using the current context." + (interactive) + (cond ((featurep 'rt-liberation-gnus) + (rt-liber-gnus-compose-comment-this)) + (t (error "no function defined")))) + + +;;; ------------------------------------------------------------------ +;;; viewer2 +;;; ------------------------------------------------------------------ + +;; Comment: The goal is to eventually break this code away to its own +;; file. + +(defface rt-liber-ticket-emph-face + '((((class color) (background dark)) + (:foreground "gray53")) + (((class color) (background light)) + (:foreground "gray65")) + (((type tty) (class mono)) + (:inverse-video t)) + (t (:background "Blue"))) + "Face for important text.") + +(defconst rt-liber-viewer2-font-lock-keywords + `(("^$" 0 'rt-liber-ticket-subdued-face)) + "Expressions to font-lock for RT ticket viewer.") + + +(defun rt-liber-viewer2-vernacular-plural (time) + "Add an ess as needed." + (if (= time 1) + "" + "s")) + +(defun rt-liber-viewer2-vernacular-date (date) + "Return a vernacular time delta." + (let* ((now (format-time-string "%Y-%m-%dT%T%z" (current-time))) + (days-ago (days-between now date))) + (cond ((= 0 days-ago) + "today") + ((< 0 days-ago 7) + (format "%s day%s ago" days-ago + (rt-liber-viewer2-vernacular-plural days-ago))) + ((< 7 days-ago 30) + (let ((weeks (floor (/ days-ago 7.0)))) + (format "%s week%s ago" + weeks + (rt-liber-viewer2-vernacular-plural weeks)))) + ((< 30 days-ago 365) + (let ((months (floor (/ days-ago 30.0)))) + (format "%s month%s ago" + months + (rt-liber-viewer2-vernacular-plural months)))) + (t (let ((years (floor (/ days-ago 365.0)))) + (format "%s year%s ago" + years + (rt-liber-viewer2-vernacular-plural years))))))) + +(defun rt-liber-viewer2-mode-quit () + "Bury the ticket viewer." + (interactive) + (bury-buffer)) + +(defun rt-liber-viewer-reduce (section-list f acc) + "A Not Invented Here tail-recursive reduce function." + (cond ((null (cdr section-list)) acc) + (t (rt-liber-viewer-reduce (cdr section-list) + f + (append acc (list + (funcall f + (car section-list) + (cadr section-list)))))))) + +;; According to: +;; "https://rt-wiki.bestpractical.com/wiki/REST#Ticket_History_Entry" +;; id: <history-id> +;; Ticket: <ticket-id> +;; TimeTaken: <...> +;; Type: <...> +;; Field: <...> +;; OldValue: <...> +;; NewValue: <...> +;; Data: <...> +;; Description: <...> + +;; Content: <lin1-0> +;; <line-1> +;; ... +;; <line-n> + +;; Creator: <...> +;; Created: <...> +;; Attachments: <...> +(defun rt-liber-viewer-parse-section (start end) + (goto-char start) + (when (not (re-search-forward + rt-liber-viewer-section-header-regexp + end t)) + (error "invalid section")) + (forward-line 2) + (let (section-field-alist + (rt-field-list + '(id Ticket TimeTaken Type Field + OldValue NewValue Data Description + Creator Created))) + ;; definitely error out if any of this doesn't work + (setq section-field-alist + (mapcar + (lambda (field-symbol) + (re-search-forward (format "^%s:" (symbol-name field-symbol)) end nil) + (cons field-symbol (buffer-substring (1+ (point)) (point-at-eol)))) + rt-field-list)) + ;; content + (goto-char start) + (let ((content-start (re-search-forward "^Content: " end nil)) + (content-end (progn + (re-search-forward "^Creator: " end nil) + (point-at-bol)))) + (append section-field-alist + `(,(cons 'Content + (buffer-substring content-start + content-end))))))) + +;; According to: +;; "https://rt-wiki.bestpractical.com/wiki/REST#Ticket_History" is of +;; the form: "# <n>/<n> (id/<history-id>/total)" +(defun rt-liber-viewer-parse-history (ticket-history) + "Parse the string TICKET-HISTORY." + (when (not (stringp ticket-history)) + (error "invalid ticket-history")) + (with-temp-buffer + (insert ticket-history) + (goto-char (point-min)) + ;; find history detail sections and procude a list of section + ;; (start . end) pairs + (let (section-point-list + section-list) + (while (re-search-forward rt-liber-viewer-section-header-regexp (point-max) t) + (setq section-point-list (append section-point-list + (list (point-at-bol))))) + (when (not section-point-list) + (error "no history detail sections found")) + (setq section-point-list (append section-point-list + (list (point-max))) + section-point-list (rt-liber-viewer-reduce section-point-list #'cons nil)) + ;; collect the sections + (setq section-list + (mapcar + (lambda (section-points) + (rt-liber-viewer-parse-section + (car section-points) + (cdr section-points))) + section-point-list)) + section-list))) + +(defun rt-liber-viewer2-get-section-data () + "Return the current section data." + (let ((section (get-text-property (point) 'rt-liberation-section-data))) + (when (not section) + (save-excursion + (rt-liber-viewer2-previous-section-in) + (setq section (get-text-property (point) 'rt-liberation-section-data)))) + section)) + +(defun rt-liber-viewer2-format-content (content) + "Wrangle RT's content format." + (with-temp-buffer + (insert content) + (goto-char (point-min)) + (if (re-search-forward "^This transaction appears to have no content" (point-max) t) + "" ; make no content mean... no content + ;; trim leading blank lines + (save-excursion + (goto-char (point-min)) + (re-search-forward "[[:graph:]]" (point-max) t) + (forward-line -1) + (flush-lines "^[[:space:]]+$" (point-min) (point))) + ;; trim trailing blank lines + (save-excursion + (goto-char (point-max)) + (re-search-backward "[[:graph:]]" (point-min) t) + (forward-line 2) + (flush-lines "^[[:space:]]+$" (point-max) (point))) + ;; Convert the 9 leading whitespaces from RT's comment lines. + (goto-char (point-min)) + (insert " ") + (while (re-search-forward "^ " (point-max) t) + (replace-match " ")) + ;; fill + (let ((paragraph-separate " >[[:space:]]+$")) + (fill-region (point-min) + (point-max))) + ;; finally + (buffer-substring (point-min) + (point-max))))) + +(defun rt-liber-viewer2-clean-content (section) + "Format section content for email." + (with-temp-buffer + (insert (rt-liber-viewer2-format-content + (alist-get 'Content section))) + (goto-char (point-min)) + (while (re-search-forward "^ " (point-max) t) + (replace-match "")) + ;; fill + (let ((paragraph-separate ">[[:space:]]+$")) + (fill-region (point-min) + (point-max))) + ;; finally + (buffer-substring (point-min) + (point-max)))) + +(defun rt-liber-viewer2-display-section (section) + (let ((start (point)) + (ticket-id (alist-get 'Ticket section)) + (creator (alist-get 'Creator section)) + (date (alist-get 'Created section)) + (type (alist-get 'Type section)) + (oldvalue (alist-get 'OldValue section)) + (newvalue (alist-get 'NewValue section)) + (field (alist-get 'Field section)) + (s-content (rt-liber-viewer2-format-content + (alist-get 'Content section)))) + (when (not (or (string= type "CommentEmailRecord") + (string= type "EmailRecord"))) + (insert + (format "Ticket %s by %s, %s (%s) [%s]\n" + ticket-id + creator + (rt-liber-viewer2-vernacular-date date) + date + (format "%s%s%s" + type + (if (< 0 (length oldvalue)) + (concat " " oldvalue) + "") + (if (< 0 (length newvalue)) + (concat "->" newvalue) + "")))) + (add-text-properties start + (point) + `(font-lock-face rt-liber-ticket-emph-face)) + (add-text-properties start + (point) + `(rt-liberation-viewer-header t)) + (add-text-properties start + (point) + `(rt-liberation-section-data ,section)) + (cond ((string= type "Status") + (insert + (format "\n Status change from %s to %s\n\n" oldvalue newvalue))) + ((and (string= type "Set") + (string= field "Owner") + (string= oldvalue "10")) + (insert + (format "\n Ticket assigned\n\n"))) + ;; catch-all + (t + (insert + (format "\n%s\n" s-content))))))) + +(defun rt-liber-viewer2-display-history (contents) + (let ((section-list (rt-liber-viewer-parse-history contents))) + (mapc + (lambda (section) + (rt-liber-viewer2-display-section section)) + section-list))) + +(defun rt-liber-viewer2-display-ticket-at-point () + "Display the contents of the ticket at point." + (interactive) + (let ((ticket-alist (get-text-property (point) 'rt-ticket))) + (rt-liber-viewer2-display-ticket-history ticket-alist (current-buffer)))) + +(defun rt-liber-viewer2-display-ticket-history (ticket-alist &optional assoc-browser) + "Display history for ticket. +TICKET-ALIST alist of ticket data. +ASSOC-BROWSER if non-nil should be a ticket browser." + (let* ((ticket-id (rt-liber-ticket-id-only ticket-alist)) + (contents (rt-liber-rest-run-ticket-history-base-query ticket-id)) + (new-ticket-buffer (get-buffer-create + (concat "*RT (Viewer) Ticket #" ticket-id "*")))) + (with-current-buffer new-ticket-buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (rt-liber-viewer2-display-history contents) + (goto-char (point-min)) + (rt-liber-viewer2-mode) + (set + (make-local-variable 'rt-liber-ticket-local) + ticket-alist) + (when assoc-browser + (set + (make-local-variable 'rt-liber-assoc-browser) + assoc-browser)) + (set-buffer-modified-p nil) + (setq buffer-read-only t))) + (switch-to-buffer new-ticket-buffer))) + +(defun rt-liber-viewer2-refresh-ticket-history (&optional _ignore-auto _noconfirm) + (interactive) + (if rt-liber-ticket-local + (rt-liber-viewer2-display-ticket-history rt-liber-ticket-local + rt-liber-assoc-browser) + (error "not viewing a ticket"))) + +(defun rt-liber-viewer2-next-section-in () + (interactive) + (when (looking-at rt-liber-viewer2-section-regexp) + (goto-char (1+ (point)))) + (let ((next (re-search-forward rt-liber-viewer2-section-regexp + (point-max) + t))) + (cond ((not next) + (message "no next section")) + (t + (recenter rt-liber-viewer2-recenter))) + (goto-char (point-at-bol)))) + +(defun rt-liber-viewer2-last-section-in () + (interactive) + (goto-char (point-max)) + (let ((last (re-search-backward rt-liber-viewer2-section-regexp + (point-min) + t))) + (if (not last) + (error "no sections found") + (recenter rt-liber-viewer2-recenter) + (goto-char (point-at-bol))))) + +(defun rt-liber-viewer2-previous-section-in () + (interactive) + (when (looking-at rt-liber-viewer2-section-regexp) + (goto-char (1- (point)))) + (let ((prev (re-search-backward rt-liber-viewer2-section-regexp + (point-min) + t))) + (cond ((not prev) + (message "no previous section")) + (t + (recenter rt-liber-viewer2-recenter))) + (goto-char (point-at-bol)))) + +(defun rt-liber-viewer2-answer () + (interactive) + (let ((section (rt-liber-viewer2-get-section-data))) + (when (not section) + (error "no section found")) + (if (not (featurep 'rt-liberation-gnus)) + (error "rt-liberation-gnus feature not found") + (rt-liber-gnus-compose + rt-liber-gnus-address + rt-liber-ticket-local + `((contents . ,(rt-liber-viewer2-clean-content section))))))) + +(defun rt-liber-viewer2-comment () + (interactive) + (let ((section (rt-liber-viewer2-get-section-data))) + (when (not section) + (error "no section found")) + (if (not (featurep 'rt-liberation-gnus)) + (error "rt-liberation-gnus feature not found") + (rt-liber-gnus-compose + rt-liber-gnus-comment-address + rt-liber-ticket-local + `((contents . ,(rt-liber-viewer2-clean-content section))))))) + +(defconst rt-liber-viewer2-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "q") 'rt-liber-viewer2-mode-quit) + (define-key map (kbd "N") 'rt-liber-viewer2-last-section-in) + (define-key map (kbd "n") 'rt-liber-viewer2-next-section-in) + (define-key map (kbd "p") 'rt-liber-viewer2-previous-section-in) + (define-key map (kbd "V") 'rt-liber-viewer-visit-in-browser) + (define-key map (kbd "M") 'rt-liber-viewer2-answer) + (define-key map (kbd "C") 'rt-liber-viewer2-comment) + (define-key map (kbd "g") 'revert-buffer) + (define-key map (kbd "SPC") 'scroll-up) + (define-key map (kbd "DEL") 'scroll-down) + (define-key map (kbd "h") 'rt-liber-viewer-show-ticket-browser) + map) + "Key map for ticket viewer2.") + +(define-derived-mode rt-liber-viewer2-mode nil + "RT Liberation Viewer" + "Major Mode for viewing RT tickets. +\\{rt-liber-viewer-mode-map}" + (set + (make-local-variable 'font-lock-defaults) + '((rt-liber-viewer2-font-lock-keywords))) + (set (make-local-variable 'revert-buffer-function) + #'rt-liber-viewer2-refresh-ticket-history) + (set (make-local-variable 'buffer-stale-function) + (lambda (&optional _noconfirm) 'slow)) + (run-hooks 'rt-liber-viewer-hook)) + + (provide 'rt-liberation) ;;; rt-liberation.el ends here. |