From 148a193ca09289fb4c394f30297a6698856bf56e Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Sun, 22 Nov 2020 19:06:16 -0600 Subject: [PATCH 001/292] Meta: 0.6-pre --- README.org | 4 ++ org-ql.el | 2 +- org-ql.info | 147 ++++++++++++++++++++++++++++------------------------ 3 files changed, 84 insertions(+), 69 deletions(-) diff --git a/README.org b/README.org index b3e7e42..b4f8596 100644 --- a/README.org +++ b/README.org @@ -461,6 +461,10 @@ Simple links may also be written manually in either sexp or non-sexp form, like: /Note:/ Breaking changes may be made before version 1.0, but in the event of major changes, attempts at backward compatibility will be made with obsolescence declarations, translation of arguments, etc. Users who need stability guarantees before 1.0 may choose to use tagged stable releases. +** 0.6-pre + +Nothing new yet. + ** 0.5 *Added* diff --git a/org-ql.el b/org-ql.el index 70fefe0..bd53108 100644 --- a/org-ql.el +++ b/org-ql.el @@ -2,7 +2,7 @@ ;; Author: Adam Porter ;; Url: https://github.com/alphapapa/org-ql -;; Version: 0.5 +;; Version: 0.6-pre ;; Package-Requires: ((emacs "26.1") (dash "2.13") (dash-functional "1.2.0") (f "0.17.2") (map "2.1") (org "9.0") (org-super-agenda "1.2") (ov "1.0.6") (peg "0.6") (s "1.12.0") (transient "0.1") (ts "0.2-pre")) ;; Keywords: hypermedia, outlines, Org, agenda diff --git a/org-ql.info b/org-ql.info index 60d0048..abfce7a 100644 --- a/org-ql.info +++ b/org-ql.info @@ -66,6 +66,7 @@ Functions / Macros Changelog +* 0.6-pre: 06-pre. * 0.5: 05. * 0.4.9: 049. * 0.4.8: 048. @@ -831,6 +832,7 @@ releases. * Menu: +* 0.6-pre: 06-pre. * 0.5: 05. * 0.4.9: 049. * 0.4.8: 048. @@ -852,9 +854,17 @@ releases. * 0.1: 01.  -File: README.info, Node: 05, Next: 049, Up: Changelog +File: README.info, Node: 06-pre, Next: 05, Up: Changelog -5.1 0.5 +5.1 0.6-pre +=========== + +Nothing new yet. + + +File: README.info, Node: 05, Next: 049, Prev: 06-pre, Up: Changelog + +5.2 0.5 ======= *Added* @@ -895,7 +905,7 @@ File: README.info, Node: 05, Next: 049, Up: Changelog  File: README.info, Node: 049, Next: 048, Prev: 05, Up: Changelog -5.2 0.4.9 +5.3 0.4.9 ========= *Fixed* @@ -906,7 +916,7 @@ File: README.info, Node: 049, Next: 048, Prev: 05, Up: Changelog  File: README.info, Node: 048, Next: 047, Prev: 049, Up: Changelog -5.3 0.4.8 +5.4 0.4.8 ========= *Fixed* @@ -918,7 +928,7 @@ File: README.info, Node: 048, Next: 047, Prev: 049, Up: Changelog  File: README.info, Node: 047, Next: 046, Prev: 048, Up: Changelog -5.4 0.4.7 +5.5 0.4.7 ========= *Fixed* @@ -931,7 +941,7 @@ File: README.info, Node: 047, Next: 046, Prev: 048, Up: Changelog  File: README.info, Node: 046, Next: 045, Prev: 047, Up: Changelog -5.5 0.4.6 +5.6 0.4.6 ========= *Fixed* @@ -944,7 +954,7 @@ File: README.info, Node: 046, Next: 045, Prev: 047, Up: Changelog  File: README.info, Node: 045, Next: 044, Prev: 046, Up: Changelog -5.6 0.4.5 +5.7 0.4.5 ========= *Fixed* @@ -956,7 +966,7 @@ File: README.info, Node: 045, Next: 044, Prev: 046, Up: Changelog  File: README.info, Node: 044, Next: 043, Prev: 045, Up: Changelog -5.7 0.4.4 +5.8 0.4.4 ========= *Fixed* @@ -968,7 +978,7 @@ File: README.info, Node: 044, Next: 043, Prev: 045, Up: Changelog  File: README.info, Node: 043, Next: 042, Prev: 044, Up: Changelog -5.8 0.4.3 +5.9 0.4.3 ========= *Fixed* @@ -978,8 +988,8 @@ File: README.info, Node: 043, Next: 042, Prev: 044, Up: Changelog  File: README.info, Node: 042, Next: 041, Prev: 043, Up: Changelog -5.9 0.4.2 -========= +5.10 0.4.2 +========== *Fixed* • Items’ to-do keywords were not shown in views. @@ -987,7 +997,7 @@ File: README.info, Node: 042, Next: 041, Prev: 043, Up: Changelog  File: README.info, Node: 041, Next: 04, Prev: 042, Up: Changelog -5.10 0.4.1 +5.11 0.4.1 ========== *Fixed* @@ -997,7 +1007,7 @@ File: README.info, Node: 041, Next: 04, Prev: 042, Up: Changelog  File: README.info, Node: 04, Next: 032, Prev: 041, Up: Changelog -5.11 0.4 +5.12 0.4 ======== _Note:_ The next release, 0.5, may include changes which will require @@ -1078,7 +1088,7 @@ as they will be pushed to the master branch when ready.  File: README.info, Node: 032, Next: 031, Prev: 04, Up: Changelog -5.12 0.3.2 +5.13 0.3.2 ========== *Fixed* @@ -1091,7 +1101,7 @@ File: README.info, Node: 032, Next: 031, Prev: 04, Up: Changelog  File: README.info, Node: 031, Next: 03, Prev: 032, Up: Changelog -5.13 0.3.1 +5.14 0.3.1 ========== *Fixed* @@ -1101,7 +1111,7 @@ File: README.info, Node: 031, Next: 03, Prev: 032, Up: Changelog  File: README.info, Node: 03, Next: 023, Prev: 031, Up: Changelog -5.14 0.3 +5.15 0.3 ======== *Added* @@ -1166,7 +1176,7 @@ File: README.info, Node: 03, Next: 023, Prev: 031, Up: Changelog  File: README.info, Node: 023, Next: 022, Prev: 03, Up: Changelog -5.15 0.2.3 +5.16 0.2.3 ========== *Fixed* @@ -1176,7 +1186,7 @@ File: README.info, Node: 023, Next: 022, Prev: 03, Up: Changelog  File: README.info, Node: 022, Next: 021, Prev: 023, Up: Changelog -5.16 0.2.2 +5.17 0.2.2 ========== *Fixed* @@ -1187,7 +1197,7 @@ File: README.info, Node: 022, Next: 021, Prev: 023, Up: Changelog  File: README.info, Node: 021, Next: 02, Prev: 022, Up: Changelog -5.17 0.2.1 +5.18 0.2.1 ========== *Fixed* @@ -1197,7 +1207,7 @@ File: README.info, Node: 021, Next: 02, Prev: 022, Up: Changelog  File: README.info, Node: 02, Next: 01, Prev: 021, Up: Changelog -5.18 0.2 +5.19 0.2 ======== *Added* @@ -1280,7 +1290,7 @@ File: README.info, Node: 02, Next: 01, Prev: 021, Up: Changelog  File: README.info, Node: 01, Prev: 02, Up: Changelog -5.19 0.1 +5.20 0.1 ======== First tagged release. @@ -1338,53 +1348,54 @@ GPLv3  Tag Table: Node: Top225 -Node: Contents1586 -Node: Screenshots1709 -Node: Installation1827 -Node: Quelpa2465 -Node: Usage2908 -Node: Commands3306 -Node: org-ql-search3779 -Node: helm-org-ql5429 -Node: org-ql-view5841 -Node: org-ql-view-sidebar6341 -Node: org-ql-view-recent-items6697 -Node: org-ql-sparse-tree7181 -Node: Queries7981 -Node: Non-sexp query syntax9092 -Node: General predicates10799 -Node: Ancestor/descendant predicates16212 -Node: Date/time predicates17340 -Node: Functions / Macros19995 -Node: Agenda-like views20250 -Node: Listing / acting-on results21655 -Node: Dynamic block26850 -Node: Links29548 -Node: Tips30235 -Node: Changelog30553 -Node: 0531229 -Node: 04932691 -Node: 04832965 -Node: 04733312 -Node: 04633707 -Node: 04534107 -Node: 04434466 -Node: 04334823 -Node: 04235018 -Node: 04135177 -Node: 0435418 -Node: 03239351 -Node: 03139730 -Node: 0339927 -Node: 02342902 -Node: 02243130 -Node: 02143398 -Node: 0243597 -Node: 0147632 -Node: Notes47733 -Node: Comparison with Org Agenda searches47895 -Node: org-sidebar48767 -Node: License49046 +Node: Contents1606 +Node: Screenshots1729 +Node: Installation1847 +Node: Quelpa2485 +Node: Usage2928 +Node: Commands3326 +Node: org-ql-search3799 +Node: helm-org-ql5449 +Node: org-ql-view5861 +Node: org-ql-view-sidebar6361 +Node: org-ql-view-recent-items6717 +Node: org-ql-sparse-tree7201 +Node: Queries8001 +Node: Non-sexp query syntax9112 +Node: General predicates10819 +Node: Ancestor/descendant predicates16232 +Node: Date/time predicates17360 +Node: Functions / Macros20015 +Node: Agenda-like views20270 +Node: Listing / acting-on results21675 +Node: Dynamic block26870 +Node: Links29568 +Node: Tips30255 +Node: Changelog30573 +Node: 06-pre31269 +Node: 0531375 +Node: 04932852 +Node: 04833126 +Node: 04733473 +Node: 04633868 +Node: 04534268 +Node: 04434627 +Node: 04334984 +Node: 04235179 +Node: 04135340 +Node: 0435581 +Node: 03239514 +Node: 03139893 +Node: 0340090 +Node: 02343065 +Node: 02243293 +Node: 02143561 +Node: 0243760 +Node: 0147795 +Node: Notes47896 +Node: Comparison with Org Agenda searches48058 +Node: org-sidebar48930 +Node: License49209  End Tag Table From 57b3eeba49745579e0908a4a2fb54f64b2fad88c Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 01:13:03 -0600 Subject: [PATCH 002/292] Merge: org-ql-defpred MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit a0ace307b0de3f5e811f042fd9f0b821a0bbc95c Author: Adam Porter Date: Mon Nov 23 01:11:07 2020 -0600 Docs: Fix TOC commit bef9f3c9b5b762070445c76ac3c833e51dd893ba Author: Adam Porter Date: Mon Nov 23 01:09:33 2020 -0600 Docs: (README.org) Add org-ql-defpred commit ff0257696fe87764e3289e12ddc336377a9a5d01 Author: Adam Porter Date: Mon Nov 23 00:52:06 2020 -0600 Docs: Improve defpred.org Thanks to piyo in #emacsconf@freenode. commit 18d6d73a99c336cd637c99309ee5d150106b73b7 Author: Adam Porter Date: Mon Nov 23 00:35:24 2020 -0600 Docs: Improve custom predicate tutorial Closes #155. Thanks to Merlin Göttlinger (@mgttlinger) for suggesting improvements. commit b8cbe36fb3bec23dad5fac07cd11018d98a0e9c1 Author: Adam Porter Date: Mon Nov 23 00:05:52 2020 -0600 Docs: Add custom predicate tutorial commit b1008d0c526b988e958809997351f347af41a7f7 Author: Adam Porter Date: Sun Nov 22 19:34:16 2020 -0600 Docs: Update readme commit 73d4f95cd4eb429774c16b6d403285e9755a2412 Merge: 9a2719f 148a193 Author: Adam Porter Date: Sun Nov 22 19:29:15 2020 -0600 Merge branch 'wip/0.6-pre' into wip/define-predicate ... --- README.org | 64 +- examples.org | 5 + examples/defpred.org | 331 ++++++++++ helm-org-ql.el | 4 +- org-ql-search.el | 4 +- org-ql-view.el | 2 +- org-ql.el | 1499 +++++++++++++++++++++++------------------- org-ql.info | 192 ++++-- tests/test-org-ql.el | 122 ++-- 9 files changed, 1432 insertions(+), 791 deletions(-) create mode 100644 examples/defpred.org diff --git a/README.org b/README.org index b4f8596..5f9e718 100644 --- a/README.org +++ b/README.org @@ -258,15 +258,19 @@ The following predicates, in addition to the keyword arguments, can also take a ** Functions / Macros :PROPERTIES: -:TOC: ignore-children +:TOC: :include descendants +:END: +:CONTENTS: +- [[#agenda-like-views][Agenda-like views]] + - [[#function-org-ql-block][Function: org-ql-block]] +- [[#listing--acting-on-results][Listing / acting-on results]] + - [[#caching][Caching]] + - [[#function-org-ql-select][Function: org-ql-select]] + - [[#function-org-ql-query][Function: org-ql-query]] + - [[#macro-org-ql][Macro: org-ql]] +- [[#custom-predicates][Custom predicates]] + - [[#macro-org-ql-defpred][Macro: org-ql-defpred]] :END: - -- [[#agenda-like-views][Agenda-like views]] - - [[#function-org-ql-block][Function: org-ql-block]] -- [[#listing--acting-on-results][Listing / acting-on results]] - - [[#function-org-ql-select][Function: org-ql-select]] - - [[#function-org-ql-query][Function: org-ql-query]] - - [[#macro-org-ql][Macro: org-ql]] *** Agenda-like views @@ -405,6 +409,44 @@ Examples: Expands into a call to ~org-ql-select~ with the same arguments. For convenience, arguments should be unquoted. +*** Custom predicates + ++ See: [[file:examples/defpred.org][Custom predicate tutorial]] + +**** Macro: =org-ql-defpred= + +/Arguments:/ ~(name args docstring &key body preambles normalizers)~ + +Define an ~org-ql~ selector predicate named ~org-ql--predicate-NAME~. ~NAME~ may be a symbol or a list of symbols: if a list, the first is used as ~NAME~ and the rest are aliases. ~A~ function is only created for ~NAME~, not for aliases, so a normalizer should be used to replace aliases with ~NAME~ in queries (keep reading). + +~ARGS~ is a ~cl-defun~-style argument list. ~DOCSTRING~ is the function's docstring. + +~BODY~ is the body of the predicate. It will be evaluated with point on the beginning of an Org heading and should return non-nil if the heading's entry is a match. + +~PREAMBLES~ and ~NORMALIZERS~ are lists of ~pcase~ forms matched against Org ~QL~ query sexps. They are spliced into ~pcase~ forms in the definitions of the functions ~org-ql--query-preamble~ and ~org-ql--normalize-query~, which see. Those functions are redefined when this macro is expanded, unless variable ~org-ql-defpred-defer~ is non-nil, in which case those functions should be redefined manually after defining predicates by calling ~org-ql--define-query-preamble-fn~ and ~org-ql--define-normalize-query-fn~. + +~NORMALIZERS~ are used to normalize query expressions to standard forms. For example, when the predicate has aliases, the aliases should be replaced with predicate names using a normalizer. Also, predicate arguments may be put into a more optimal form so that the predicate has less work to do at query time. + +~PREAMBLES~ refer to regular expressions which may be used to search through a buffer directly to a potential match rather than testing the predicate body on each heading. (Naming things is hard.) In each ~pcase~ form in ~PREAMBLES~, the ~pcase~ expression (not the pattern) should be a plist with the following keys, each value of which should be an expression which may refer to variables bound in the pattern: + +~:regexp~ Regular expression which searches directly to a potential match. + +~:case-fold~ Bound to ~case-fold-search~ around the regexp search. + +~:query~ Expression which should replace the query expression, or ~query~ if it should not be changed (e.g. if the regexp is insufficient to determine whether a heading matches, in which case the predicate's body needs to be tested on the heading). If the regexp guarantees a match, this may be simply ~t~, leaving the query expression with no work to do, which improves performance. + +For convenience, within the ~pcase~ patterns, the symbol ~predicate-names~ is a special form which is replaced with a pattern matching any of the predicate's name and aliases. For example, if ~NAME~ were: + +~(heading h)~ + +Then if ~NORMALIZERS~ were: + +~((`(,predicate-names . ,args) `(heading ,@args)))~ + +It would be expanded to: + +~((`(,(or 'heading 'h) . ,args) `(heading ,@args)))~ + ** Dynamic block Org QL provides a dynamic block that lists entries in the current document matching a query. In the header, these parameters are supported: @@ -463,7 +505,11 @@ Simple links may also be written manually in either sexp or non-sexp form, like: ** 0.6-pre -Nothing new yet. +*Added* ++ Macro =org-ql-defpred=, used to define search predicates. (See [[file:examples/defpred.org][tutorial]].) + +*Internal* ++ Predicates are now defined more cleanly with a macro (=org-ql-defpred=) that consolidates functionality related to each predicate. This will also allow users to more easily define custom predicates. ** 0.5 diff --git a/examples.org b/examples.org index eb14f08..7d5f28d 100644 --- a/examples.org +++ b/examples.org @@ -5,6 +5,7 @@ :TOC: :include siblings :ignore this :END: :CONTENTS: +- [[#custom-predicates][Custom predicates]] - [[#agenda-like-view][Agenda-like view]] - [[#entries-from-the-past-week][Entries from the past week]] - [[#find-entries-matching-a-certain-custom_id][Find entries matching a certain CUSTOM_ID]] @@ -18,6 +19,10 @@ - [[#task-list-for-files-in-subdirectories][Task list for files in subdirectories]] :END: +* Custom predicates + ++ [[file:examples/defpred.org][Custom predicate tutorial]] + * Agenda-like view Show an agenda-like view, similar to a "traditional" Org Agenda with Log Mode turned on. diff --git a/examples/defpred.org b/examples/defpred.org new file mode 100644 index 0000000..86565cf --- /dev/null +++ b/examples/defpred.org @@ -0,0 +1,331 @@ +#+TITLE: Org QL Custom Predicates Tutorial +#+OPTIONS: author:nil creator:nil created:nil date:nil num:nil title:t + +[2020-11-22 Sun 22:35] Imagine you have weekly meetings with other people, and during the week you take notes about items to discuss at each meeting. When the time for a meeting comes, you want to quickly and easily search for all of the items to discuss at the meeting. + +You've been experimenting with different ways to track such data in Org. You've tried using tags, but some of the names in question conflict with other tags in your data (e.g. someone's named Charles, but you also work with a firm named Charles, Inc., and you'd prefer to continue using the tag =Charles= for entries about that firm), so you've been using tags like ~:personNAME:~, which seems awkward. You've tried using a ~:person: NAME~ property on entries, which has the advantage of not cluttering the tags list, but also the disadvantage of not being readily visible in an outline. + +So you haven't decided on a long-term solution, but the meetings aren't going to wait--you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged ~:personAlice:~) when you're meeting with her, and all of the entries about Bob (which have the property ~:person: Bob~) when you're meeting with him What do you do? + +* Contents +:PROPERTIES: +:TOC: :include siblings :ignore this +:END: +:CONTENTS: +- [[#using-built-in-predicates][Using built-in predicates]] +- [[#a-custom-person-predicate][A custom (person) predicate]] +- [[#searching-for-multiple-people-at-once][Searching for multiple people at once]] +- [[#normalizing-queries-to-rewrite-arguments][Normalizing queries to rewrite arguments]] +- [[#non-sexp-query-syntax][Non-sexp query syntax]] +- [[#using-multiple-predicates][Using multiple predicates]] +- [[#predicate-aliases][Predicate aliases]] +- [[#appendix-anaphoric-macros][Appendix: Anaphoric macros]] +:END: + +* Using built-in predicates + +You could start by using built-in Org QL predicates to search your data. For example: + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(or (tags "personAlice") + (property "person" "Bob"))) +#+END_SRC + +#+RESULTS[91a413cda23cb65d6bb99212e111f283e5a5c910]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +That was easy enough, but it's not very...semantic. You have to think about the implementation details: Alice uses tags, Bob uses properties, and what if Charlie uses both? It starts to feel complicated, and it's a lot to type out every time. Is there an easier way? + +* A custom ~(person)~ predicate + +Enter =org-ql= custom search predicates. Let's start simple, by defining a predicate to search for just the ~:person:~ property, one person at a time. The predicate will take one argument, a person's name, and search for that property. It would look like this: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred person (name) + "Search for entries with the \"person\" property being NAME." + :body (property "person" name)) +#+END_SRC + +Now let's see what results we get for searching this file for entries about Bob: + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Bob")) +#+END_SRC + +#+RESULTS[c11a4ce2c4f179d7487c9b46eff9f72766bc2bc4]: +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +Hmm, looks like we need to remind Bob to wash his mug and take some mints after lunch. Now what do we need to discuss with Alice? + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice")) +#+END_SRC + +#+RESULTS[1f12f437042bbc077a4696d707805c1367f2ca3d]: +#+BEGIN_EXAMPLE + +#+END_EXAMPLE + +Nothing? Oh, right, Alice's entries use the ~:personAlice:~ tag, so we'll also need to also search those kind of entries. Let's make the predicate do that too: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred person (name) + "Search for entries with the \"person\" property being NAME or having the tag \"personNAME\"." + :body (or (property "person" name) + (tags (concat "person" name)))) +#+END_SRC + +How about now? + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice")) +#+END_SRC + +#+RESULTS[1f12f437042bbc077a4696d707805c1367f2ca3d]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink + +Hmm, I thought we already told her to leave Polly at home. + +* Searching for multiple people at once + +Oh, wait, this week is shortened due to holidays, so we're having a combined meeting. How do we search for entries about either of them? Well, this is the obvious solution: + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(or (person "Alice") + (person "Bob"))) +#+END_SRC + +#+RESULTS[4e4c75bde4fbceaadb076a53410c1625d1283e06]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +And that works fine. But it seems like a lot to type. Could we make the =person= predicate accept multiple names instead? + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred person (&rest names) + "Search for entries about any of NAMES." + :body (cl-loop for name in names + thereis (or (property "person" name) + (tags (concat "person" name))))) +#+END_SRC + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice" "Bob")) +#+END_SRC + +#+RESULTS[4f5971c56616f01d8d3c28a66ef380495ee3e158]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +That was easy! + +* Normalizing queries to rewrite arguments + +Now, all this is well and good if you don't have hundreds of thousands of Org entries in your files. But what if you do? All that =concat='ing happening on every entry could add up, and the query might take a few seconds. What if we could do that stringing-along just once, before running the query? We want to turn our ~(person "Alice" "Bob")~ query into this, with the =:personNAME:= strings already made and the per-person ~(property ...)~ predicates also included: + +#+BEGIN_SRC elisp + (or (tags "personAlice" "personBob") + (property "person" "Alice") + (property "person" "Bob")) +#+END_SRC + +Can we do that? In fact, we can, by using a query normalizer. Normalizers are =pcase= forms (I /know/) that normalize query expressions before execution. We can use one to rewrite the query ahead of time, like this: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred person (&rest names) + "Search for entries about any of NAMES." + :normalizers ((`(person . ,names) + `(or (tags ,@(cl-loop for name in names + collect (concat "person" name))) + ,@(cl-loop for name in names + collect `(property "person" ,name))))) + :body (cl-loop for name in names + thereis (or (property "person" name) + (tags (concat "person" name))))) +#+END_SRC + +Now, don't faint from all the backquoting and unquoting--it's just Lisp, nothing to be afraid of! Let's slow down a moment and see what the normalized query looks like to be sure we're doing it correctly: + +#+BEGIN_SRC elisp :results code :exports both :cache yes + (org-ql--normalize-query '(person "Alice" "Bob")) +#+END_SRC + +#+RESULTS[ebc46fff31b72359353dda539a26c95b7d650df2]: +#+BEGIN_SRC elisp + (or (tags "personAlice" "personBob") + (property "person" "Alice") + (property "person" "Bob")) +#+END_SRC + +And, as they say, Bob's your uncle! Or even if he isn't, let's see if it works: + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice" "Bob")) +#+END_SRC + +#+RESULTS[4f5971c56616f01d8d3c28a66ef380495ee3e158]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +Yep, same result as the non-normalized query. And look at how much simpler it is to write ~(person "Alice" "Bob")~ than to write ~(or (tags "personAlice" "personBob") (property "person" "Alice") (property "person" "Bob"))~. + +* Non-sexp query syntax + +But wait, that's not all! If you order now, we'll throw in non-sexp query syntax for free! That's right, your search could be as simple as typing ~person:Alice,Bob~! + +#+BEGIN_SRC elisp :results none :exports code + (org-ql-search (current-buffer) "person:Alice,Bob") +#+END_SRC + +Don't believe me? Well, you see, queries in this syntax are automatically converted to the sexp syntax, like: + +#+BEGIN_SRC elisp :results code :exports both :cache yes + (org-ql--query-string-to-sexp "person:Alice,Bob") +#+END_SRC + +#+RESULTS[a60655544956644605c23c152570185c329faa87]: +#+BEGIN_SRC elisp + (person "Alice" "Bob") +#+END_SRC + +But that happens automatically when you use a search command like =org-ql-search=. If you have =org-ql= installed already, you could even click this link: [[org-ql-search:person:Alice,Bob][Alice or Bob]]. Which, in Org syntax, looks like: + +#+BEGIN_SRC org + [[org-ql-search:person:Alice,Bob]] +#+END_SRC + +And that would open an Agenda Mode buffer that looks like this: + +#+BEGIN_EXAMPLE + Query: (person "Alice" "Bob") In:meetings.org + [#A] Loud pet parakeet :personAlice: + [#C] Missing sticky notes :personAlice: + [#C] Dirty dishes in sink :personAlice: + [#A] Stinky coffee breath +#+END_EXAMPLE + +* Using multiple predicates + +Oops, you forgot that there's a birthday party in 20 minutes, so you only have time to talk about the highest priority items at this joint meeting today. + +No problem, let's just select high-priority items: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-search (current-buffer) "person:Alice,Bob priority:A") +#+END_SRC + +#+BEGIN_EXAMPLE + Query: (and (person "Alice" "Bob") (priority "A")) In:meetings.org + [#A] Loud pet parakeet :personAlice: + [#A] Stinky coffee breath +#+END_EXAMPLE + +* Predicate aliases + +And, you know what, if you're just so busy that you don't even have time to type the word =person=, you can add an abbreviated alias, =p=, like this: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred (person p) (&rest names) + "Search for entries about any of NAMES." + :normalizers ((`(,predicate-names . ,names) + `(or (tags ,@(cl-loop for name in names + collect (concat "person" name))) + ,@(cl-loop for name in names + collect `(property "person" ,name))))) + :body (cl-loop for name in names + thereis (or (property "person" name) + (tags (concat "person" name))))) +#+END_SRC + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-search (current-buffer) "p:Alice,Bob priority:A") +#+END_SRC + +#+BEGIN_EXAMPLE + Query: (and (person "Alice" "Bob") (priority "A")) In:meetings.org + [#A] Loud pet parakeet :personAlice: + [#A] Stinky coffee breath +#+END_EXAMPLE + +(It's up to you to remember whether =p= means =person= or =priority=, but code can't solve everything.) + +* Appendix: Anaphoric macros + +Finally, if you're a Lisper who appreciates anaphora, you might prefer a more syntactically concise definition of the predicate using Dash macros: + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred (person p) (&rest names) + "Search for entries about any of NAMES." + :normalizers ((`(,predicate-names . ,names) + `(or (tags ,@(--map `(concat "person" ,it) names)) + ,@(--map `(property "person" ,it) names)))) + :body (--any (or (property "person" name) + (tags (concat "person" name))) + names)) +#+END_SRC + +Let's make sure it works: + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice" "Bob")) +#+END_SRC + +#+RESULTS[4f5971c56616f01d8d3c28a66ef380495ee3e158]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +Have fun making custom search predicates! + +* Example data +:PROPERTIES: +:TOC: :ignore (this descendants) +:END: + +** [#A] Loud pet parakeet :personAlice: + +** [#C] Missing sticky notes :personAlice: +:PROPERTIES: +:person: Bob +:END: + +** [#C] Dirty dishes in sink :personAlice: +:PROPERTIES: +:person: Bob +:END: + +** [#A] Stinky coffee breath +:PROPERTIES: +:person: Bob +:END: + diff --git a/helm-org-ql.el b/helm-org-ql.el index e95bc5f..e75cf7b 100644 --- a/helm-org-ql.el +++ b/helm-org-ql.el @@ -173,7 +173,7 @@ Is transformed into this query: (interactive) (let ((buffers-files (with-current-buffer (helm-buffer-get) helm-org-ql-buffers-files)) - (query (org-ql--plain-query helm-pattern))) + (query (org-ql--query-string-to-sexp helm-pattern))) (helm-run-after-exit #'org-ql-search buffers-files query))) ;;;###autoload @@ -189,7 +189,7 @@ Is transformed into this query: ;; Expansion of `helm-build-sync-source' macro. (helm-make-source name 'helm-source-sync :candidates (lambda () - (let* ((query (org-ql--plain-query helm-pattern)) + (let* ((query (org-ql--query-string-to-sexp helm-pattern)) (window-width (window-width (helm-window)))) (when query (with-current-buffer (helm-buffer-get) diff --git a/org-ql-search.el b/org-ql-search.el index 70d846d..c60bd8e 100644 --- a/org-ql-search.el +++ b/org-ql-search.el @@ -158,7 +158,7 @@ necessary." ;; Read sexp query. (read query) ;; Parse non-sexp query into sexp query. - (org-ql--plain-query query))) + (org-ql--query-string-to-sexp query))) (list query))) (results (org-ql-select buffers-files query :action 'element-with-markers @@ -273,7 +273,7 @@ For example, an org-ql dynamic block header could look like: #+BEGIN: org-ql :query (todo \"UNDERWAY\") :columns (priority todo heading) :sort (priority date) :ts-format \"%Y-%m-%d %H:%M\"" (-let* (((&plist :query :columns :sort :ts-format :take) params) (query (cl-etypecase query - (string (org-ql--plain-query query)) + (string (org-ql--query-string-to-sexp query)) (list ;; SAFETY: Query is in sexp form: ask for confirmation, because it could contain arbitrary code. (org-ql--ask-unsafe-query query) query))) diff --git a/org-ql-view.el b/org-ql-view.el index b954fc0..959484c 100644 --- a/org-ql-view.el +++ b/org-ql-view.el @@ -775,7 +775,7 @@ When opened, the link searches the buffer it's opened from." ;; Read sexp query. (read query) ;; Parse non-sexp query into sexp query. - (org-ql--plain-query query))))) + (org-ql--query-string-to-sexp query))))) (define-infix-command org-ql-view--transient-in () :description (lambda () (org-ql-view--format-transient-lisp-argument "In buffers/files" org-ql-view-buffers-files)) diff --git a/org-ql.el b/org-ql.el index bd53108..b4b98b3 100644 --- a/org-ql.el +++ b/org-ql.el @@ -125,7 +125,9 @@ the value returned by it at that node.") (eval-and-compile (defvar org-ql-predicates - (list (list :name 'org-back-to-heading :fn (symbol-function 'org-back-to-heading))) + ;; FIXME: Is this remapping still necessary? It was mapping `org-back-to-heading' + ;; to itself until now, so maybe I broke it and it doesn't matter anymore. + (list (cons 'org-back-to-heading (list :name 'org-back-to-heading :fn (symbol-function 'outline-back-to-heading)))) "Plist of predicates, their corresponding functions, and their docstrings. This list should not contain any duplicates.")) @@ -254,7 +256,7 @@ returns nil or non-nil." (user-error "Can't open file: %s" it))))) ;; Ignore special/hidden buffers. (--remove (string-prefix-p " " (buffer-name it))))) - (query (org-ql--pre-process-query query)) + (query (org-ql--normalize-query query)) ((&plist :query :preamble :preamble-case-fold) (org-ql--query-preamble query)) (predicate (org-ql--query-predicate query)) (action (pcase action @@ -377,13 +379,15 @@ If NARROW is non-nil, buffer will not be widened." (let (orig-fns) (--each org-ql-predicates ;; Save original function mappings. - (let ((name (plist-get it :name))) + (let* ((it (cdr it)) + (name (plist-get it :name))) (push (list :name name :fn (symbol-function name)) orig-fns))) (unwind-protect (progn (--each org-ql-predicates ;; Set predicate functions. - (fset (plist-get it :name) (plist-get it :fn))) + (let ((it (cdr it))) + (fset (plist-get it :name) (plist-get it :fn)))) ;; Run query. (save-excursion (save-restriction @@ -586,356 +590,6 @@ Or, when possible, fix the problem." (org-ql--sanity-check-form (cdr elem))) else do (check elem)))) -(defun org-ql--pre-process-query (query) - "Return QUERY having been pre-processed. -Replaces bare strings with (regexp) selectors, and appropriate -`ts'-related selectors." - ;; This is unsophisticated, but it works. - ;; TODO: Maybe query pre-processing should be done in one place, - ;; rather than here and in --query-predicate. - ;; NOTE: Don't be scared by the `pcase' patterns! They make this - ;; all very easy once you grok the backquoting and unquoting. - (cl-labels ((rec (element) - (pcase element - (`(or . ,clauses) `(or ,@(mapcar #'rec clauses))) - (`(and . ,clauses) `(and ,@(mapcar #'rec clauses))) - (`(not . ,clauses) `(not ,@(mapcar #'rec clauses))) - (`(when ,condition . ,clauses) `(when ,(rec condition) - ,@(mapcar #'rec clauses))) - (`(unless ,condition . ,clauses) `(unless ,(rec condition) - ,@(mapcar #'rec clauses))) - ;; TODO: Combine (regexp) when appropriate (i.e. inside an OR, not an AND). - ((pred stringp) `(regexp ,element)) - ;; Quote children queries so the user doesn't have to. - (`(children ,query) `(children ',query)) - (`(children) '(children (lambda () t))) - (`(descendants ,query) `(descendants ',query)) - (`(descendants) '(descendants (lambda () t))) - (`(parent ,query) `(parent ,(org-ql--query-predicate (rec query)))) - (`(parent) '(parent (lambda () t))) - (`(ancestors ,query) `(ancestors ,(org-ql--query-predicate (rec query)))) - (`(ancestors) '(ancestors (lambda () t))) - ;; Timestamp-based predicates. I think this is the way that makes the most sense: - ;; set the limit to N days in the future, adjusted to 23:59:59 (since Org doesn't - ;; support timestamps down to the second, anyway, there should be no need to adjust - ;; it forward to 00:00:00 of the next day). That way, e.g. if it's Monday at 3 PM, - ;; and N is 1, rather than showing items up to 3 PM Tuesday, it will show items any - ;; time on Tuesday. If this isn't desired, the user can pass a specific timestamp. - (`(,(and pred (or 'clocked 'closed)) - ,(and num-days (pred numberp))) - ;; (clocked) and (closed) implicitly look into the past. - (let ((from (->> (ts-now) - (ts-adjust 'day (* -1 num-days)) - (ts-apply :hour 0 :minute 0 :second 0)))) - `(,pred :from ,from))) - (`(deadline auto) - ;; Use `org-deadline-warning-days' as the :to arg. - (let ((to (->> (ts-now) - (ts-adjust 'day org-deadline-warning-days) - (ts-apply :hour 23 :minute 59 :second 59)))) - `(deadline-warning :to ,to))) - (`(,(and pred (or 'deadline 'scheduled 'planning)) - ,(and num-days (pred numberp))) - (let ((to (->> (ts-now) - (ts-adjust 'day num-days) - (ts-apply :hour 23 :minute 59 :second 59)))) - `(,pred :to ,to))) - - ;; Headings. - (`(h . ,args) - ;; "h" alias. - `(heading ,@args)) - - ;; Outline level. - (`(level . ,args) - ;; Arguments could be given as strings (e.g. from a non-Lisp query). - `(level ,@(--map (pcase it - ((or "<" "<=" ">" ">=" "=") - (intern it)) - ((pred stringp) (string-to-number it)) - (_ it)) - args))) - - ;; Regexps. - (`(r . ,args) - ;; "r" alias. - `(regexp ,@args)) - - ;; Outline paths. - (`(,(or 'outline-path 'olp) . ,strings) - ;; Regexp quote headings. - `(outline-path ,@(mapcar #'regexp-quote strings))) - (`(,(or 'outline-path-segment 'olps) . ,strings) - ;; Regexp quote headings. - `(outline-path-segment ,@(mapcar #'regexp-quote strings))) - - ;; Priorities - (`(priority ,(and (or '= '< '> '<= '>=) comparator) ,letter) - ;; Quote comparator. - `(priority ',comparator ,letter)) - - ;; Properties. - (`(property ,property . ,value) - ;; Convert keyword property arguments to strings. Non-sexp - ;; queries result in keyword property arguments (because to do - ;; otherwise would require ugly special-casing in the parsing). - (when (keywordp property) - (setf property (substring (symbol-name property) 1))) - (cons 'property (cons property value))) - - ;; Source blocks. - (`(src . ,args) - ;; Rewrite to use keyword args. - (-let (regexps lang keyword-index) - (cond ((plist-get args :lang) - ;; Lang given first, or only lang given. - (setf lang (plist-get args :lang) - regexps (seq-difference args (list :lang lang)))) - ((setf keyword-index (-find-index #'keywordp args)) - ;; Regexps and lang given. - (setf lang (plist-get (cl-subseq args keyword-index) :lang) - regexps (cl-subseq args 0 keyword-index))) - (t ;; Only regexps given. - (setf regexps args))) - (when regexps - ;; This feels awkward and wrong, but we have to quote lists - ;; and avoid quoting nil. There must be a better way. - (setf regexps `(',regexps))) - `(src :lang ,lang :regexps ,@regexps))) - - ;; Tags. - (`(,(or 'tags-all 'tags&) . ,tags) `(and ,@(--map `(tags ,it) tags))) - ;; MAYBE: -all versions for inherited and local. - ;; Inherited and local predicate aliases. - (`(,(or 'tags-i 'itags 'inherited-tags) . ,tags) `(tags-inherited ,@tags)) - (`(,(or 'tags-l 'ltags 'local-tags) . ,tags) `(tags-local ,@tags)) - (`(,(or 'tags*) . ,regexps) `(tags-regexp ,@regexps)) - - ;; Timestamps - (`(,(or 'ts-active 'ts-a) . ,rest) `(ts :type active ,@rest)) - (`(,(or 'ts-inactive 'ts-i) . ,rest) `(ts :type inactive ,@rest)) - ;; Any other form: passed through unchanged. - (_ element)))) - (rec query))) - -(defun org-ql--query-preamble (query) - "Return plist (QUERY PREAMBLE PREAMBLE-CASE-FOLD) for QUERY. -When QUERY has a clause with a corresponding preamble, and it's -appropriate to use one (i.e. the clause is not in an `or'), -replace the clause with a preamble." - (pcase org-ql-use-preamble - ('nil (list :query query :preamble nil)) - (_ (let ((preamble-case-fold t) - org-ql-preamble) - (cl-labels ((rec (element) - (or (when org-ql-preamble - ;; Only one preamble is allowed - element) - (pcase element - (`(or _) element) - (`(clocked . ,_) - (setq org-ql-preamble org-ql-clock-regexp) - element) - (`(closed . ,_) - (setq org-ql-preamble org-closed-time-regexp) - ;; Return element, because the predicate still needs testing. - element) - (`(deadline . ,_) - (setq org-ql-preamble org-deadline-time-regexp) - ;; Return element, because the predicate still needs testing. - element) - (`(regexp . ,regexps) - ;; Search for first regexp, then confirm with predicate. - (setq org-ql-preamble (car regexps)) - element) - (`(todo . ,(and todo-keywords (guard todo-keywords))) - (setf org-ql-preamble - (rx-to-string `(seq bol (1+ "*") (1+ space) (or ,@todo-keywords) (or " " eol)) - t) - preamble-case-fold nil) - ;; Return nil, don't test the predicate. - nil) - (`(habit) - (setq org-ql-preamble (rx bol (0+ space) ":STYLE:" (1+ space) "habit" (0+ space) eol)) - nil) - - ;; Heading text. - ;; MAYBE: Adjust regexp to avoid matching in tag list. - (`(heading ,regexp) - ;; Only one regexp: match with preamble, then let predicate confirm (because - ;; the match could be in e.g. the tags rather than the heading text). - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) - ,regexp) - 'no-group)) - element) - (`(heading . ,regexps) - ;; Multiple regexps: use preamble to match against first - ;; regexp, then let the predicate match the rest. - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) - ,(car regexps)) - 'no-group)) - element) - - ;; Heading levels. - (`(level ,comparator-or-num ,num) - (let ((repeat (pcase comparator-or-num - ('< `(repeat 1 ,(1- num) "*")) - ('<= `(repeat 1 ,num "*")) - ('> `(>= ,(1+ num) "*")) - ('>= `(>= ,num "*")) - ((pred integerp) `(repeat ,comparator-or-num ,num "*"))))) - (setq org-ql-preamble (rx-to-string `(seq bol ,repeat " ") t)) - ;; Return nil, because we don't need to test the predicate. - nil)) - (`(level ,num) - (setq org-ql-preamble (rx-to-string `(seq bol (repeat ,num "*") " ") t)) - nil) - - ;; Links. Always return nil, because we - ;; shouldn't need to test the predicate. - (`(link) - (setq org-ql-preamble - ;; Match a link with a target and optionally a description. - (rx (or bol (1+ blank)) - "[[" (1+ (not (any "]"))) "]" - (optional (seq "[" (0+ (not (any "]"))) "]")) - "]" - (or eol blank))) - nil) - ;; NOTE: I would use the form "(map :regexp-p)", or at least - ;; "(map (:regexp-p regexp))" but they require map versions from - ;; ELPA. That would be fine, except that I can't automatically - ;; install those versions with makem.sh into a sandbox, because - ;; `package-install' doesn't accept a version argument. So I - ;; have to use `plist-get' here for now. Maybe when we drop - ;; support for Emacs <28... - (`(link ,(and description-or-target - (guard (not (keywordp description-or-target))))) - (setq org-ql-preamble - (org-ql--link-regexp :description-or-target - (regexp-quote description-or-target))) - nil) - (`(link . ,plist) - (setq org-ql-preamble - (org-ql--link-regexp - :description - (when (plist-get plist :description) - (regexp-quote (plist-get plist :description))) - :target (when (plist-get plist :target) - (regexp-quote (plist-get plist :target))))) - nil) - - ;; Planning lines. - (`(planning . ,_) - (setq org-ql-preamble org-ql-planning-regexp) - ;; Return element, because the predicate still needs testing. - element) - - ;; Priorities. - ;; NOTE: This only accepts A, B, or C. I haven't seen - ;; other priorities in the wild, so this will do for now. - (`(priority) - ;; Any priority cookie. - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) "[#" (in "ABC") "]") t)) - nil) - (`(priority ,(and (or ''= ''< ''> ''<= ''>=) comparator) ,letter) - ;; Comparator and priority letter. - ;; NOTE: The double-quoted comparators. See below. - (let* ((priority-letters '("A" "B" "C")) - (index (-elem-index letter priority-letters)) - ;; NOTE: Higher priority == lower number. - ;; NOTE: Because we need to support both preamble-based queries and - ;; regular predicate ones, we work around an idiosyncrasy of query - ;; pre-processing by accepting both quoted and double-quoted comparator - ;; function symbols. Not the most elegant solution, but it works. - (priorities (s-join "" (pcase comparator - ((or '= ''=) (list letter)) - ((or '> ''>) (cl-subseq priority-letters 0 index)) - ((or '>= ''>=) (cl-subseq priority-letters 0 (1+ index))) - ((or '< ''<) (cl-subseq priority-letters (1+ index))) - ((or '<= ''<=) (cl-subseq priority-letters index)))))) - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) (optional (1+ upper) (1+ blank)) - "[#" (in ,priorities) "]") t)) - nil)) - (`(priority . ,letters) - ;; One or more priorities. - ;; MAYBE: Disable case-folding. - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ blank) - (optional (1+ upper) (1+ blank)) - "[#" (or ,@letters) "]") t)) - nil) - - ;; Properties. - ;; MAYBE: Should case folding be disabled for properties? What about values? - (`(property ,property ,value) - ;; We do NOT return nil, because the predicate still needs to be tested, - ;; because the regexp could match a string not inside a property drawer. - (setq org-ql-preamble (rx-to-string `(seq bol (0+ space) ":" ,property ":" - (1+ space) ,value (0+ space) eol))) - element) - (`(property ,property) - ;; We do NOT return nil, because the predicate still needs to be tested, - ;; because the regexp could match a string not inside a property drawer. - ;; NOTE: The preamble only matches if there appears to be a value. - ;; A line like ":ID: " without any other text does not match. - (setq org-ql-preamble (rx-to-string `(seq bol (0+ space) ":" ,property ":" (1+ space) - (minimal-match (1+ not-newline)) eol))) - element) - ;; MAYBE: Support (property) without args. - ;; (`(property) - ;; ;; We do NOT return nil, because the predicate still needs to be tested, - ;; ;; because the regexp could match a string not inside a property drawer. - ;; ;; NOTE: The preamble only matches if there appears to be a value. - ;; ;; A line like ":ID: " without any other text does not match. - ;; (setq org-ql-preamble (rx-to-string `(seq bol (0+ space) ":" (1+ (not (or space ":"))) ":" - ;; (1+ space) (minimal-match (1+ not-newline)) eol))) - ;; element) - - ;; Src blocks. - (`(src . ,args) - (setq org-ql-preamble (org-ql--format-src-block-regexp (plist-get args :lang))) - ;; Always check contents with predicate. - element) - - ;; Scheduled. - (`(scheduled . ,_) - (setq org-ql-preamble org-scheduled-time-regexp) - ;; Return element, because the predicate still needs testing. - element) - - ;; Tags. - (`((or 'tags-local 'local-tags 'tags-l 'ltags) . ,tags) - ;; When searching for local, non-inherited tags, we can - ;; search directly to headings containing one of the tags. - (setq org-ql-preamble (rx-to-string `(seq bol (1+ "*") (1+ space) (1+ not-newline) - ":" (or ,@tags) ":") - t)) - ;; Return nil, because we don't need to test the predicate. - nil) - - ;; Timestamps. - (`(ts . ,rest) - (setq org-ql-preamble (pcase (plist-get rest :type) - ((or 'nil 'both) org-tsr-regexp-both) - ('active org-tsr-regexp) - ('inactive org-ql-tsr-regexp-inactive))) - ;; Predicate needs testing only when args are present. - (-let (((&keys :from :to :on) rest)) - (when (or from to on) - element))) - (`(and . ,rest) - (let ((clauses (mapcar #'rec rest))) - `(and ,@(-non-nil clauses)))) - (_ element))))) - (setq query (pcase (mapcar #'rec (list query)) - ((or `(nil) - `((nil)) - `((and)) - `((or))) - t) - (query (-flatten-n 1 query)))) - (list :query query :preamble org-ql-preamble :preamble-case-fold preamble-case-fold)))))) - (cl-defun org-ql--link-regexp (&key description-or-target description target) "Return a regexp matching Org links according to arguments. Each argument is treated as a regexp (so non-regexp strings @@ -1000,6 +654,351 @@ respectively." "#+end_src") t)) +(defun org-ql--byte-compile-warning (_string _pos _fill level) + "Signal an `org-ql-invalid-query' error. +Arguments STRING, POS, FILL, and LEVEL are according to +`byte-compile-log-warning-function'." + ;; Used as the `byte-compile-log-warning-function' in `org-ql--query-preamble'. + (signal 'org-ql-invalid-query level)) + +(defun org-ql--query-predicate (query) + "Return predicate function for QUERY." + ;; Use custom log function to prevent warnings for e.g. partially typed queries. + (let ((byte-compile-log-warning-function #'org-ql--byte-compile-warning)) + (byte-compile + `(lambda () + (cl-macrolet ((clocked (&key from to on) + (org-ql--from-to-on) + `(org-ql--predicate-clocked :from ,from :to ,to)) + (closed (&key from to on) + (org-ql--from-to-on) + `(org-ql--predicate-closed :from ,from :to ,to)) + (deadline (&key from to on) + (org-ql--from-to-on) + `(org-ql--predicate-deadline :from ,from :to ,to)) + (planning (&key from to on) + (org-ql--from-to-on) + `(org-ql--predicate-planning :from ,from :to ,to)) + (scheduled (&key from to on) + (org-ql--from-to-on) + `(org-ql--predicate-scheduled :from ,from :to ,to)) + (ts (&key from to on (type 'both)) + (org-ql--from-to-on) + `(org-ql--predicate-ts :from ,from :to ,to + :regexp ,(pcase type + ('both org-tsr-regexp-both) + ('active org-tsr-regexp) + ('inactive org-ql-tsr-regexp-inactive))))) + ,query))))) + +;;;;; String query parsing + +;; This section implements parsing of "plain," non-Lisp queries using the `peg' +;; library. NOTE: This needs to appear after the predicates are defined. + +(require 'peg) + +;; Fix compiler warnings probably caused by `peg' not using lexical-binding. +;; TODO: File bug report upstream. +(defvar peg-errors nil) +(defvar peg-stack nil) + +(defmacro org-ql--peg-parse-string (rules string &optional noerror) + "Parse STRING according to RULES." + ;; This sentence was in the docstring but Checkdoc is complaining, + ;; so moving it to a comment: "If NOERROR is non-nil, push nil + ;; resp. t if the parse failed resp. succeded instead of signaling + ;; an error." + + ;; Unfortunately, this macro was moved to peg-tests.el, so we copy it here. + `(with-temp-buffer + (insert ,string) + (goto-char (point-min)) + ,(if noerror + (let ((entry (make-symbol "entry")) + (start (caar rules))) + `(peg-parse (,entry (or (and ,start `(-- t)) "")) + . ,rules)) + `(peg-parse . ,rules)))) + +(cl-eval-when (compile load eval) + ;; This `eval-when' is necessary, otherwise the macro does not define + ;; the function correctly, apparently because `org-ql-predicates' + ;; ends up being not defined correctly at expansion time. + + (defun org-ql--def-query-string-to-sexp-fn (predicates) + "Define function `org-ql--query-string-to-sexp' according to PREDICATES. +Builds the PEG expression using PREDICATES (which should be the +value of `org-ql-predicates')." + (let* ((names (--map (symbol-name (plist-get (cdr it) :name)) + predicates)) + (aliases (->> predicates + (--map (plist-get (cdr it) :aliases)) + -non-nil + -flatten + (-map #'symbol-name))) + (predicates (->> (append names aliases) + -uniq + ;; Sort the keywords longest-first to work around what seems to be an + ;; obscure bug in `peg': when one keyword is a substring of another, + ;; and the shorter one is listed first, the shorter one fails to match. + (-sort (-on #'> #'length))))) + (fset 'org-ql--query-string-to-sexp + (byte-compile + `(cl-function + (lambda (input &optional (boolean 'and)) + "Return query parsed from plain query string INPUT. +Multiple predicates are combined with BOOLEAN." + (unless (s-blank-str? input) + (let* ((query (org-ql--peg-parse-string + ((query (+ term + (opt (+ (syntax-class whitespace) (any))))) + (term (or (and negation (list positive-term) + ;; This is a bit confusing, but it seems to work. There's probably a better way. + `(pred -- (list 'not (car pred)))) + positive-term)) + (positive-term (or (and predicate-with-args `(pred args -- (cons (intern pred) args))) + (and predicate-without-args `(pred -- (list (intern pred)))) + (and plain-string `(s -- (list 'regexp s))))) + (plain-string (or quoted-arg unquoted-arg)) + (predicate-with-args (substring predicate) ":" args) + (predicate-without-args (substring predicate) ":") + (predicate (or ,@predicates)) + (args (list (+ (and (or keyword-arg quoted-arg unquoted-arg) (opt separator))))) + (keyword-arg (and keyword "=" `(kw -- (intern (concat ":" kw))))) + (keyword (substring (+ (not (or separator "=" "\"" (syntax-class whitespace))) (any)))) + (quoted-arg "\"" (substring (+ (not (or separator "\"")) (any))) "\"") + (unquoted-arg (substring (+ (not (or separator "\"" (syntax-class whitespace))) (any)))) + (negation "!") + (separator "," )) + input 'noerror))) + ;; Discard the t that `peg-parse-string' always returns as the first + ;; element. I don't know what it means, but we don't want it. + (if (> (length (cdr query)) 1) + (cons boolean (nreverse (cdr query))) + (cadr query))))))))))) + +;;;;; Predicate definition + +(defvar org-ql-defpred-defer nil + "Defer expensive function redefinitions when defining predicates. +When non-nil, defining a predicate with `org-ql-defpred' does not +cause the functions `org-ql--normalize-query', +`org-ql--query-preamble', and `org-ql--query-string-to-sexp' to +be redefined. These functions must be redefined in order to +account for new predicates, but when defining many +predicates (like at load time), that may be deferred for +performance (after which those functions should be updated +manually; see the definition of `org-ql-defpred').") + +;; Yes, these two functions are a little hairy: `pcase' is challenging +;; enough, but splicing forms into one is something else. But it's +;; worth it to do this ugly stuff here, in one place, so the +;; `org-ql-defpred' macro becomes easy to use. + +(defun org-ql--define-normalize-query-fn (predicates) + "Define function `org-ql--normalize-query' for PREDICATES. +PREDICATES should be the value of `org-ql-predicates'." + (let ((normalizer-patterns (->> predicates + (--map (plist-get (cdr it) :normalizers)) + (-flatten-n 1)))) + (fset 'org-ql--normalize-query + (byte-compile + `(lambda (query) + "Return normalized form of QUERY expression. +This function is defined by calling +`org-ql--define-normalize-query-fn', which uses normalizer forms +defined in `org-ql-predicates' by calling `org-ql-defpred'." + (cl-labels ((rec (element) + (pcase element + (`(or . ,clauses) `(or ,@(mapcar #'rec clauses))) + (`(and . ,clauses) `(and ,@(mapcar #'rec clauses))) + (`(not . ,clauses) `(not ,@(mapcar #'rec clauses))) + (`(when ,condition . ,clauses) `(when ,(rec condition) + ,@(mapcar #'rec clauses))) + (`(unless ,condition . ,clauses) `(unless ,(rec condition) + ,@(mapcar #'rec clauses))) + ;; TODO: Combine (regexp) when appropriate (i.e. inside an OR, not an AND). + ((pred stringp) `(regexp ,element)) + + ,@normalizer-patterns + + ;; Any other form: passed through unchanged. + (_ element)))) + (rec query))))))) + +(defun org-ql--define-query-preamble-fn (predicates) + "Define function `org-ql--query-preamble' for PREDICATES. +PREDICATES should be the value of `org-ql-predicates'." + ;; NOTE: I don't how the `list' symbol ends up in the list, but anyway... + (let* ((preamble-patterns + (-flatten-n 1 (-non-nil + ;; NOTE: Using -let instead of pcase-let here because I can't make map 2.1 install in the test sandbox. + (--map (-let* (((&plist :preambles) (cdr it))) + (--map (pcase-let* ((`(,pattern ,exp) it)) + `(,pattern + (-let* (((&plist :regexp :case-fold :query) ,exp)) + (setf org-ql-preamble regexp + preamble-case-fold case-fold) + ;; NOTE: Even when `predicate' is nil, it must be returned in the pcase form. + query))) + preambles)) + predicates))))) + (fset 'org-ql--query-preamble + `(lambda (query) + "Return preamble data plist for QUERY. +The plist has the following keys: + + :preamble Regexp to search the Org buffer for to find potential + matches. If the regexp doesn't guarantee a match to + QUERY, the :query key should be an appropriate query + expression to test against the heading (i.e. usually + QUERY again). + + :preamble-case-fold What to bind variable `case-fold-search' to + around the regexp search. + + :query The query expression to use instead of QUERY (or QUERY + again, when appropriate). + +This function is defined by calling +`org-ql--define-query-preamble-fn', which uses preamble forms +defined in `org-ql-predicates' by calling `org-ql-defpred'." + (pcase org-ql-use-preamble + ('nil (list :query query :preamble nil)) + (_ (let ((preamble-case-fold t) + org-ql-preamble) + (cl-labels ((rec (element) + (or (when org-ql-preamble + ;; Only one preamble is allowed + element) + (pcase element + (`(or _) element) + + ,@preamble-patterns + + (`(and . ,rest) + (let ((clauses (mapcar #'rec rest))) + `(and ,@(-non-nil clauses)))) + (_ element))))) + (setq query (pcase (mapcar #'rec (list query)) + ((or `(nil) + `((nil)) + `((and)) + `((or))) + t) + (`(t) t) + (query (-flatten-n 1 query)))) + (list :query query :preamble org-ql-preamble :preamble-case-fold preamble-case-fold))))))) + ;; For some reason, byte-compiling the backquoted lambda form directly causes a warning + ;; that `query' refers to an unbound variable, even though that's not the case, and the + ;; function still works. But to avoid the warning, we byte-compile it afterward. + (byte-compile 'org-ql--query-preamble))) + +(cl-defmacro org-ql-defpred (name args docstring &key body preambles normalizers) + "Define an `org-ql' selector predicate named `org-ql--predicate-NAME'. +NAME may be a symbol or a list of symbols: if a list, the first +is used as NAME and the rest are aliases. A function is only +created for NAME, not for aliases, so a normalizer should be used +to replace aliases with NAME in queries (keep reading). + +ARGS is a `cl-defun'-style argument list. DOCSTRING is the +function's docstring. + +BODY is the body of the predicate. It will be evaluated with +point on the beginning of an Org heading and should return +non-nil if the heading's entry is a match. + +PREAMBLES and NORMALIZERS are lists of `pcase' forms matched +against Org QL query sexps. They are spliced into `pcase' forms +in the definitions of the functions `org-ql--query-preamble' and +`org-ql--normalize-query', which see. Those functions are +redefined when this macro is expanded, unless variable +`org-ql-defpred-defer' is non-nil, in which case those functions +should be redefined manually after defining predicates by calling +`org-ql--define-query-preamble-fn' and `org-ql--define-normalize-query-fn'. + +NORMALIZERS are used to normalize query expressions to standard +forms. For example, when the predicate has aliases, the aliases +should be replaced with predicate names using a normalizer. +Also, predicate arguments may be put into a more optimal form so +that the predicate has less work to do at query time. + +PREAMBLES refer to regular expressions which may be used to +search through a buffer directly to a potential match rather than +testing the predicate body on each heading. (Naming things is +hard.) In each `pcase' form in PREAMBLES, the `pcase' +expression (not the pattern) should be a plist with the following +keys, each value of which should be an expression which may refer +to variables bound in the pattern: + + :regexp Regular expression which searches directly to a + potential match. + + :case-fold Bound to `case-fold-search' around the regexp search. + + :query Expression which should replace the query expression, + or `query' if it should not be changed (e.g. if the + regexp is insufficient to determine whether a + heading matches, in which case the predicate's body + needs to be tested on the heading). If the regexp + guarantees a match, this may be simply t, leaving the + query expression with no work to do, which improves + performance. + +For convenience, within the `pcase' patterns, the symbol +`predicate-names' is a special form which is replaced with a +pattern matching any of the predicate's name and aliases. For +example, if NAME were: + + (heading h) + +Then if NORMALIZERS were: + + ((`(,predicate-names . ,args) + `(heading ,@args))) + +It would be expanded to: + + ((`(,(or 'heading 'h) . ,args) + `(heading ,@args)))" + ;; NOTE: The debug form works, completely! For example, use `edebug-defun' + ;; on the `heading' predicate, then evaluate this form: + ;; (let* ((query '(heading "HEADING")) + ;; (normalized (org-ql--normalize-query query)) + ;; (preamble (org-ql--query-preamble normalized))) + ;; (list :query query + ;; :normalized normalized + ;; :preamble preamble)) + (declare (debug ([&or symbolp listp] listp stringp + &rest [&or [":body" def-body] + [":normalizers" (&rest (sexp def-body))] + [":preambles" (&rest (sexp def-body))]])) + (indent defun)) + (let* ((aliases (when (listp name) + (cdr name))) + (name (cl-etypecase name + (list (car name)) + (atom name))) + (fn-name (intern (concat "org-ql--predicate-" (symbol-name name)))) + (predicate-name (intern (symbol-name name))) + (predicate-names (delq nil (cons predicate-name aliases))) + (normalizers (cl-sublis (list (cons 'predicate-names (cons 'or (--map (list 'quote it) predicate-names)))) + normalizers)) + (preambles (cl-sublis (list (cons 'predicate-names (cons 'or (--map (list 'quote it) predicate-names)))) + preambles))) + `(progn + (cl-eval-when (compile load eval) + (cl-defun ,fn-name ,args ,docstring ,body)) + (setf (map-elt org-ql-predicates ',predicate-name) + `(:name ,',name :aliases ,',aliases :fn ,',fn-name :docstring ,,docstring :args ,',args + :normalizers ,',normalizers :preambles ,',preambles)) + (unless org-ql-defpred-defer + ;; Reversing preserves the order in which predicates were defined. + (org-ql--define-normalize-query-fn (reverse org-ql-predicates)) + (org-ql--define-query-preamble-fn (reverse org-ql-predicates)) + (org-ql--def-query-string-to-sexp-fn (reverse org-ql-predicates)))))) + (defmacro org-ql--from-to-on () "For internal use. Expands into a form that processes arguments to timestamp-related @@ -1043,95 +1042,164 @@ predicates." ((pred stringp) (ts-parse-fill 'end to)) ((pred ts-p) to)))))) -(defun org-ql--byte-compile-warning (_string _pos _fill level) - "Signal an `org-ql-invalid-query' error. -Arguments STRING, POS, FILL, and LEVEL are according to -`byte-compile-log-warning-function'." - ;; Used as the `byte-compile-log-warning-function' in `org-ql--query-preamble'. - (signal 'org-ql-invalid-query level)) +;;;;;; Predicates -(defun org-ql--query-predicate (query) - "Return predicate function for QUERY." - ;; Use custom log function to prevent warnings for e.g. partially typed queries. - (let ((byte-compile-log-warning-function #'org-ql--byte-compile-warning)) - (byte-compile - `(lambda () - (cl-macrolet ((clocked (&key from to on) - (org-ql--from-to-on) - `(org-ql--predicate-clocked :from ,from :to ,to)) - (closed (&key from to on) - (org-ql--from-to-on) - `(org-ql--predicate-closed :from ,from :to ,to)) - (deadline (&key from to on) - (org-ql--from-to-on) - `(org-ql--predicate-deadline :from ,from :to ,to)) - (planning (&key from to on) - (org-ql--from-to-on) - `(org-ql--predicate-planning :from ,from :to ,to)) - (scheduled (&key from to on) - (org-ql--from-to-on) - `(org-ql--predicate-scheduled :from ,from :to ,to)) - (ts (&key from to on (type 'both)) - (org-ql--from-to-on) - `(org-ql--predicate-ts :from ,from :to ,to - :regexp ,(pcase type - ('both org-tsr-regexp-both) - ('active org-tsr-regexp) - ('inactive org-ql-tsr-regexp-inactive))))) - ,query))))) +(cl-eval-when (compile load eval) + ;; Improve load time by deferring the per-predicate preamble- and normalizer-function + ;; redefinitions until all of the predicates have been defined. + (setf org-ql-defpred-defer t)) -;;;;; Predicates - -(org-ql--defpred category (&rest categories) +(org-ql-defpred category (&rest categories) "Return non-nil if current heading is in one or more of CATEGORIES (a list of strings)." - (when-let ((category (org-get-category (point)))) - (cl-typecase categories - (null t) - (otherwise (member category categories))))) + :body (when-let ((category (org-get-category (point)))) + (cl-typecase categories + (null t) + (otherwise (member category categories))))) -(org-ql--defpred path (&rest regexps) - "Return non-nil if current heading's buffer's filename path matches any of REGEXPS (regexp strings). -Without arguments, return non-nil if buffer is file-backed." - (when (buffer-file-name) - (cl-typecase regexps - (null t) - (list (cl-loop for regexp in regexps - thereis (string-match regexp (buffer-file-name))))))) - -(org-ql--defpred todo (&rest keywords) - "Return non-nil if current heading is a TODO item. -With KEYWORDS, return non-nil if its keyword is one of KEYWORDS (a list of strings)." - (when-let ((state (org-get-todo-state))) - (cl-typecase keywords - (null (not (member state org-done-keywords))) - (list (member state keywords)) - (symbol (member state (symbol-value keywords))) - (otherwise (user-error "Invalid todo keywords: %s" keywords))))) - -(org-ql--defpred done () +(org-ql-defpred done () "Return non-nil if entry's TODO keyword is in `org-done-keywords'." ;; NOTE: This was a defsubst before being defined with the macro. Might be good to make it a defsubst again. - (or (apply #'org-ql--predicate-todo org-done-keywords))) + :body (or (apply #'org-ql--predicate-todo org-done-keywords))) -(org-ql--defpred (tags tags-all tags&) (&rest tags) - ;; NOTE: tags-all and tags& are "virtual" predicates that are handled by query pre-processing. - "Return non-nil if current heading has one or more of TAGS (a list of strings). -Tests both inherited and local tags." - (cl-macrolet ((tags-p (tags) - `(and ,tags - (not (eq 'org-ql-nil ,tags))))) - (-let* (((inherited local) (org-ql--tags-at (point)))) - (cl-typecase tags - (null (or (tags-p inherited) - (tags-p local))) - (otherwise (or (when (tags-p inherited) - (seq-intersection tags inherited)) - (when (tags-p local) - (seq-intersection tags local)))))))) +(org-ql-defpred habit () + "Return non-nil if entry is a habit." + :preambles ((`(,predicate-names) + (list :regexp (rx bol (0+ space) ":STYLE:" (1+ space) "habit" (0+ space) eol)))) + :body (org-is-habit-p)) + +(org-ql-defpred (heading h) (&rest regexps) + "Return non-nil if current entry's heading matches all REGEXPS (regexp strings)." + :normalizers ((`(,predicate-names . ,args) + ;; "h" alias. + `(heading ,@args))) + ;; MAYBE: Adjust regexp to avoid matching in tag list. + :preambles ((`(,predicate-names ,regexp) + ;; Only one regexp: match with preamble, then let predicate confirm (because + ;; the match could be in e.g. the tags rather than the heading text). + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) + ,regexp) + 'no-group) + :query query)) + (`(,predicate-names . ,regexps) + ;; Multiple regexps: use preamble to match against first + ;; regexp, then let the predicate match the rest. + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) + ,(car regexps)) + 'no-group) + :query query))) + ;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments. + :body (let ((heading (org-get-heading 'no-tags 'no-todo))) + (--all? (string-match it heading) regexps))) + +(org-ql-defpred level (level-or-comparator &optional level) + "Return non-nil if current heading's outline level matches arguments. +The following forms are accepted: + + (level NUMBER): Matches if heading level is NUMBER. + (level NUMBER NUMBER): Matches if heading level is equal to or between NUMBERs. + (level COMPARATOR NUMBER): Matches if heading level compares to NUMBER with COMPARATOR. + +COMPARATOR may be `<', `<=', `>', or `>='." + :normalizers ((`(,predicate-names . ,args) + ;; Arguments could be given as strings (e.g. from a non-Lisp query). + `(level ,@(--map (pcase it + ((or "<" "<=" ">" ">=" "=") + (intern it)) + ((pred stringp) (string-to-number it)) + (_ it)) + args)))) + :preambles ((`(,predicate-names ,comparator-or-num ,num) + (let ((repeat (pcase comparator-or-num + ('< `(repeat 1 ,(1- num) "*")) + ('<= `(repeat 1 ,num "*")) + ('> `(>= ,(1+ num) "*")) + ('>= `(>= ,num "*")) + ((pred integerp) `(repeat ,comparator-or-num ,num "*"))))) + (list :regexp (rx-to-string `(seq bol ,repeat " ") t) + :case-fold t))) + (`(,predicate-names ,num) + (list :regexp (rx-to-string `(seq bol (repeat ,num "*") " ") t) + :case-fold t))) + ;; NOTE: It might be necessary to take into account `org-odd-levels'; see docstring for + ;; `org-outline-level'. + :body (when-let ((outline-level (org-outline-level))) + (pcase level-or-comparator + ((pred numberp) (pcase level + ('nil ;; Equality + (= outline-level level-or-comparator)) + ((pred numberp) ;; Between two levels + (>= level-or-comparator outline-level level)))) + ((pred symbolp) ;; Compare with function + (funcall level-or-comparator outline-level level))))) + +(org-ql-defpred link (&rest args) + ;; User-facing argument form: (&optional description-or-target &key description target regexp-p). + "Return non-nil if current heading contains a link matching arguments. +DESCRIPTION-OR-TARGET is matched against the link's description +and target. Alternatively, one or both of DESCRIPTION and TARGET +may be matched separately. Without arguments, return non-nil if +any link is found." + ;; NOTE: It would be preferable to avoid this manual argument parsing every time the predicate + ;; is called, but pre-processing it to a normal form gets complicated with the preamble and + ;; pre-processing, because we don't want to display a query like "(link :description-or-target + ;; "FOO")" in the view header, which would be ugly. So, since preambles are expected to be + ;; enabled nearly all of the time, in which case this function won't be called anyway, it's + ;; probably not worth rewriting code all over the place to fix this. + :preambles ((`(,predicate-names) + (list :regexp ;; Match a link with a target and optionally a description. + (rx (or bol (1+ blank)) + "[[" (1+ (not (any "]"))) "]" + (optional (seq "[" (0+ (not (any "]"))) "]")) + "]" + (or eol blank)))) + (`(,predicate-names ,(and description-or-target + (guard (not (keywordp description-or-target))))) + (list :regexp (org-ql--link-regexp :description-or-target + (regexp-quote description-or-target))) + nil) + (`(,predicate-names . ,plist) + (list :regexp (org-ql--link-regexp + :description + (when (plist-get plist :description) + (regexp-quote (plist-get plist :description))) + :target (when (plist-get plist :target) + (regexp-quote (plist-get plist :target))))) + nil)) + :body (let* (plist description-or-target description target regexp-p) + (if (not (keywordp (car args))) + (setf description-or-target (car args) + plist (cdr args)) + (setf plist args)) + (setf description (plist-get plist :description) + target (plist-get plist :description) + regexp-p (plist-get plist :regexp-p)) + (unless regexp-p + ;; NOTE: It would also be preferable to avoid regexp-quoting every time this predicate + ;; is called. Ideally that would be handled in the query pre-processing step. However, + ;; handling that properly, in combination with preparing the query preamble and whether + ;; REGEXP-P is enabled, is also complicated, so let's not. + (when description-or-target + (setf description-or-target (regexp-quote description-or-target))) + (when description + (setf description (regexp-quote description))) + (when target + (setf target (regexp-quote target)))) + (when (re-search-forward org-ql-link-regexp (org-entry-end-position) t) + (pcase description-or-target + ('nil (and (or (null target) + (string-match-p target (match-string 1))) + (or (null description) + (string-match-p description (match-string org-ql-link-description-group))))) + (_ (if (and description target) + (and (string-match-p target (match-string 1)) + (string-match-p description (match-string org-ql-link-description-group))) + (or (string-match-p description-or-target (match-string 1)) + (string-match-p description-or-target + (match-string org-ql-link-description-group))))))))) ;; MAYBE: Preambles for outline-path predicates. Not sure if possible without complicated logic. -(org-ql--defpred (outline-path olp) (&rest regexps) +(org-ql-defpred (outline-path olp) (&rest regexps) "Return non-nil if current node's outline path matches all of REGEXPS. Each string is compared as a regexp to each element of the node's outline path with `string-match'. For example, if an entry's @@ -1143,11 +1211,14 @@ the following queries: (olp \"Food\" \"Fruits\") (olp \"Fruits\" \"Grapes\") (olp \"Food\" \"Grapes\")" - (let ((entry-olp (org-ql--value-at (point) #'org-ql--outline-path))) - (cl-loop for h in regexps - always (cl-member h entry-olp :test #'string-match)))) + :normalizers ((`(,predicate-names . ,strings) + ;; Regexp quote headings. + `(outline-path ,@(mapcar #'regexp-quote strings)))) + :body (let ((entry-olp (org-ql--value-at (point) #'org-ql--outline-path))) + (cl-loop for h in regexps + always (cl-member h entry-olp :test #'string-match)))) -(org-ql--defpred (outline-path-segment olps) (&rest regexps) +(org-ql-defpred (outline-path-segment olps) (&rest regexps) "Return non-nil if current node's outline path matches segment REGEXPS. Matches REGEXPS as a contiguous segment of the outline path. Each regexp is compared to each element of the node's outline @@ -1165,118 +1236,21 @@ contiguous segment of the outline path: (olp \"Food\" \"Grape\")" ;; MAYBE: Allow anchored matching. - (org-ql--infix-p regexps (org-ql--value-at (point) #'org-ql--outline-path))) + :normalizers ((`(,(or 'outline-path-segment 'olps) . ,strings) + ;; Regexp quote headings. + `(outline-path-segment ,@(mapcar #'regexp-quote strings)))) + :body (org-ql--infix-p regexps (org-ql--value-at (point) #'org-ql--outline-path))) -(org-ql--defpred (tags-inherited tags-i itags) (&rest tags) - "Return non-nil if current heading's inherited tags include one or more of TAGS (a list of strings). -If TAGS is nil, return non-nil if heading has any inherited tags." - (cl-macrolet ((tags-p (tags) - `(and ,tags - (not (eq 'org-ql-nil ,tags))))) - (-let* (((inherited _) (org-ql--tags-at (point)))) - (cl-typecase tags - (null (tags-p inherited)) - (otherwise (when (tags-p inherited) - (seq-intersection tags inherited))))))) +(org-ql-defpred path (&rest regexps) + "Return non-nil if current heading's buffer's filename path matches any of REGEXPS (regexp strings). +Without arguments, return non-nil if buffer is file-backed." + :body (when (buffer-file-name) + (cl-typecase regexps + (null t) + (list (cl-loop for regexp in regexps + thereis (string-match regexp (buffer-file-name))))))) -(org-ql--defpred (tags-local tags-l ltags) (&rest tags) - "Return non-nil if current heading's local tags include one or more of TAGS (a list of strings). -If TAGS is nil, return non-nil if heading has any local tags." - (cl-macrolet ((tags-p (tags) - `(and ,tags - (not (eq 'org-ql-nil ,tags))))) - (-let* (((_ local) (org-ql--tags-at (point)))) - (cl-typecase tags - (null (tags-p local)) - (otherwise (when (tags-p local) - (seq-intersection tags local))))))) - -(org-ql--defpred (tags-regexp tags*) (&rest regexps) - "Return non-nil if current heading has tags matching one or more of REGEXPS. -Tests both inherited and local tags." - (cl-macrolet ((tags-p (tags) - `(and ,tags - (not (eq 'org-ql-nil ,tags))))) - (-let* (((inherited local) (org-ql--tags-at (point)))) - (cl-typecase regexps - (null (or (tags-p inherited) - (tags-p local))) - (otherwise (or (when (tags-p inherited) - (cl-loop for tag in inherited - thereis (cl-loop for regexp in regexps - thereis (string-match regexp tag)))) - (when (tags-p local) - (cl-loop for tag in local - thereis (cl-loop for regexp in regexps - thereis (string-match regexp tag)))))))))) - -(org-ql--defpred level (level-or-comparator &optional level) - "Return non-nil if current heading's outline level matches arguments. -The following forms are accepted: - - (level NUMBER): Matches if heading level is NUMBER. - (level NUMBER NUMBER): Matches if heading level is equal to or between NUMBERs. - (level COMPARATOR NUMBER): Matches if heading level compares to NUMBER with COMPARATOR. - -COMPARATOR may be `<', `<=', `>', or `>='." - ;; NOTE: It might be necessary to take into account `org-odd-levels'; see docstring for - ;; `org-outline-level'. - (when-let ((outline-level (org-outline-level))) - (pcase level-or-comparator - ((pred numberp) (pcase level - ('nil ;; Equality - (= outline-level level-or-comparator)) - ((pred numberp) ;; Between two levels - (>= level-or-comparator outline-level level)))) - ((pred symbolp) ;; Compare with function - (funcall level-or-comparator outline-level level))))) - -(org-ql--defpred link (&rest args) - ;; User-facing argument form: (&optional description-or-target &key description target regexp-p). - "Return non-nil if current heading contains a link matching arguments. -DESCRIPTION-OR-TARGET is matched against the link's description -and target. Alternatively, one or both of DESCRIPTION and TARGET -may be matched separately. Without arguments, return non-nil if -any link is found." - ;; NOTE: It would be preferable to avoid this manual argument parsing every time the predicate - ;; is called, but pre-processing it to a normal form gets complicated with the preamble and - ;; pre-processing, because we don't want to display a query like "(link :description-or-target - ;; "FOO")" in the view header, which would be ugly. So, since preambles are expected to be - ;; enabled nearly all of the time, in which case this function won't be called anyway, it's - ;; probably not worth rewriting code all over the place to fix this. - (let* (plist description-or-target description target regexp-p) - (if (not (keywordp (car args))) - (setf description-or-target (car args) - plist (cdr args)) - (setf plist args)) - (setf description (plist-get plist :description) - target (plist-get plist :description) - regexp-p (plist-get plist :regexp-p)) - (unless regexp-p - ;; NOTE: It would also be preferable to avoid regexp-quoting every time this predicate - ;; is called. Ideally that would be handled in the query pre-processing step. However, - ;; handling that properly, in combination with preparing the query preamble and whether - ;; REGEXP-P is enabled, is also complicated, so let's not. - (when description-or-target - (setf description-or-target (regexp-quote description-or-target))) - (when description - (setf description (regexp-quote description))) - (when target - (setf target (regexp-quote target)))) - (when (re-search-forward org-ql-link-regexp (org-entry-end-position) t) - (pcase description-or-target - ('nil (and (or (null target) - (string-match-p target (match-string 1))) - (or (null description) - (string-match-p description (match-string org-ql-link-description-group))))) - (_ (if (and description target) - (and (string-match-p target (match-string 1)) - (string-match-p description (match-string org-ql-link-description-group))) - (or (string-match-p description-or-target (match-string 1)) - (string-match-p description-or-target - (match-string org-ql-link-description-group))))))))) - -(org-ql--defpred priority (&rest args) +(org-ql-defpred priority (&rest args) "Return non-nil if current heading has a certain priority. ARGS may be either a list of one or more priority letters as strings, or a comparator function symbol followed by a priority @@ -1296,6 +1270,42 @@ priority B)." ;; value. We do this because it doesn't seem very useful or intuitive for a ;; query like (priority "B") to match an item that has no priority cookie. ;; TODO: Convert priority arg(s) to numeric values in pre-processing. + :normalizers + ((`(,predicate-names ,(and (or '= '< '> '<= '>=) comparator) ,letter) + ;; Quote comparator. + `(priority ',comparator ,letter))) + + :preambles + (;; NOTE: This only accepts A, B, or C. I haven't seen + ;; other priorities in the wild, so this will do for now. + (`(,predicate-names) + ;; Any priority cookie. + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) "[#" (in "ABC") "]") t))) + (`(,predicate-names ,(and (or ''= ''< ''> ''<= ''>=) comparator) ,letter) + ;; Comparator and priority letter. + ;; NOTE: The double-quoted comparators. See below. + (let* ((priority-letters '("A" "B" "C")) + (index (-elem-index letter priority-letters)) + ;; NOTE: Higher priority == lower number. + ;; NOTE: Because we need to support both preamble-based queries and + ;; regular predicate ones, we work around an idiosyncrasy of query + ;; pre-processing by accepting both quoted and double-quoted comparator + ;; function symbols. Not the most elegant solution, but it works. + (priorities (s-join "" (pcase comparator + ((or '= ''=) (list letter)) + ((or '> ''>) (cl-subseq priority-letters 0 index)) + ((or '>= ''>=) (cl-subseq priority-letters 0 (1+ index))) + ((or '< ''<) (cl-subseq priority-letters (1+ index))) + ((or '<= ''<=) (cl-subseq priority-letters index)))))) + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (optional (1+ upper) (1+ blank)) + "[#" (in ,priorities) "]") t)))) + (`(,predicate-names . ,letters) + ;; One or more priorities. + ;; MAYBE: Disable case-folding. + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) + (optional (1+ upper) (1+ blank)) + "[#" (or ,@letters) "]") t)))) + :body (when-let* ((item-priority (save-excursion (save-match-data ;; TODO: Is the save-match-data above necessary? @@ -1317,29 +1327,32 @@ priority B)." (cl-loop for priority-arg in args thereis (= item-priority (* 1000 (- org-lowest-priority (string-to-char priority-arg))))))))) -(org-ql--defpred habit () - "Return non-nil if entry is a habit." - (org-is-habit-p)) - -(org-ql--defpred (regexp r) (&rest regexps) - "Return non-nil if current entry matches all of REGEXPS (regexp strings)." - (let ((end (or (save-excursion - (outline-next-heading)) - (point-max)))) - (save-excursion - (goto-char (line-beginning-position)) - (cl-loop for regexp in regexps - always (save-excursion - (re-search-forward regexp end t)))))) - -(org-ql--defpred (heading h) (&rest regexps) - "Return non-nil if current entry's heading matches all REGEXPS (regexp strings)." - ;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments. - (let ((heading (org-get-heading 'no-tags 'no-todo))) - (--all? (string-match it heading) regexps))) - -(org-ql--defpred property (property &optional value) +(org-ql-defpred property (property &optional value) "Return non-nil if current entry has PROPERTY (a string), and optionally VALUE (a string)." + :normalizers ((`(,predicate-names ,property . ,value) + ;; Convert keyword property arguments to strings. Non-sexp + ;; queries result in keyword property arguments (because to do + ;; otherwise would require ugly special-casing in the parsing). + (when (keywordp property) + (setf property (substring (symbol-name property) 1))) + (cons 'property (cons property value)))) + ;; MAYBE: Should case folding be disabled for properties? What about values? + ;; MAYBE: Support (property) without args. + :preambles ((`(,predicate-names ,property ,value) + ;; We do NOT return nil, because the predicate still needs to be tested, + ;; because the regexp could match a string not inside a property drawer. + (list :regexp (rx-to-string `(seq bol (0+ space) ":" ,property ":" + (1+ space) ,value (0+ space) eol)) + :query query)) + (`(,predicate-names ,property) + ;; We do NOT return nil, because the predicate still needs to be tested, + ;; because the regexp could match a string not inside a property drawer. + ;; NOTE: The preamble only matches if there appears to be a value. + ;; A line like ":ID: " without any other text does not match. + (list :regexp (rx-to-string `(seq bol (0+ space) ":" ,property ":" (1+ space) + (minimal-match (1+ not-newline)) eol)) + :query query))) + :body (pcase property ('nil (user-error "Property matcher requires a PROPERTY argument")) (_ (pcase value @@ -1350,10 +1363,53 @@ priority B)." ;; Check that PROPERTY has VALUE (string-equal value (org-entry-get (point) property 'selective))))))) -(org-ql--defpred src (&key regexps lang) +(org-ql-defpred (regexp r) (&rest regexps) + "Return non-nil if current entry matches all of REGEXPS (regexp strings)." + :normalizers ((`(,predicate-names . ,args) + `(regexp ,@args))) + ;; MAYBE: Separate case-sensitive (Regexp) predicate. + :preambles ((`(,predicate-names ,regexp) + (list :case-fold t :regexp regexp :query t)) + (`(,predicate-names . ,regexps) + ;; Search for first regexp, then confirm with predicate. + (list :case-fold t :regexp (car regexps) :query query))) + :body + (let ((end (or (save-excursion + (outline-next-heading)) + (point-max)))) + (save-excursion + (goto-char (line-beginning-position)) + (cl-loop for regexp in regexps + always (save-excursion + (re-search-forward regexp end t)))))) + +(org-ql-defpred src (&key regexps lang) "Return non-nil if current entry contains an Org source block matching all of REGEXPS. If keyword argument LANG is non-nil, the block must be in that language." + :normalizers ((`(,predicate-names . ,args) + ;; Rewrite to use keyword args. + (-let (regexps lang keyword-index) + (cond ((plist-get args :lang) + ;; Lang given first, or only lang given. + (setf lang (plist-get args :lang) + regexps (seq-difference args (list :lang lang)))) + ((setf keyword-index (-find-index #'keywordp args)) + ;; Regexps and lang given. + (setf lang (plist-get (cl-subseq args keyword-index) :lang) + regexps (cl-subseq args 0 keyword-index))) + (t ;; Only regexps given. + (setf regexps args))) + (when regexps + ;; This feels awkward and wrong, but we have to quote lists + ;; and avoid quoting nil. There must be a better way. + (setf regexps `(',regexps))) + `(src :lang ,lang :regexps ,@regexps)))) + :preambles ((`(,predicate-names . ,args) + (list :regexp (org-ql--format-src-block-regexp (plist-get args :lang)) + ;; Always check contents with predicate. + :query query))) + :body (catch 'return (save-excursion (save-match-data @@ -1375,6 +1431,101 @@ language." ;; No regexps to check: return non-nil. t)))))) +(org-ql-defpred (tags) (&rest tags) + "Return non-nil if current heading has one or more of TAGS (a list of strings). +Tests both inherited and local tags." + ;; MAYBE: -all versions for inherited and local. + :body (cl-macrolet ((tags-p (tags) + `(and ,tags + (not (eq 'org-ql-nil ,tags))))) + (-let* (((inherited local) (org-ql--tags-at (point)))) + (cl-typecase tags + (null (or (tags-p inherited) + (tags-p local))) + (otherwise (or (when (tags-p inherited) + (seq-intersection tags inherited)) + (when (tags-p local) + (seq-intersection tags local)))))))) + +(org-ql-defpred (tags-all tags&) (&rest tags) + "Return non-nil if current heading has all of TAGS (a list of strings). +Tests both inherited and local tags." + ;; MAYBE: -all versions for inherited and local. + :normalizers ((`(,predicate-names) `(tags)) + (`(,predicate-names . ,tags) `(and ,@(--map `(tags ,it) tags)))) + :body (apply #'org-ql--predicate-tags tags)) + +(org-ql-defpred (tags-inherited inherited-tags tags-i itags) (&rest tags) + "Return non-nil if current heading's inherited tags include one or more of TAGS (a list of strings). +If TAGS is nil, return non-nil if heading has any inherited tags." + :normalizers ((`(,predicate-names . ,tags) + `(tags-inherited ,@tags)) + (`(,predicate-names) + `(tags-inherited))) + :body (cl-macrolet ((tags-p (tags) + `(and ,tags + (not (eq 'org-ql-nil ,tags))))) + (-let* (((inherited _) (org-ql--tags-at (point)))) + (cl-typecase tags + (null (tags-p inherited)) + (otherwise (when (tags-p inherited) + (seq-intersection tags inherited))))))) + +(org-ql-defpred (tags-local local-tags tags-l ltags) (&rest tags) + "Return non-nil if current heading's local tags include one or more of TAGS (a list of strings). +If TAGS is nil, return non-nil if heading has any local tags." + :normalizers ((`(,predicate-names) `(tags-local)) + (`(,predicate-names . ,tags) `(tags-local ,@tags))) + :preambles ((`(,predicate-names . ,(and tags (guard tags))) + ;; When searching for local, non-inherited tags, we can + ;; search directly to headings containing one of the tags. + (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ space) (1+ not-newline) + ":" (or ,@tags) ":") + t) + :query t))) + :body (cl-macrolet ((tags-p (tags) + `(and ,tags + (not (eq 'org-ql-nil ,tags))))) + (-let* (((_ local) (org-ql--tags-at (point)))) + (cl-typecase tags + (null (tags-p local)) + (otherwise (when (tags-p local) + (seq-intersection tags local))))))) + +(org-ql-defpred (tags-regexp tags*) (&rest regexps) + "Return non-nil if current heading has tags matching one or more of REGEXPS. +Tests both inherited and local tags." + :normalizers ((`(,predicate-names . ,regexps) + `(tags-regexp ,@regexps))) + :body (cl-macrolet ((tags-p (tags) + `(and ,tags + (not (eq 'org-ql-nil ,tags))))) + (-let* (((inherited local) (org-ql--tags-at (point)))) + (cl-typecase regexps + (null (or (tags-p inherited) + (tags-p local))) + (otherwise (or (when (tags-p inherited) + (cl-loop for tag in inherited + thereis (cl-loop for regexp in regexps + thereis (string-match regexp tag)))) + (when (tags-p local) + (cl-loop for tag in local + thereis (cl-loop for regexp in regexps + thereis (string-match regexp tag)))))))))) + +(org-ql-defpred todo (&rest keywords) + "Return non-nil if current heading is a TODO item. +With KEYWORDS, return non-nil if its keyword is one of KEYWORDS (a list of strings)." + ;; TODO: Can we make a preamble for plain (todo) queries? + :preambles ((`(,predicate-names . ,(and todo-keywords (guard todo-keywords))) + (list :case-fold nil :regexp (rx-to-string `(seq bol (1+ "*") (1+ space) (or ,@todo-keywords) (or " " eol)) t)))) + :body (when-let ((state (org-get-todo-state))) + (cl-typecase keywords + (null (not (member state org-done-keywords))) + (list (member state keywords)) + (symbol (member state (symbol-value keywords))) + (otherwise (user-error "Invalid todo keywords: %s" keywords))))) + ;;;;;; Ancestor/descendant ;; These predicates search ancestor and descendant headings for sub-queries. @@ -1393,14 +1544,20 @@ language." ;; rather than user-facing, since their arguments are predicates provided ;; automatically by `--pre-process-query'. -(org-ql--defpred ancestors (predicate) +(org-ql-defpred ancestors (predicate) "Return non-nil if any of current entry's ancestors satisfy PREDICATE." + :normalizers ((`(,predicate-names ,query) `(ancestors ,(org-ql--query-predicate (rec query)))) + (`(,predicate-names) '(ancestors (lambda () t)))) + :body (org-with-wide-buffer (cl-loop while (org-up-heading-safe) thereis (org-ql--value-at (point) predicate)))) -(org-ql--defpred parent (predicate) +(org-ql-defpred parent (predicate) "Return non-nil if the current entry's parent satisfies PREDICATE." + :normalizers ((`(,predicate-names ,query) `(parent ,(org-ql--query-predicate (rec query)))) + (`(,predicate-names) '(parent (lambda () t)))) + :body (org-with-wide-buffer (when (org-up-heading-safe) (org-ql--value-at (point) predicate)))) @@ -1411,8 +1568,12 @@ language." ;; on the Org file being searched and the sub-query, performance could be better or ;; worse. It should be benchmarked extensively before so changing the implementation. -(org-ql--defpred children (query) +(org-ql-defpred children (query) "Return non-nil if current entry has children matching QUERY." + ;; Quote children queries so the user doesn't have to. + :normalizers ((`(,predicate-names ,query) `(children ',query)) + (`(,predicate-names) '(children (lambda () t)))) + :body (org-with-wide-buffer ;; Widening is needed if inside an "ancestors" query (org-narrow-to-subtree) @@ -1432,10 +1593,13 @@ language." :action (lambda () (throw 'found t)))))))) -(org-ql--defpred descendants (query) +(org-ql-defpred descendants (query) "Return non-nil if current entry has descendants matching QUERY." ;; TODO: This could probably be rewritten like the `ancestors' predicate, ;; which avoids calling `org-ql-select' recursively and its associated overhead. + :normalizers ((`(,predicate-names ,query) `(descendants ',query)) + (`(,predicate-names) '(descendants (lambda () t)))) + :body (org-with-wide-buffer (org-narrow-to-subtree) (when (org-goto-first-child) @@ -1454,13 +1618,13 @@ language." ;; NOTE: These docstrings apply to the functions defined by `org-ql--defpref', ;; not necessarily to the way users are expected to call them in queries. The -;; queries are pre-processed by `org-ql--pre-process-query' to handle +;; queries are pre-processed by `org-ql--normalize-query' to handle ;; arguments which are constant during a query's execution. ;; TODO: Update the macro to define a user-facing docstring so I don't ;; have to manually update the documentation. -(org-ql--defpred clocked (&key from to _on) +(org-ql-defpred clocked (&key from to _on) ;; The underscore before `on' prevents "unused lexical variable" ;; warnings, because we pre-process that argument in a macro before ;; this function is called. @@ -1477,9 +1641,20 @@ If ON, return non-nil if entry has a timestamp on date ON. FROM, TO, and ON should be either `ts' structs, or strings parseable by `parse-time-string' which may omit the time value." + :normalizers ((`(,predicate-names ,(and num-days (pred numberp))) + ;; (clocked) and (closed) implicitly look into the past. + (let ((from (->> (ts-now) + (ts-adjust 'day (* -1 num-days)) + (ts-apply :hour 0 :minute 0 :second 0)))) + `(clocked :from ,from)))) + :preambles ((`(,predicate-names ,(pred numberp)) + (list :regexp org-ql-clock-regexp :query t)) + (`(,predicate-names) + (list :regexp org-ql-clock-regexp :query t))) + :body (org-ql--predicate-ts :from from :to to :regexp org-ql-clock-regexp :match-group 1)) -(org-ql--defpred closed (&key from to _on) +(org-ql-defpred closed (&key from to _on) ;; The underscore before `on' prevents "unused lexical variable" ;; warnings, because we pre-process that argument in a macro before ;; this function is called. @@ -1496,10 +1671,20 @@ If ON, return non-nil if entry has a timestamp on date ON. FROM, TO, and ON should be either `ts' structs, or strings parseable by `parse-time-string' which may omit the time value." + :normalizers ((`(,predicate-names ,(and num-days (pred numberp))) + ;; (clocked) and (closed) implicitly look into the past. + (let ((from (->> (ts-now) + (ts-adjust 'day (* -1 num-days)) + (ts-apply :hour 0 :minute 0 :second 0)))) + `(closed :from ,from)))) + :preambles ((`(,predicate-names . ,_) + ;; Predicate still needs testing. + (list :regexp org-closed-time-regexp :query query))) + :body (org-ql--predicate-ts :from from :to to :regexp org-closed-time-regexp :match-group 1 :limit (line-end-position 2))) -(org-ql--defpred deadline (&key from to _on) +(org-ql-defpred deadline (&key from to _on) ;; The underscore before `on' prevents "unused lexical variable" ;; warnings, because we pre-process that argument in a macro before ;; this function is called. @@ -1516,11 +1701,29 @@ If ON, return non-nil if entry has a timestamp on date ON. FROM, TO, and ON should be either `ts' structs, or strings parseable by `parse-time-string' which may omit the time value." + :normalizers ((`(,predicate-names auto) + ;; Use `org-deadline-warning-days' as the :to arg. + (let ((to (->> (ts-now) + (ts-adjust 'day org-deadline-warning-days) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(deadline-warning :to ,to))) + (`(,predicate-names ,(and num-days (pred numberp))) + (let ((to (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(deadline :to ,to)))) + ;; NOTE: Does this normalizer cause the preamble to not be used? (Adding one to the deadline-warning definition to be sure.) + :preambles ((`(,predicate-names . ,_) + (list :regexp org-deadline-time-regexp :query query))) + :body (org-ql--predicate-ts :from from :to to :regexp org-deadline-time-regexp :match-group 1 :limit (line-end-position 2))) -(org-ql--defpred deadline-warning (&key from to) +(org-ql-defpred deadline-warning (&key from to) "Internal selector used to handle `org-deadline-warning-days' and deadlines with warning periods." + :preambles ((`(,predicate-names . ,_) + (list :regexp org-deadline-time-regexp :query query))) + :body (save-excursion (forward-line 1) (when (re-search-forward org-deadline-time-regexp (line-end-position) t) @@ -1535,7 +1738,7 @@ parseable by `parse-time-string' which may omit the time value." ((_timestamp (&keys :warning-value :warning-unit)) deadline-ts-element) (ts (ts-parse-org-element deadline-ts-element))) (pcase warning-unit - ('nil ;; Deadline has no warning unit: compare with ts passed in. + ('nil ;; Deadline has no warning unit: compare with ts passed in. (cond ((and from to) (ts-in from to ts)) (from (ts<= from ts)) (to (ts<= ts to)))) @@ -1544,7 +1747,7 @@ parseable by `parse-time-string' which may omit the time value." (ts<= (->> ts (ts-adjust unit (- warning-value))) org-ql--today)) ('week (ts<= (->> ts (ts-adjust 'day (* -7 warning-value))) org-ql--today))))))) -(org-ql--defpred planning (&key from to _on) +(org-ql-defpred planning (&key from to _on) ;; The underscore before `on' prevents "unused lexical variable" ;; warnings, because we pre-process that argument in a macro before ;; this function is called. @@ -1561,10 +1764,18 @@ If ON, return non-nil if entry has a timestamp on date ON. FROM, TO, and ON should be either `ts' structs, or strings parseable by `parse-time-string' which may omit the time value." + :normalizers ((`(,predicate-names ,(and num-days (pred numberp))) + (let ((to (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(planning :to ,to)))) + :preambles ((`(,predicate-names . ,_) + (list :regexp org-ql-planning-regexp :query query))) + :body (org-ql--predicate-ts :from from :to to :regexp org-ql-planning-regexp :match-group 1 :limit (line-end-position 2))) -(org-ql--defpred scheduled (&key from to _on) +(org-ql-defpred scheduled (&key from to _on) ;; The underscore before `on' prevents "unused lexical variable" ;; warnings, because we pre-process that argument in a macro before ;; this function is called. @@ -1581,12 +1792,20 @@ If ON, return non-nil if entry has a timestamp on date ON. FROM, TO, and ON should be either `ts' structs, or strings parseable by `parse-time-string' which may omit the time value." + :normalizers ((`(,predicate-names ,(and num-days (pred numberp))) + (let ((to (->> (ts-now) + (ts-adjust 'day num-days) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(scheduled :to ,to)))) + :preambles ((`(,predicate-names . ,_) + (list :regexp org-scheduled-time-regexp :query query))) + :body (org-ql--predicate-ts :from from :to to :regexp org-scheduled-time-regexp :match-group 1 :limit (line-end-position 2))) -(org-ql--defpred (ts ts-active ts-a ts-inactive ts-i) +(org-ql-defpred (ts ts-active ts-a ts-inactive ts-i) (&key from to _on regexp (match-group 0) (limit (org-entry-end-position))) - ;; NOTE: Arguments to this predicate are pre-processed in `org-ql--pre-process-query'. + ;; NOTE: Arguments to this predicate are pre-processed in `org-ql--normalize-query'. ;; The underscore before `on' prevents "unused lexical variable" warnings due to the ;; pre-processing converting that argument to FROM and TO. The `regexp' argument is ;; also provided by the pre-processing and is not to be given by the user. FROM and @@ -1613,7 +1832,23 @@ the end of the entry, i.e. the position returned by `org-entry-end-position', but for certain searches it should be bound to a different positiion, e.g. for planning lines, the end of the line after the heading." + ;; MAYBE: Define active/inactive ones separately? + :normalizers ((`(,(or 'ts-active 'ts-a) . ,rest) `(ts :type active ,@rest)) + (`(,(or 'ts-inactive 'ts-i) . ,rest) `(ts :type inactive ,@rest))) + :preambles ((`(,predicate-names . ,rest) + (list :regexp (pcase (plist-get rest :type) + ((or 'nil 'both) org-tsr-regexp-both) + ('active org-tsr-regexp) + ('inactive org-ql-tsr-regexp-inactive)) + ;; Predicate needs testing only when args are present. + :query (-let (((&keys :from :to :on) rest)) + ;; FIXME: This used to be (when (or from to on) query), but that doesn't seem right, so I + ;; changed it to this if, and the tests pass either way. Might deserve a little scrutiny. + (if (or from to on) + query + t))))) ;; TODO: DRY this with the clocked predicate. + :body (cl-macrolet ((next-timestamp () `(when (re-search-forward regexp limit t) (ts-parse-org (match-string match-group)))) @@ -1627,6 +1862,14 @@ of the line after the heading." (from (test-timestamps (ts<= from next-ts))) (to (test-timestamps (ts<= next-ts to))))))) +;; Predicates defined: stop deferring and define normalizer and preamble functions now. +(setf org-ql-defpred-defer nil) +;; Reversing preserves the order in which they were defined. +;; Generally it shouldn't matter, but it might... +(org-ql--define-normalize-query-fn (reverse org-ql-predicates)) +(org-ql--define-query-preamble-fn (reverse org-ql-predicates)) +(org-ql--def-query-string-to-sexp-fn (reverse org-ql-predicates)) + ;;;;; Sorting ;; TODO: These appear to work properly, but it would be good to have tests for them. @@ -1720,94 +1963,6 @@ element should be a regexp string." always (string-match i l)) do (pop list))) -;;;;; Plain query parsing - -;; This section implements parsing of "plain," non-Lisp queries using the `peg' -;; library. NOTE: This needs to appear after the predicates are defined. - -;; TODO: Rename "plain" to "string", or something like that. - -(require 'peg) - -;; Fix compiler warnings probably caused by `peg' not using lexical-binding. -;; TODO: File bug report upstream. -(defvar peg-errors) -(defvar peg-stack) - -(defmacro org-ql--peg-parse-string (rules string &optional noerror) - "Parse STRING according to RULES." - ;; This sentence was in the docstring but Checkdoc is complaining, - ;; so moving it to a comment: "If NOERROR is non-nil, push nil - ;; resp. t if the parse failed resp. succeded instead of signaling - ;; an error." - - ;; Unfortunately, this macro was moved to peg-tests.el, so we copy it here. - `(with-temp-buffer - (insert ,string) - (goto-char (point-min)) - ,(if noerror - (let ((entry (make-symbol "entry")) - (start (caar rules))) - `(peg-parse (,entry (or (and ,start `(-- t)) "")) - . ,rules)) - `(peg-parse . ,rules)))) - -(cl-eval-when (compile load eval) - ;; This `eval-when' is necessary, otherwise the macro does not define - ;; the function correctly, apparently because `org-ql-predicates' - ;; ends up being not defined correctly at expansion time. - - (defmacro org-ql--def-plain-query-fn () - "Define function `org-ql--plain-query'. -Builds the PEG expression using predicates defined in -`org-ql-predicates' and `org-ql-predicates-extra-aliases'." - (let* ((predicates (--map (symbol-name (plist-get it :name)) - org-ql-predicates)) - (aliases (->> org-ql-predicates - (--map (plist-get it :aliases)) - -non-nil - -flatten - (-map #'symbol-name))) - (predicates (->> (append predicates aliases) - -uniq - ;; Sort the keywords longest-first to work around what seems to be an - ;; obscure bug in `peg': when one keyword is a substring of another, - ;; and the shorter one is listed first, the shorter one fails to match. - (-sort (-on #'> #'length))))) - `(cl-defun org-ql--plain-query (input &optional (boolean 'and)) - "Return query parsed from plain query string INPUT. -Multiple predicates are combined with BOOLEAN." - (unless (s-blank-str? input) - (let* ((query (org-ql--peg-parse-string - ((query (+ term - (opt (+ (syntax-class whitespace) (any))))) - (term (or (and negation (list positive-term) - ;; This is a bit confusing, but it seems to work. There's probably a better way. - `(pred -- (list 'not (car pred)))) - positive-term)) - (positive-term (or (and predicate-with-args `(pred args -- (cons (intern pred) args))) - (and predicate-without-args `(pred -- (list (intern pred)))) - (and plain-string `(s -- (list 'regexp s))))) - (plain-string (or quoted-arg unquoted-arg)) - (predicate-with-args (substring predicate) ":" args) - (predicate-without-args (substring predicate) ":") - (predicate (or ,@predicates)) - (args (list (+ (and (or keyword-arg quoted-arg unquoted-arg) (opt separator))))) - (keyword-arg (and keyword "=" `(kw -- (intern (concat ":" kw))))) - (keyword (substring (+ (not (or separator "=" "\"" (syntax-class whitespace))) (any)))) - (quoted-arg "\"" (substring (+ (not (or separator "\"")) (any))) "\"") - (unquoted-arg (substring (+ (not (or separator "\"" (syntax-class whitespace))) (any)))) - (negation "!") - (separator "," )) - input 'noerror))) - ;; Discard the t that `peg-parse-string' always returns as the first - ;; element. I don't know what it means, but we don't want it. - (if (> (length (cdr query)) 1) - (cons boolean (nreverse (cdr query))) - (cadr query))))))) - - (org-ql--def-plain-query-fn)) - ;; And now we go the other direction... (defun org-ql--query-sexp-to-string (query) diff --git a/org-ql.info b/org-ql.info index abfce7a..1d693c3 100644 --- a/org-ql.info +++ b/org-ql.info @@ -63,6 +63,7 @@ Functions / Macros * Agenda-like views:: * Listing / acting-on results:: +* Custom predicates:: Changelog @@ -565,11 +566,13 @@ File: README.info, Node: Functions / Macros, Next: Dynamic block, Prev: Queri • • • - • • • + • • • • • + • * Menu: * Agenda-like views:: * Listing / acting-on results:: +* Custom predicates::  File: README.info, Node: Agenda-like views, Next: Listing / acting-on results, Up: Functions / Macros @@ -606,7 +609,7 @@ File: README.info, Node: Agenda-like views, Next: Listing / acting-on results, the block header, otherwise the header is formed automatically.  -File: README.info, Node: Listing / acting-on results, Prev: Agenda-like views, Up: Functions / Macros +File: README.info, Node: Listing / acting-on results, Next: Custom predicates, Prev: Agenda-like views, Up: Functions / Macros 4.3.2 Listing / acting-on results --------------------------------- @@ -737,6 +740,84 @@ File: README.info, Node: Listing / acting-on results, Prev: Agenda-like views, Expands into a call to ‘org-ql-select’ with the same arguments. For convenience, arguments should be unquoted. + +File: README.info, Node: Custom predicates, Prev: Listing / acting-on results, Up: Functions / Macros + +4.3.3 Custom predicates +----------------------- + + • See: Custom predicate tutorial (examples/defpred.org) + + 1. Macro: org-ql-defpred + + _Arguments:_ ‘(name args docstring &key body preambles + normalizers)’ + + Define an ‘org-ql’ selector predicate named + ‘org-ql--predicate-NAME’. ‘NAME’ may be a symbol or a list of + symbols: if a list, the first is used as ‘NAME’ and the rest are + aliases. ‘A’ function is only created for ‘NAME’, not for aliases, + so a normalizer should be used to replace aliases with ‘NAME’ in + queries (keep reading). + + ‘ARGS’ is a ‘cl-defun’-style argument list. ‘DOCSTRING’ is the + function’s docstring. + + ‘BODY’ is the body of the predicate. It will be evaluated with + point on the beginning of an Org heading and should return non-nil + if the heading’s entry is a match. + + ‘PREAMBLES’ and ‘NORMALIZERS’ are lists of ‘pcase’ forms matched + against Org ‘QL’ query sexps. They are spliced into ‘pcase’ forms + in the definitions of the functions ‘org-ql--query-preamble’ and + ‘org-ql--normalize-query’, which see. Those functions are + redefined when this macro is expanded, unless variable + ‘org-ql-defpred-defer’ is non-nil, in which case those functions + should be redefined manually after defining predicates by calling + ‘org-ql--define-query-preamble-fn’ and + ‘org-ql--define-normalize-query-fn’. + + ‘NORMALIZERS’ are used to normalize query expressions to standard + forms. For example, when the predicate has aliases, the aliases + should be replaced with predicate names using a normalizer. Also, + predicate arguments may be put into a more optimal form so that the + predicate has less work to do at query time. + + ‘PREAMBLES’ refer to regular expressions which may be used to + search through a buffer directly to a potential match rather than + testing the predicate body on each heading. (Naming things is + hard.) In each ‘pcase’ form in ‘PREAMBLES’, the ‘pcase’ expression + (not the pattern) should be a plist with the following keys, each + value of which should be an expression which may refer to variables + bound in the pattern: + + ‘:regexp’ Regular expression which searches directly to a potential + match. + + ‘:case-fold’ Bound to ‘case-fold-search’ around the regexp search. + + ‘:query’ Expression which should replace the query expression, or + ‘query’ if it should not be changed (e.g. if the regexp is + insufficient to determine whether a heading matches, in which case + the predicate’s body needs to be tested on the heading). If the + regexp guarantees a match, this may be simply ‘t’, leaving the + query expression with no work to do, which improves performance. + + For convenience, within the ‘pcase’ patterns, the symbol + ‘predicate-names’ is a special form which is replaced with a + pattern matching any of the predicate’s name and aliases. For + example, if ‘NAME’ were: + + ‘(heading h)’ + + Then if ‘NORMALIZERS’ were: + + ‘((`(,predicate-names . ,args) `(heading ,@args)))’ + + It would be expanded to: + + ‘((`(,(or 'heading 'h) . ,args) `(heading ,@args)))’ +  File: README.info, Node: Dynamic block, Next: Links, Prev: Functions / Macros, Up: Usage @@ -859,7 +940,15 @@ File: README.info, Node: 06-pre, Next: 05, Up: Changelog 5.1 0.6-pre =========== -Nothing new yet. +*Added* + • Macro org-ql-defpred, used to define search predicates. (See + tutorial (examples/defpred.org).) + + *Internal* + • Predicates are now defined more cleanly with a macro + (org-ql-defpred) that consolidates functionality related to each + predicate. This will also allow users to more easily define custom + predicates.  File: README.info, Node: 05, Next: 049, Prev: 06-pre, Up: Changelog @@ -1348,54 +1437,55 @@ GPLv3  Tag Table: Node: Top225 -Node: Contents1606 -Node: Screenshots1729 -Node: Installation1847 -Node: Quelpa2485 -Node: Usage2928 -Node: Commands3326 -Node: org-ql-search3799 -Node: helm-org-ql5449 -Node: org-ql-view5861 -Node: org-ql-view-sidebar6361 -Node: org-ql-view-recent-items6717 -Node: org-ql-sparse-tree7201 -Node: Queries8001 -Node: Non-sexp query syntax9112 -Node: General predicates10819 -Node: Ancestor/descendant predicates16232 -Node: Date/time predicates17360 -Node: Functions / Macros20015 -Node: Agenda-like views20270 -Node: Listing / acting-on results21675 -Node: Dynamic block26870 -Node: Links29568 -Node: Tips30255 -Node: Changelog30573 -Node: 06-pre31269 -Node: 0531375 -Node: 04932852 -Node: 04833126 -Node: 04733473 -Node: 04633868 -Node: 04534268 -Node: 04434627 -Node: 04334984 -Node: 04235179 -Node: 04135340 -Node: 0435581 -Node: 03239514 -Node: 03139893 -Node: 0340090 -Node: 02343065 -Node: 02243293 -Node: 02143561 -Node: 0243760 -Node: 0147795 -Node: Notes47896 -Node: Comparison with Org Agenda searches48058 -Node: org-sidebar48930 -Node: License49209 +Node: Contents1628 +Node: Screenshots1751 +Node: Installation1869 +Node: Quelpa2507 +Node: Usage2950 +Node: Commands3348 +Node: org-ql-search3821 +Node: helm-org-ql5471 +Node: org-ql-view5883 +Node: org-ql-view-sidebar6383 +Node: org-ql-view-recent-items6739 +Node: org-ql-sparse-tree7223 +Node: Queries8023 +Node: Non-sexp query syntax9134 +Node: General predicates10841 +Node: Ancestor/descendant predicates16254 +Node: Date/time predicates17382 +Node: Functions / Macros20037 +Node: Agenda-like views20335 +Node: Listing / acting-on results21740 +Node: Custom predicates26961 +Node: Dynamic block30452 +Node: Links33150 +Node: Tips33837 +Node: Changelog34155 +Node: 06-pre34851 +Node: 0535291 +Node: 04936768 +Node: 04837042 +Node: 04737389 +Node: 04637784 +Node: 04538184 +Node: 04438543 +Node: 04338900 +Node: 04239095 +Node: 04139256 +Node: 0439497 +Node: 03243430 +Node: 03143809 +Node: 0344006 +Node: 02346981 +Node: 02247209 +Node: 02147477 +Node: 0247676 +Node: 0151711 +Node: Notes51812 +Node: Comparison with Org Agenda searches51974 +Node: org-sidebar52846 +Node: License53125  End Tag Table diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index c9d560e..3aebd3e 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -50,7 +50,7 @@ Set at runtime by test suite.") ('org-ql 'org-ql) ('org-ql-expect t) ('org-ql--query-preamble 'query-preamble) - ('org-ql--pre-process-query t) + ('org-ql--normalize-query t) (_ nil))) (result (pcase sexp (`(org-ql-expect ,args) @@ -63,7 +63,7 @@ Set at runtime by test suite.") :action (org-ql-test-org-get-heading)))) (`(org-ql-select . _) (org-ql-test--format-result--ql sexp)) (`(org-ql--query-preamble . _) (org-ql-test--format-result--query-preamble sexp)) - (`(org-ql--pre-process-query . _) (format "'%S" (eval sexp))) + (`(org-ql--normalize-query . _) (format "'%S" (eval sexp))) (_ nil)))) (progn (backward-char 1) @@ -222,61 +222,75 @@ RESULTS should be a list of strings as returned by (describe "(level)" (it "with one level" - (expect (org-ql--pre-process-query '(level "1")) + (expect (org-ql--normalize-query '(level "1")) :to-equal '(level 1))) (it "with two levels" - (expect (org-ql--pre-process-query '(level "1" "2")) + (expect (org-ql--normalize-query '(level "1" "2")) :to-equal '(level 1 2))) (it "with a comparator and a level" - (expect (org-ql--pre-process-query '(level ">" "1")) + (expect (org-ql--normalize-query '(level ">" "1")) :to-equal '(level > 1)))) (describe "(link)" (it "with one argument" - (expect (org-ql--pre-process-query '(link "DESC-OR-TARGET")) + (expect (org-ql--normalize-query '(link "DESC-OR-TARGET")) :to-equal '(link "DESC-OR-TARGET"))) (it "with one argument and :regexp-p" - (expect (org-ql--pre-process-query '(link "DESC-OR-TARGET" :regexp-p t)) + (expect (org-ql--normalize-query '(link "DESC-OR-TARGET" :regexp-p t)) :to-equal '(link "DESC-OR-TARGET" :regexp-p t))) (it "with keyword arguments" - (expect (org-ql--pre-process-query '(link :description "DESCRIPTION" :target "TARGET" - :regexp-p t)) + (expect (org-ql--normalize-query '(link :description "DESCRIPTION" :target "TARGET" + :regexp-p t)) :to-equal '(link :description "DESCRIPTION" :target "TARGET" :regexp-p t)))) - (expect (org-ql--pre-process-query '(and "string1" "string2")) + (expect (org-ql--normalize-query '(and "string1" "string2")) :to-equal '(and (regexp "string1") (regexp "string2"))) - (expect (org-ql--pre-process-query '(or "string1" "string2")) + (expect (org-ql--normalize-query '(or "string1" "string2")) :to-equal '(or (regexp "string1") (regexp "string2"))) - (expect (org-ql--pre-process-query '(and (todo "TODO") - (or "string1" "string2"))) + (expect (org-ql--normalize-query '(and (todo "TODO") + (or "string1" "string2"))) :to-equal '(and (todo "TODO") (or (regexp "string1") (regexp "string2")))) - (expect (org-ql--pre-process-query '(when (todo "TODO") - (or "string1" "string2"))) + (expect (org-ql--normalize-query '(when (todo "TODO") + (or "string1" "string2"))) :to-equal '(when (todo "TODO") (or (regexp "string1") (regexp "string2")))) - (expect (org-ql--pre-process-query '(when "string-cond1" - (or "string1" "string2"))) + (expect (org-ql--normalize-query '(when "string-cond1" + (or "string1" "string2"))) :to-equal '(when (regexp "string-cond1") (or (regexp "string1") (regexp "string2")))) - (expect (org-ql--pre-process-query '(when (and "string-cond1" "string-cond2") - (or "string1" "string2"))) + (expect (org-ql--normalize-query '(when (and "string-cond1" "string-cond2") + (or "string1" "string2"))) :to-equal '(when (and (regexp "string-cond1") (regexp "string-cond2")) (or (regexp "string1") (regexp "string2")))) - (expect (org-ql--pre-process-query '(unless (and "stringcondition1" "stringcond2") - (or "string1" "string2"))) + (expect (org-ql--normalize-query '(unless (and "stringcondition1" "stringcond2") + (or "string1" "string2"))) :to-equal '(unless (and (regexp "stringcondition1") (regexp "stringcond2")) (or (regexp "string1") (regexp "string2")))) - (expect (org-ql--pre-process-query '(or (ts-active :on "2019-01-01") - (ts-a :on "2019-01-01") - (ts-inactive :on "2019-01-01") - (ts-i :on "2019-01-01"))) + (expect (org-ql--normalize-query '(or (ts-active :on "2019-01-01") + (ts-a :on "2019-01-01") + (ts-inactive :on "2019-01-01") + (ts-i :on "2019-01-01"))) :to-equal '(or (ts :type active :on "2019-01-01") (ts :type active :on "2019-01-01") (ts :type inactive :on "2019-01-01") (ts :type inactive :on "2019-01-01")))) - (describe "Query optimizing" + (describe "Query preambles" ;; TODO: Other predicates. + (describe "(clocked)" + (it "without arguments" + (expect (org-ql--query-preamble '(clocked)) + :to-equal (list :query t + :preamble org-ql-clock-regexp + :preamble-case-fold nil))) + (it "with a number of days" + (expect (org-ql--query-preamble '(clocked 1)) + :to-equal (list :query t + :preamble org-ql-clock-regexp + :preamble-case-fold nil))) + ;; TODO: Other arguments for (clocked). + ) + (describe "(level)" (it "with a number" (expect (org-ql--query-preamble '(level 2)) @@ -314,60 +328,60 @@ RESULTS should be a list of strings as returned by ;; TODO: Other predicates. (it "Negated terms" - (expect (org-ql--plain-query "todo: !todo:CHECK,SOMEDAY") + (expect (org-ql--query-string-to-sexp "todo: !todo:CHECK,SOMEDAY") :to-equal '(and (todo) (not (todo "CHECK" "SOMEDAY")))) - (expect (org-ql--plain-query "!todo:CHECK,SOMEDAY todo:") + (expect (org-ql--query-string-to-sexp "!todo:CHECK,SOMEDAY todo:") :to-equal '(and (not (todo "CHECK" "SOMEDAY")) (todo))) - (expect (org-ql--plain-query "tags:universe !moon") + (expect (org-ql--query-string-to-sexp "tags:universe !moon") :to-equal '(and (tags "universe") (not (regexp "moon")))) - (expect (org-ql--plain-query "!moon tags:universe") + (expect (org-ql--query-string-to-sexp "!moon tags:universe") :to-equal '(and (not (regexp "moon")) (tags "universe"))) - (expect (org-ql--plain-query "mars !ts:on=today") + (expect (org-ql--query-string-to-sexp "mars !ts:on=today") :to-equal '(and (regexp "mars") (not (ts :on "today")))) - (expect (org-ql--plain-query "!\"quoted phrase\"") + (expect (org-ql--query-string-to-sexp "!\"quoted phrase\"") :to-equal '(not (regexp "quoted phrase")))) (it "Regexp predicates" - (expect (org-ql--plain-query "scheduled") + (expect (org-ql--query-string-to-sexp "scheduled") ;; No colon after keyword, so not a predicate query. :to-equal '(regexp "scheduled")) - (expect (org-ql--plain-query "\"quoted phrase\"") + (expect (org-ql--query-string-to-sexp "\"quoted phrase\"") :to-equal '(regexp "quoted phrase")) - (expect (org-ql--plain-query "regexp:word") + (expect (org-ql--query-string-to-sexp "regexp:word") :to-equal '(regexp "word")) - (expect (org-ql--plain-query "regexp:\"quoted phrase\"") + (expect (org-ql--query-string-to-sexp "regexp:\"quoted phrase\"") :to-equal '(regexp "quoted phrase"))) (it "Timestamp-based predicates" - (expect (org-ql--plain-query "scheduled:on=2017-07-07") + (expect (org-ql--query-string-to-sexp "scheduled:on=2017-07-07") :to-equal '(scheduled :on "2017-07-07")) - (expect (org-ql--plain-query "deadline:from=2017-07-07,to=2017-07-09") + (expect (org-ql--query-string-to-sexp "deadline:from=2017-07-07,to=2017-07-09") :to-equal '(deadline :from "2017-07-07" :to "2017-07-09")) - (expect (org-ql--plain-query "planning:from=2017-07-07") + (expect (org-ql--query-string-to-sexp "planning:from=2017-07-07") :to-equal '(planning :from "2017-07-07")) - (expect (org-ql--plain-query "closed:from=2017-07-07") + (expect (org-ql--query-string-to-sexp "closed:from=2017-07-07") :to-equal '(closed :from "2017-07-07")) - (expect (org-ql--plain-query "ts-active:to=2017-07-07") + (expect (org-ql--query-string-to-sexp "ts-active:to=2017-07-07") :to-equal '(ts-active :to "2017-07-07")) - (expect (org-ql--plain-query "ts-inactive:to=2017-07-07") + (expect (org-ql--query-string-to-sexp "ts-inactive:to=2017-07-07") :to-equal '(ts-inactive :to "2017-07-07")) - (expect (org-ql--plain-query "ts-a:to=2017-07-07") + (expect (org-ql--query-string-to-sexp "ts-a:to=2017-07-07") :to-equal '(ts-a :to "2017-07-07")) - (expect (org-ql--plain-query "ts-i:on=2017-07-07") + (expect (org-ql--query-string-to-sexp "ts-i:on=2017-07-07") :to-equal '(ts-i :on "2017-07-07")) - (expect (org-ql--plain-query "ts:") + (expect (org-ql--query-string-to-sexp "ts:") :to-equal '(ts)) - (expect (org-ql--plain-query "clocked:") + (expect (org-ql--query-string-to-sexp "clocked:") :to-equal '(clocked))) (it "To-do predicates" - (expect (org-ql--plain-query "todo:") + (expect (org-ql--query-string-to-sexp "todo:") :to-equal '(todo)) - (expect (org-ql--plain-query "todo:TODO") + (expect (org-ql--query-string-to-sexp "todo:TODO") :to-equal '(todo "TODO")) - (expect (org-ql--plain-query "todo:TODO,SOMEDAY") + (expect (org-ql--query-string-to-sexp "todo:TODO,SOMEDAY") :to-equal '(todo "TODO" "SOMEDAY"))) (it "Compound queries" - (expect (org-ql--plain-query "todo:SOMEDAY ts-a:from=2020-01-01,to=2021-01-01") + (expect (org-ql--query-string-to-sexp "todo:SOMEDAY ts-a:from=2020-01-01,to=2021-01-01") :to-equal '(and (todo "SOMEDAY") (ts-a :from "2020-01-01" :to "2021-01-01"))) - (expect (org-ql--plain-query "regexp:\"quoted phrase\" todo:SOMEDAY") + (expect (org-ql--query-string-to-sexp "regexp:\"quoted phrase\" todo:SOMEDAY") :to-equal '(and (regexp "quoted phrase") (todo "SOMEDAY"))))) (describe "Convert sexp queries to non-sexp queries" @@ -842,9 +856,9 @@ RESULTS should be a list of strings as returned by (org-ql-expect ('(scheduled :to today)) '("Skype with president of Antarctica" "Practice leaping tall buildings in a single bound" "Order a pizza" "Get haircut" "Fix flux capacitor" "Shop for groceries" "Rewrite Emacs in Common Lisp"))))) - ;; TODO: Test (src) predicate. That will require modifying test data, which will be a - ;; significant hassle. Manual testing shows that the predicate appears to work properly. - + ;; ;; TODO: Test (src) predicate. That will require modifying test data, which will be a + ;; ;; significant hassle. Manual testing shows that the predicate appears to work properly. + ;; (describe "(todo)" (org-ql-it "without arguments" From a2acf93a2c4fdcfd4752aa3c71d946b882ad8a09 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 01:33:14 -0600 Subject: [PATCH 003/292] Docs: (defpred.org) Fix example --- examples/defpred.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/defpred.org b/examples/defpred.org index 86565cf..75c28b7 100644 --- a/examples/defpred.org +++ b/examples/defpred.org @@ -164,7 +164,7 @@ Can we do that? In fact, we can, by using a query normalizer. Normalizers are collect `(property "person" ,name))))) :body (cl-loop for name in names thereis (or (property "person" name) - (tags (concat "person" name))))) + (tags name)))) #+END_SRC Now, don't faint from all the backquoting and unquoting--it's just Lisp, nothing to be afraid of! Let's slow down a moment and see what the normalized query looks like to be sure we're doing it correctly: From 485bbdfda8723722fbd2b39fe15a852ef5a99703 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 02:03:48 -0600 Subject: [PATCH 004/292] Meta: Upgrade makem.sh, Makefile, test.yml --- .github/workflows/test.yml | 2 +- Makefile | 2 +- makem.sh | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b274ed..d5ec8cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ # * test.yml --- Test Emacs packages using makem.sh on GitHub Actions # URL: https://github.com/alphapapa/makem.sh -# Version: 0.2 +# Version: 0.2.1 # * Commentary: diff --git a/Makefile b/Makefile index 68906e9..4abfce9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # * makem.sh/Makefile --- Script to aid building and testing Emacs Lisp packages # URL: https://github.com/alphapapa/makem.sh -# Version: 0.2 +# Version: 0.2.1 # * Arguments diff --git a/makem.sh b/makem.sh index 2d710a3..0cc3cbc 100755 --- a/makem.sh +++ b/makem.sh @@ -3,7 +3,7 @@ # * makem.sh --- Script to aid building and testing Emacs Lisp packages # URL: https://github.com/alphapapa/makem.sh -# Version: 0.2 +# Version: 0.2.1 # * Commentary: @@ -422,8 +422,9 @@ function package-main-file { function dependencies { # Echo list of package dependencies. - # Search package headers. - egrep -i '^;; Package-Requires: ' $(files-project-feature) $(files-project-test) \ + # Search package headers. Use -a so grep won't think that an Elisp file containing + # control characters (rare, but sometimes necessary) is binary and refuse to search it. + egrep -a -i '^;; Package-Requires: ' $(files-project-feature) $(files-project-test) \ | egrep -o '\([^([:space:]][^)]*\)' \ | egrep -o '^[^[:space:])]+' \ | sed -r 's/\(//g' \ From a24de862ec848568ffae1ed3fb2d515dca0dc6b1 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 02:03:58 -0600 Subject: [PATCH 005/292] Fix: (org-ql-defpred) Use alist-get Until map-2.1 can automatically be installed with makem.sh... --- org-ql.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org-ql.el b/org-ql.el index b4b98b3..499e0b1 100644 --- a/org-ql.el +++ b/org-ql.el @@ -990,7 +990,8 @@ It would be expanded to: `(progn (cl-eval-when (compile load eval) (cl-defun ,fn-name ,args ,docstring ,body)) - (setf (map-elt org-ql-predicates ',predicate-name) + ;; SOMEDAY: Use `map-elt' here, after map 2.1 can be automatically installed in CI sandbox... + (setf (alist-get ',predicate-name org-ql-predicates) `(:name ,',name :aliases ,',aliases :fn ,',fn-name :docstring ,,docstring :args ,',args :normalizers ,',normalizers :preambles ,',preambles)) (unless org-ql-defpred-defer From 58bf5dc0a38050df6fc9494334a602181743b0bc Mon Sep 17 00:00:00 2001 From: "Zen Monk Alain M. Lafon" Date: Mon, 23 Nov 2020 09:33:44 +0100 Subject: [PATCH 006/292] Proofread `defpred` tutorial --- examples/defpred.org | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/defpred.org b/examples/defpred.org index 75c28b7..d5fc7c5 100644 --- a/examples/defpred.org +++ b/examples/defpred.org @@ -3,7 +3,7 @@ [2020-11-22 Sun 22:35] Imagine you have weekly meetings with other people, and during the week you take notes about items to discuss at each meeting. When the time for a meeting comes, you want to quickly and easily search for all of the items to discuss at the meeting. -You've been experimenting with different ways to track such data in Org. You've tried using tags, but some of the names in question conflict with other tags in your data (e.g. someone's named Charles, but you also work with a firm named Charles, Inc., and you'd prefer to continue using the tag =Charles= for entries about that firm), so you've been using tags like ~:personNAME:~, which seems awkward. You've tried using a ~:person: NAME~ property on entries, which has the advantage of not cluttering the tags list, but also the disadvantage of not being readily visible in an outline. +You've been experimenting with different ways to track such data in Org. You've tried using tags, but some of the names in question conflict with other tags in your data (e.g. someone's named Charles, but you also work with a firm named Charles, Inc., and you'd prefer to continue using the tag =Charles= for entries about that firm), so you've been using tags like ~:personNAME:~, which seems awkward. You've tried using a ~:person: NAME~ property on entries, which has the advantage of not cluttering the tags list, but also the disadvantage of not being readily visible in an outline. So you haven't decided on a long-term solution, but the meetings aren't going to wait--you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged ~:personAlice:~) when you're meeting with her, and all of the entries about Bob (which have the property ~:person: Bob~) when you're meeting with him What do you do? @@ -51,7 +51,7 @@ Enter =org-ql= custom search predicates. Let's start simple, by defining a pred :body (property "person" name)) #+END_SRC -Now let's see what results we get for searching this file for entries about Bob: +Now, let's see what results we get for searching this file for entries about Bob: #+BEGIN_SRC elisp :results list :exports both :cache yes (org-ql-query :select '(org-get-heading :no-tags) @@ -77,7 +77,7 @@ Hmm, looks like we need to remind Bob to wash his mug and take some mints after #+END_EXAMPLE -Nothing? Oh, right, Alice's entries use the ~:personAlice:~ tag, so we'll also need to also search those kind of entries. Let's make the predicate do that too: +Nothing? Oh, right, Alice's entries use the ~:personAlice:~ tag, so we'll also need to search those kind of entries. Let's make the predicate do that, too: #+BEGIN_SRC elisp :results silent :exports code (org-ql-defpred person (name) From a06ea62521c2ebbb0cdafb78f83a01896414ce24 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 02:59:43 -0600 Subject: [PATCH 007/292] Docs: (defpred.org) Tidy --- examples/defpred.org | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/defpred.org b/examples/defpred.org index d5fc7c5..3cdf07f 100644 --- a/examples/defpred.org +++ b/examples/defpred.org @@ -204,7 +204,7 @@ But wait, that's not all! If you order now, we'll throw in non-sexp query synta (org-ql-search (current-buffer) "person:Alice,Bob") #+END_SRC -Don't believe me? Well, you see, queries in this syntax are automatically converted to the sexp syntax, like: +Don't believe me? Well, you see, queries in this syntax are converted to the sexp syntax, like: #+BEGIN_SRC elisp :results code :exports both :cache yes (org-ql--query-string-to-sexp "person:Alice,Bob") @@ -215,7 +215,7 @@ Don't believe me? Well, you see, queries in this syntax are automatically conve (person "Alice" "Bob") #+END_SRC -But that happens automatically when you use a search command like =org-ql-search=. If you have =org-ql= installed already, you could even click this link: [[org-ql-search:person:Alice,Bob][Alice or Bob]]. Which, in Org syntax, looks like: +And that happens automatically when you use a search command like =org-ql-search=. If you have =org-ql= installed already, you could even click this link: [[org-ql-search:person:Alice,Bob][Alice or Bob]]. Which, in Org syntax, looks like: #+BEGIN_SRC org [[org-ql-search:person:Alice,Bob]] @@ -286,8 +286,8 @@ Finally, if you're a Lisper who appreciates anaphora, you might prefer a more sy :normalizers ((`(,predicate-names . ,names) `(or (tags ,@(--map `(concat "person" ,it) names)) ,@(--map `(property "person" ,it) names)))) - :body (--any (or (property "person" name) - (tags (concat "person" name))) + :body (--any (or (property "person" it) + (tags (concat "person" it))) names)) #+END_SRC From b01106bd0f73c5eaea55a8d41a6943977363f356 Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 03:10:10 -0600 Subject: [PATCH 008/292] Docs: (defpred.org) Formless, conclusion --- examples/defpred.org | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/examples/defpred.org b/examples/defpred.org index 3cdf07f..53dac32 100644 --- a/examples/defpred.org +++ b/examples/defpred.org @@ -5,7 +5,7 @@ You've been experimenting with different ways to track such data in Org. You've tried using tags, but some of the names in question conflict with other tags in your data (e.g. someone's named Charles, but you also work with a firm named Charles, Inc., and you'd prefer to continue using the tag =Charles= for entries about that firm), so you've been using tags like ~:personNAME:~, which seems awkward. You've tried using a ~:person: NAME~ property on entries, which has the advantage of not cluttering the tags list, but also the disadvantage of not being readily visible in an outline. -So you haven't decided on a long-term solution, but the meetings aren't going to wait--you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged ~:personAlice:~) when you're meeting with her, and all of the entries about Bob (which have the property ~:person: Bob~) when you're meeting with him What do you do? +So you haven't decided on a long-term solution, but the meetings aren't going to wait--you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged ~:personAlice:~) when you're meeting with her, and all of the entries about Bob (which have the property ~:person: Bob~) when you're meeting with him. What do you do? * Contents :PROPERTIES: @@ -19,6 +19,8 @@ So you haven't decided on a long-term solution, but the meetings aren't going to - [[#non-sexp-query-syntax][Non-sexp query syntax]] - [[#using-multiple-predicates][Using multiple predicates]] - [[#predicate-aliases][Predicate aliases]] +- [[#be-formless][Be formless]] +- [[#conclusion][Conclusion]] - [[#appendix-anaphoric-macros][Appendix: Anaphoric macros]] :END: @@ -276,6 +278,42 @@ And, you know what, if you're just so busy that you don't even have time to type (It's up to you to remember whether =p= means =person= or =priority=, but code can't solve everything.) +* Be formless + +We can even go a step further: since the normalizer rewrites the query to call the =property= and =tags= predicates instead, this =person= predicate doesn't even need a body form! + +#+BEGIN_SRC elisp :results silent :exports code + (org-ql-defpred (person p) (&rest names) + "Search for entries about any of NAMES." + :normalizers ((`(,predicate-names . ,names) + `(or (tags ,@(cl-loop for name in names + collect (concat "person" name))) + ,@(cl-loop for name in names + collect `(property "person" ,name)))))) +#+END_SRC + +Will it still work? + +#+BEGIN_SRC elisp :results list :exports both :cache yes + (org-ql-query :select '(org-get-heading :no-tags) + :from (current-buffer) + :where '(person "Alice" "Bob")) +#+END_SRC + +#+RESULTS[4f5971c56616f01d8d3c28a66ef380495ee3e158]: +- [#A] Loud pet parakeet +- [#C] Missing sticky notes +- [#C] Dirty dishes in sink +- [#A] Stinky coffee breath + +It does! + +* Conclusion + +In this tutorial, we've gone from having to write lengthy, complex query expressions for accommodating idiosyncratic requirements, to being able to write simple query expressions that abstract away ugly details, to rewriting those query expressions into a more optimal form before a search is even run. The end result is an Org Query Language that is customized to meet your specific needs. + +What new custom predicates will you write next? + * Appendix: Anaphoric macros Finally, if you're a Lisper who appreciates anaphora, you might prefer a more syntactically concise definition of the predicate using Dash macros: @@ -285,10 +323,7 @@ Finally, if you're a Lisper who appreciates anaphora, you might prefer a more sy "Search for entries about any of NAMES." :normalizers ((`(,predicate-names . ,names) `(or (tags ,@(--map `(concat "person" ,it) names)) - ,@(--map `(property "person" ,it) names)))) - :body (--any (or (property "person" it) - (tags (concat "person" it))) - names)) + ,@(--map `(property "person" ,it) names))))) #+END_SRC Let's make sure it works: @@ -305,7 +340,7 @@ Let's make sure it works: - [#C] Dirty dishes in sink - [#A] Stinky coffee breath -Have fun making custom search predicates! +Lisp is fun! * Example data :PROPERTIES: From 92d099caf61c87a712117fffee618485885679eb Mon Sep 17 00:00:00 2001 From: Adam Porter Date: Mon, 23 Nov 2020 03:19:13 -0600 Subject: [PATCH 009/292] Docs: (defpred.org) Add HTML export It looks nice, even if it isn't practically viewable on GitHub. --- examples/defpred.html | 766 ++++++++++++++++++ examples/defpred.org | 13 +- .../export/setup/theme-darksun-local.setup | 17 + examples/export/setup/theme-darksun.setup | 15 + .../export/styles/darksun/css/darksun.css | 264 ++++++ .../export/styles/darksun/css/hideshow.css | 110 +++ .../export/styles/darksun/css/htmlize.css | 145 ++++ .../export/styles/darksun/images/checked.png | Bin 0 -> 3136 bytes .../export/styles/darksun/images/fixme.png | Bin 0 -> 625 bytes .../styles/darksun/images/halfchecked.png | Bin 0 -> 3080 bytes .../export/styles/darksun/images/info.png | Bin 0 -> 4403 bytes .../export/styles/darksun/images/note.png | Bin 0 -> 4567 bytes examples/export/styles/darksun/images/tip.png | Bin 0 -> 4447 bytes .../styles/darksun/images/unchecked.png | Bin 0 -> 3087 bytes .../export/styles/darksun/images/warning.png | Bin 0 -> 4340 bytes examples/export/styles/darksun/js/darksun.js | 524 ++++++++++++ examples/export/styles/darksun/js/hideshow.js | 335 ++++++++ .../styles/darksun/js/jquery-1.11.0.min.js | 4 + .../styles/darksun/js/jquery-1.9.1.min.js | 5 + .../styles/darksun/js/jquery-ui-1.10.2.min.js | 12 + .../darksun/js/jquery.localscroll-min.js | 9 + .../darksun/js/jquery.scrollTo-1.4.3.1-min.js | 7 + .../styles/darksun/js/jquery.zclip.min.js | 12 + .../lib/js/jquery.stickytableheaders.min.js | 1 + .../lib/js/stickytableheaders-license.txt | 20 + 25 files changed, 2257 insertions(+), 2 deletions(-) create mode 100644 examples/defpred.html create mode 100644 examples/export/setup/theme-darksun-local.setup create mode 100644 examples/export/setup/theme-darksun.setup create mode 100644 examples/export/styles/darksun/css/darksun.css create mode 100644 examples/export/styles/darksun/css/hideshow.css create mode 100644 examples/export/styles/darksun/css/htmlize.css create mode 100644 examples/export/styles/darksun/images/checked.png create mode 100644 examples/export/styles/darksun/images/fixme.png create mode 100644 examples/export/styles/darksun/images/halfchecked.png create mode 100644 examples/export/styles/darksun/images/info.png create mode 100644 examples/export/styles/darksun/images/note.png create mode 100644 examples/export/styles/darksun/images/tip.png create mode 100644 examples/export/styles/darksun/images/unchecked.png create mode 100644 examples/export/styles/darksun/images/warning.png create mode 100644 examples/export/styles/darksun/js/darksun.js create mode 100644 examples/export/styles/darksun/js/hideshow.js create mode 100644 examples/export/styles/darksun/js/jquery-1.11.0.min.js create mode 100644 examples/export/styles/darksun/js/jquery-1.9.1.min.js create mode 100644 examples/export/styles/darksun/js/jquery-ui-1.10.2.min.js create mode 100644 examples/export/styles/darksun/js/jquery.localscroll-min.js create mode 100644 examples/export/styles/darksun/js/jquery.scrollTo-1.4.3.1-min.js create mode 100644 examples/export/styles/darksun/js/jquery.zclip.min.js create mode 100644 examples/export/styles/lib/js/jquery.stickytableheaders.min.js create mode 100644 examples/export/styles/lib/js/stickytableheaders-license.txt diff --git a/examples/defpred.html b/examples/defpred.html new file mode 100644 index 0000000..9c9b57e --- /dev/null +++ b/examples/defpred.html @@ -0,0 +1,766 @@ + + + + + + + +Org QL Custom Predicates Tutorial + + + + + + + + + + + + + +
+

Org QL Custom Predicates Tutorial

+ +

+[2020-11-22 Sun 22:35] Imagine you have weekly meetings with other people, and during the week you take notes about items to discuss at each meeting. When the time for a meeting comes, you want to quickly and easily search for all of the items to discuss at the meeting. +

+ +

+You've been experimenting with different ways to track such data in Org. You've tried using tags, but some of the names in question conflict with other tags in your data (e.g. someone's named Charles, but you also work with a firm named Charles, Inc., and you'd prefer to continue using the tag Charles for entries about that firm), so you've been using tags like :personNAME:, which seems awkward. You've tried using a :person: NAME property on entries, which has the advantage of not cluttering the tags list, but also the disadvantage of not being readily visible in an outline. +

+ +

+So you haven't decided on a long-term solution, but the meetings aren't going to wait–you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged :personAlice:) when you're meeting with her, and all of the entries about Bob (which have the property :person: Bob) when you're meeting with him. What do you do? +

+ +
+

Using built-in predicates

+
+

+You could start by using built-in Org QL predicates to search your data. For example: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(or (tags "personAlice")
+                          (property "person" "Bob")))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+That was easy enough, but it's not very…semantic. You have to think about the implementation details: Alice uses tags, Bob uses properties, and what if Charlie uses both? It starts to feel complicated, and it's a lot to type out every time. Is there an easier way? +

+
+
+ +
+

A custom (person) predicate

+
+

+Enter org-ql custom search predicates. Let's start simple, by defining a predicate to search for just the :person: property, one person at a time. The predicate will take one argument, a person's name, and search for that property. It would look like this: +

+ +
+
(org-ql-defpred person (name)
+  "Search for entries with the \"person\" property being NAME."
+  :body (property "person" name))
+
+
+ +

+Now, let's see what results we get for searching this file for entries about Bob: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Bob"))
+
+
+ +
    +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+Hmm, looks like we need to remind Bob to wash his mug and take some mints after lunch. Now what do we need to discuss with Alice? +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice"))
+
+
+ +
+
+ +

+Nothing? Oh, right, Alice's entries use the :personAlice: tag, so we'll also need to search those kind of entries. Let's make the predicate do that, too: +

+ +
+
(org-ql-defpred person (name)
+  "Search for entries with the \"person\" property being NAME or having the tag \"personNAME\"."
+  :body (or (property "person" name)
+            (tags (concat "person" name))))
+
+
+ +

+How about now? +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice"))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
+ +

+Hmm, I thought we already told her to leave Polly at home. +

+
+
+ +
+

Searching for multiple people at once

+
+

+Oh, wait, this week is shortened due to holidays, so we're having a combined meeting. How do we search for entries about either of them? Well, this is the obvious solution: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(or (person "Alice")
+                          (person "Bob")))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+And that works fine. But it seems like a lot to type. Could we make the person predicate accept multiple names instead? +

+ +
+
(org-ql-defpred person (&rest names)
+  "Search for entries about any of NAMES."
+  :body (cl-loop for name in names
+                 thereis (or (property "person" name)
+                             (tags (concat "person" name)))))
+
+
+ +

+Now let's search again: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice" "Bob"))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+That was easy! +

+
+
+ +
+

Normalizing queries to rewrite arguments

+
+

+Now, all this is well and good if you don't have hundreds of thousands of Org entries in your files. But what if you do? All that concat'ing happening on every entry could add up, and the query might take a few seconds. What if we could do that stringing-along just once, before running the query? We want to turn our (person "Alice" "Bob") query into this, with the :personNAME: strings already made and the per-person (property ...) predicates also included: +

+ +
+
(or (tags "personAlice" "personBob")
+    (property "person" "Alice")
+    (property "person" "Bob"))
+
+
+ +

+Can we do that? In fact, we can, by using a query normalizer. Normalizers are pcase forms (I know) that normalize query expressions before execution. We can use one to rewrite the query ahead of time, like this: +

+ +
+
(org-ql-defpred person (&rest names)
+  "Search for entries about any of NAMES."
+  :normalizers ((`(person . ,names)
+                 `(or (tags ,@(cl-loop for name in names
+                                       collect (concat "person" name)))
+                      ,@(cl-loop for name in names
+                                 collect `(property "person" ,name)))))
+  :body (cl-loop for name in names
+                 thereis (or (property "person" name)
+                             (tags name))))
+
+
+ +

+Now, don't faint from all the backquoting and unquoting–it's just Lisp, nothing to be afraid of! Let's slow down a moment and see what the normalized query looks like to be sure we're doing it correctly: +

+ +
+
(org-ql--normalize-query '(person "Alice" "Bob"))
+
+
+ +
+
(or (tags "personAlice" "personBob")
+    (property "person" "Alice")
+    (property "person" "Bob"))
+
+
+ +

+And, as they say, Bob's your uncle! Or even if he isn't, let's see if it works: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice" "Bob"))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+Yep, same result as the non-normalized query. And look at how much simpler it is to write (person "Alice" "Bob") than to write (or (tags "personAlice" "personBob") (property "person" "Alice") (property "person" "Bob")). +

+
+
+ +
+

Non-sexp query syntax

+
+

+But wait, that's not all! If you order now, we'll throw in non-sexp query syntax for free! That's right, your search could be as simple as typing person:Alice,Bob! +

+ +
+
(org-ql-search (current-buffer) "person:Alice,Bob")
+
+
+ +

+Don't believe me? Well, you see, queries in this syntax are converted to the sexp syntax, like: +

+ +
+
(org-ql--query-string-to-sexp "person:Alice,Bob")
+
+
+ +
+
(person "Alice" "Bob")
+
+
+ +

+And that happens automatically when you use a search command like org-ql-search. If you have org-ql installed already, you could even click this link: Alice or Bob. Which, in Org syntax, looks like: +

+ + + +

+And that would open an Agenda Mode buffer that looks like this: +

+ +
+Query: (person "Alice" "Bob")  In:meetings.org
+  [#A] Loud pet parakeet                                           :personAlice:
+  [#C] Missing sticky notes                                        :personAlice:
+  [#C] Dirty dishes in sink                                        :personAlice:
+  [#A] Stinky coffee breath
+
+
+
+ +
+

Using multiple predicates

+
+

+Oops, you forgot that there's a birthday party in 20 minutes, so you only have time to talk about the highest priority items at this joint meeting today. +

+ +

+No problem, let's just select high-priority items: +

+ +
+
(org-ql-search (current-buffer) "person:Alice,Bob priority:A")
+
+
+ +

+Which shows: +

+ +
+Query: (and (person "Alice" "Bob") (priority "A"))  In:meetings.org
+  [#A] Loud pet parakeet                                           :personAlice:
+  [#A] Stinky coffee breath
+
+
+
+ +
+

Predicate aliases

+
+

+And, you know what, if you're just so busy that you don't even have time to type the word person, you can add an abbreviated alias, p, like this: +

+ +
+
(org-ql-defpred (person p) (&rest names)
+  "Search for entries about any of NAMES."
+  :normalizers ((`(,predicate-names . ,names)
+                 `(or (tags ,@(cl-loop for name in names
+                                       collect (concat "person" name)))
+                      ,@(cl-loop for name in names
+                                 collect `(property "person" ,name)))))
+  :body (cl-loop for name in names
+                 thereis (or (property "person" name)
+                             (tags (concat "person" name)))))
+
+
+ +

+Let's try it: +

+ +
+
(org-ql-search (current-buffer) "p:Alice,Bob priority:A")
+
+
+ +

+And that shows: +

+ +
+Query: (and (person "Alice" "Bob") (priority "A"))  In:meetings.org
+  [#A] Loud pet parakeet                                           :personAlice:
+  [#A] Stinky coffee breath
+
+ +

+(It's up to you to remember whether p means person or priority, but code can't solve everything.) +

+
+
+ +
+

Be formless

+
+

+We can even go a step further: since the normalizer rewrites the query to call the property and tags predicates instead, this person predicate doesn't even need a body form! +

+ +
+
(org-ql-defpred (person p) (&rest names)
+  "Search for entries about any of NAMES."
+  :normalizers ((`(,predicate-names . ,names)
+                 `(or (tags ,@(cl-loop for name in names
+                                       collect (concat "person" name)))
+                      ,@(cl-loop for name in names
+                                 collect `(property "person" ,name))))))
+
+
+ +

+Will it still work? +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice" "Bob"))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+It does! +

+
+
+ +
+

Conclusion

+
+

+In this tutorial, we've gone from having to write lengthy, complex query expressions for accommodating idiosyncratic requirements, to being able to write simple query expressions that abstract away ugly details, to rewriting those query expressions into a more optimal form before a search is even run. The end result is an Org Query Language that is customized to meet your specific needs. +

+ +

+What new custom predicates will you write next? +

+
+
+ +
+

Appendix: Anaphoric macros

+
+

+Finally, if you're a Lisper who appreciates anaphora, you might prefer a more syntactically concise definition of the predicate using Dash macros: +

+ +
+
(org-ql-defpred (person p) (&rest names)
+  "Search for entries about any of NAMES."
+  :normalizers ((`(,predicate-names . ,names)
+                 `(or (tags ,@(--map `(concat "person" ,it) names))
+                      ,@(--map `(property "person" ,it) names)))))
+
+
+ +

+Let's make sure it works: +

+ +
+
(org-ql-query :select '(org-get-heading :no-tags)
+              :from (current-buffer)
+              :where '(person "Alice" "Bob"))
+
+
+ +
    +
  • [#A] Loud pet parakeet
  • +
  • [#C] Missing sticky notes
  • +
  • [#C] Dirty dishes in sink
  • +
  • [#A] Stinky coffee breath
  • +
+ +

+Lisp is fun! +

+
+
+ +
+

Example data

+
+
+ +
+

Loud pet parakeet   personAlice

+
+ +
+

Missing sticky notes   personAlice

+
+
+
+ +
+

Dirty dishes in sink   personAlice

+
+
+
+ +
+

Stinky coffee breath

+
+
+
+
+
+
+

Created: 2020-11-23 Mon 03:31

+

Validate

+
+ + diff --git a/examples/defpred.org b/examples/defpred.org index 53dac32..4f19576 100644 --- a/examples/defpred.org +++ b/examples/defpred.org @@ -1,5 +1,6 @@ #+TITLE: Org QL Custom Predicates Tutorial -#+OPTIONS: author:nil creator:nil created:nil date:nil num:nil title:t +#+OPTIONS: author:nil creator:nil created:nil date:nil num:nil title:t broken-links:t +#+SETUPFILE: export/setup/theme-darksun-local.setup [2020-11-22 Sun 22:35] Imagine you have weekly meetings with other people, and during the week you take notes about items to discuss at each meeting. When the time for a meeting comes, you want to quickly and easily search for all of the items to discuss at the meeting. @@ -7,7 +8,7 @@ You've been experimenting with different ways to track such data in Org. You've So you haven't decided on a long-term solution, but the meetings aren't going to wait--you need to search that data now, and you have a mix of both tags and properties in your entries. What you need is to be able to search for all of the entries about Alice (which you've tagged ~:personAlice:~) when you're meeting with her, and all of the entries about Bob (which have the property ~:person: Bob~) when you're meeting with him. What do you do? -* Contents +* Contents :noexport: :PROPERTIES: :TOC: :include siblings :ignore this :END: @@ -130,6 +131,8 @@ And that works fine. But it seems like a lot to type. Could we make the =perso (tags (concat "person" name))))) #+END_SRC +Now let's search again: + #+BEGIN_SRC elisp :results list :exports both :cache yes (org-ql-query :select '(org-get-heading :no-tags) :from (current-buffer) @@ -243,6 +246,8 @@ No problem, let's just select high-priority items: (org-ql-search (current-buffer) "person:Alice,Bob priority:A") #+END_SRC +Which shows: + #+BEGIN_EXAMPLE Query: (and (person "Alice" "Bob") (priority "A")) In:meetings.org [#A] Loud pet parakeet :personAlice: @@ -266,10 +271,14 @@ And, you know what, if you're just so busy that you don't even have time to type (tags (concat "person" name))))) #+END_SRC +Let's try it: + #+BEGIN_SRC elisp :results silent :exports code (org-ql-search (current-buffer) "p:Alice,Bob priority:A") #+END_SRC +And that shows: + #+BEGIN_EXAMPLE Query: (and (person "Alice" "Bob") (priority "A")) In:meetings.org [#A] Loud pet parakeet :personAlice: diff --git a/examples/export/setup/theme-darksun-local.setup b/examples/export/setup/theme-darksun-local.setup new file mode 100644 index 0000000..1ae015b --- /dev/null +++ b/examples/export/setup/theme-darksun-local.setup @@ -0,0 +1,17 @@ +# -*- mode: org; -*- + +# The files that hide sections and display a navbar and ToC are commented out. + +# #+HTML_HEAD: +#+HTML_HEAD: +# #+HTML_HEAD: + +#+HTML_HEAD: +#+HTML_HEAD: + +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +# #+HTML_HEAD: +#+HTML_HEAD: diff --git a/examples/export/setup/theme-darksun.setup b/examples/export/setup/theme-darksun.setup new file mode 100644 index 0000000..a5abe0e --- /dev/null +++ b/examples/export/setup/theme-darksun.setup @@ -0,0 +1,15 @@ +# -*- mode: org; -*- + +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: + +#+HTML_HEAD: +#+HTML_HEAD: + +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: diff --git a/examples/export/styles/darksun/css/darksun.css b/examples/export/styles/darksun/css/darksun.css new file mode 100644 index 0000000..212fb9a --- /dev/null +++ b/examples/export/styles/darksun/css/darksun.css @@ -0,0 +1,264 @@ +* { + border-color: #657b83; +} +a { + color: #268bd2 !important; +} +a:visited { + color: #6c71c4 !important; +} +body { + background-color: #002b36 !important; + color: #839496 !important; + font-family: Libre Baskerville, serif; +} +html { + background-color: #002b36 !important; +} +input, +textarea { + background-color: #073642 !important; + color: #839496 !important; +} +blockquote, +code, +pre { + background-color: #073642 !important; + border: 1px solid #094352 !important; + border-radius: 2px; + box-shadow: none; + color: #839496 !important; +} +code { + font-size: 1.2em; +} +img { + margin: 0.5em; +} +.buttons { + background-color: #073642 !important; + color: #93a1a1 !important; + font-family: sans-serif; + font-size: 1.5rem; +} +.hsReviewPanel { + display: none; +} +.title { + border: 0; +} +#postamble { + display: none; +} +#banner { + text-align: center; +} +#tabs { + background-color: #073642 !important; + float: left; + margin: 0px 0px 20px 0px; + padding: 0; + width: 100%; + font-family: sans-serif; +} +#tabs ul { + margin: 0; + padding: 0; +} +#tabs li { + float: left; + list-style-type: none; + margin: 0px 2px 0px 0px; + padding: 0px 0px 0px 0px; + white-space: nowrap; +} +#tabs li a { + display: block; + font-size: 120%; + font-weight: bold; + margin: 0; + padding: 4px 10px 4px 10px; + text-decoration: none; +} +#tabs li a:hover { + color: #90c5eb !important; +} +#tabs li.ui-tabs-active a { + background-color: #094352 !important; + color: #59a9e1 !important; +} +#content { + clear: both; + width: 80%; +} +#content > p { + margin: 1em; +} +#content > div > h2 { + margin: 0.25rem; +} +#postamble { + text-align: right; +} +blockquote { + background-color: #073642 !important; + padding-left: 0.75em; + padding-right: 0.75em; + padding-top: 0.01em; + padding-bottom: 0.01em; + overflow: hidden; + margin-left: 0; +} +.outline-text-2, +.outline-text-3, +.outline-text-4, +.outline-text-5, +.outline-text-6 { + padding-left: 4%; +} +.outline-3 { + padding-left: 4%; +} +.outline-4 { + padding-left: 4%; +} +.outline-5 { + padding-left: 4%; +} +.outline-6 { + padding-left: 4%; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: sans-serif; + border-bottom: 2px solid; + border-color: #094352 !important; +} +a:hover { + text-decoration: underline; +} +#search { + display: block; + float: right; +} +.tag { + background-color: #002b36 !important; + color: #93a1a1 !important; + border: 0px; +} +.tag span { + background-color: #094352 !important; + margin: 0.25em; + padding: 0.25em; +} +#table-of-contents { + display: table; + float: right; +} +#table-of-contents h2 { + display: none; +} +#table-of-contents li { + list-style-type: none; +} +#table-of-contents a:hover { + text-decoration: underline; +} +#minitoc { + background-color: #073642 !important; + position: fixed; + top: 0px; + right: -5px; + padding: 0.5em; + z-index: 200; + width: 18%; + word-wrap: normal; + overflow: hidden; +} +#minitoc ul { + margin: 0.5em; + padding-left: 1em; +} +#minitoc h2 { + margin-top: 0; + margin-bottom: 0; + font-size: 110%; +} +#minitoc a { + display: block; + font-family: sans-serif; + text-decoration: none; +} +#minitoc a:hover { + text-decoration: underline; +} +/* #minitoc a { */ +/* display: none; */ +/* } */ +#minitoc:hover a { + display: block; +} +#toTop { + top: -100px; + position: fixed; + text-align: center; + z-index: 999; + display: none; +} +#toTop:hover { + text-decoration: underline; +} +#left-panel-wrapper { + display: none; + position: fixed; + z-index: 200; +} +#left-panel-contents { + display: none; + position: fixed; + text-align: left; + top: 0; + z-index: 200; +} +#left-panel-button { + transform: rotate(90deg); + left: -23px; + position: fixed; + top: 50%; + z-index: 200; +} +#right-panel-wrapper { + position: fixed; + z-index: 200; +} +#right-panel-contents { + display: none; + z-index: 200; + position: fixed; + text-align: left; + top: 0; +} +.snippet-copy-to-clipboard { + display: none; + position: absolute; + left: 50px; + top: 50px; +} +.org-ol > li, +.org-dl > dt { + margin-top: 0.5em; +} +.org-ul { + margin-top: 1em; + margin-bottom: 1em; +} +.org-ul li { + margin-top: 0.5em; +} +.org-ul > li > a { + font-family: sans-serif; +} diff --git a/examples/export/styles/darksun/css/hideshow.css b/examples/export/styles/darksun/css/hideshow.css new file mode 100644 index 0000000..4ded9b5 --- /dev/null +++ b/examples/export/styles/darksun/css/hideshow.css @@ -0,0 +1,110 @@ +/* hideshow.css --- HideShow CSS file */ + +/* Copyright (C) 2014 All Right Reserved, Fabrice Niessen */ + +/* This file is free software: you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License as */ +/* published by the Free Software Foundation, either version 3 of */ +/* the License, or (at your option) any later version. */ + +/* This file is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* Author: Fabrice Niessen <(concat "fniessen" at-sign "pirilampo.org")> */ +/* URL: https://github.com/fniessen/hide-show/ */ +/* Version: 20140912.1722 */ + +.hsExpanded.hsAnchor {background: #EE7700;} + +.buttons { + padding: 0px 7px 13px 0px; + background: #0A3F69; +} + +.hsButton { + color: white; + float: right; + font-size: 70%; + margin-left: 10px; +} + + .hsButton:hover { + text-decoration: underline; + cursor: pointer; + } + +.ellipsis { + color: #999999; + /* background-color: #FFF8C0; */ + /* float: right; */ + margin-left: 0.6em; +} + +.hsReview { + border: 1px solid #A4A4A4; + background-color: white; + z-index: 500; /* must be greater then z-index of hsOverlay */ + position: relative; /* required for z-index to work */ +} + +#hsOverlay { + width: 100%; + height: 100%; + position: fixed; + left: 0px; + top: 0px; + background-color: #000; + opacity: .70; + z-index: 250; /* must be greater than any other z-index (except the one for .hsReview */ +} + +.hsReviewPanel { + background-color: #757176; + color: white; + line-height: 1.1em; + margin: 10px 0px; + padding: 10px; + position: fixed; + width: auto; + bottom: 0px; + right: 0px; + z-index: 501; + text-align: center; +} + + .hsReviewPanel.hsReviewing { + display: none; + } + + .hsReviewPanel:hover { + cursor: pointer; + } + +.hsReviewingPanel { + background-color: white; + color: #757176; + line-height: 1.1em; + margin: 10px 0px; + padding: 10px; + position: fixed; + width: auto; + bottom: 0px; + right: 0px; + z-index: 501; + text-align: center; +} + +.hsReviewButton:hover { + cursor: pointer; +} + +.hsUnselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/examples/export/styles/darksun/css/htmlize.css b/examples/export/styles/darksun/css/htmlize.css new file mode 100644 index 0000000..f08e781 --- /dev/null +++ b/examples/export/styles/darksun/css/htmlize.css @@ -0,0 +1,145 @@ +.org-bold { /* bold */ font-weight: bold; } +.org-bold-italic { /* bold-italic */ font-weight: bold; font-style: italic; } +.org-buffer-menu-buffer { /* buffer-menu-buffer */ font-weight: bold; } +.org-builtin { /* font-lock-builtin-face */ color: #7a378b; } +.org-button { /* button */ text-decoration: underline; } +.org-calendar-today { /* calendar-today */ text-decoration: underline; } +.org-change-log-acknowledgement { /* change-log-acknowledgement */ color: #b22222; } +.org-change-log-conditionals { /* change-log-conditionals */ color: #a0522d; } +.org-change-log-date { /* change-log-date */ color: #8b2252; } +.org-change-log-email { /* change-log-email */ color: #a0522d; } +.org-change-log-file { /* change-log-file */ color: #0000ff; } +.org-change-log-function { /* change-log-function */ color: #a0522d; } +.org-change-log-list { /* change-log-list */ color: #a020f0; } +.org-change-log-name { /* change-log-name */ color: #008b8b; } +.org-comint-highlight-input { /* comint-highlight-input */ font-weight: bold; } +.org-comint-highlight-prompt { /* comint-highlight-prompt */ color: #00008b; } +.org-comment { /* font-lock-comment-face */ color: #b22222; } +.org-comment-delimiter { /* font-lock-comment-delimiter-face */ color: #b22222; } +.org-completions-annotations { /* completions-annotations */ font-style: italic; } +.org-completions-common-part { /* completions-common-part */ color: #000000; background-color: #ffffff; } +.org-completions-first-difference { /* completions-first-difference */ font-weight: bold; } +.org-constant { /* font-lock-constant-face */ color: #008b8b; } +.org-diary { /* diary */ color: #ff0000; } +.org-diff-context { /* diff-context */ color: #7f7f7f; } +.org-diff-file-header { /* diff-file-header */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-function { /* diff-function */ background-color: #cccccc; } +.org-diff-header { /* diff-header */ background-color: #cccccc; } +.org-diff-hunk-header { /* diff-hunk-header */ background-color: #cccccc; } +.org-diff-index { /* diff-index */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-nonexistent { /* diff-nonexistent */ background-color: #b3b3b3; font-weight: bold; } +.org-diff-refine-change { /* diff-refine-change */ background-color: #d9d9d9; } +.org-dired-directory { /* dired-directory */ color: #0000ff; } +.org-dired-flagged { /* dired-flagged */ color: #ff0000; font-weight: bold; } +.org-dired-header { /* dired-header */ color: #228b22; } +.org-dired-ignored { /* dired-ignored */ color: #7f7f7f; } +.org-dired-mark { /* dired-mark */ color: #008b8b; } +.org-dired-marked { /* dired-marked */ color: #ff0000; font-weight: bold; } +.org-dired-perm-write { /* dired-perm-write */ color: #b22222; } +.org-dired-symlink { /* dired-symlink */ color: #a020f0; } +.org-dired-warning { /* dired-warning */ color: #ff0000; font-weight: bold; } +.org-doc { /* font-lock-doc-face */ color: #8b2252; } +.org-escape-glyph { /* escape-glyph */ color: #a52a2a; } +.org-file-name-shadow { /* file-name-shadow */ color: #7f7f7f; } +.org-flyspell-duplicate { /* flyspell-duplicate */ color: #cdad00; font-weight: bold; text-decoration: underline; } +.org-flyspell-incorrect { /* flyspell-incorrect */ color: #ff4500; font-weight: bold; text-decoration: underline; } +.org-fringe { /* fringe */ background-color: #f2f2f2; } +.org-function-name { /* font-lock-function-name-face */ color: #0000ff; } +.org-header-line { /* header-line */ color: #333333; background-color: #e5e5e5; } +.org-help-argument-name { /* help-argument-name */ font-style: italic; } +.org-highlight { /* highlight */ background-color: #b4eeb4; } +.org-holiday { /* holiday */ background-color: #ffc0cb; } +.org-isearch { /* isearch */ color: #b0e2ff; background-color: #cd00cd; } +.org-isearch-fail { /* isearch-fail */ background-color: #ffc1c1; } +.org-italic { /* italic */ font-style: italic; } +.org-keyword { /* font-lock-keyword-face */ color: #a020f0; } +.org-lazy-highlight { /* lazy-highlight */ background-color: #afeeee; } +.org-link { /* link */ color: #0000ff; text-decoration: underline; } +.org-link-visited { /* link-visited */ color: #8b008b; text-decoration: underline; } +.org-log-edit-header { /* log-edit-header */ color: #a020f0; } +.org-log-edit-summary { /* log-edit-summary */ color: #0000ff; } +.org-log-edit-unknown-header { /* log-edit-unknown-header */ color: #b22222; } +.org-match { /* match */ background-color: #ffff00; } +.org-next-error { /* next-error */ background-color: #eedc82; } +.org-nobreak-space { /* nobreak-space */ color: #a52a2a; text-decoration: underline; } +.org-org-archived { /* org-archived */ color: #7f7f7f; } +.org-org-block { /* org-block */ color: #7f7f7f; } +.org-org-block-begin-line { /* org-block-begin-line */ color: #b22222; } +.org-org-block-end-line { /* org-block-end-line */ color: #b22222; } +.org-org-checkbox { /* org-checkbox */ font-weight: bold; } +.org-org-checkbox-statistics-done { /* org-checkbox-statistics-done */ color: #228b22; font-weight: bold; } +.org-org-checkbox-statistics-todo { /* org-checkbox-statistics-todo */ color: #ff0000; font-weight: bold; } +.org-org-clock-overlay { /* org-clock-overlay */ background-color: #ffff00; } +.org-org-code { /* org-code */ color: #7f7f7f; } +.org-org-column { /* org-column */ background-color: #e5e5e5; } +.org-org-column-title { /* org-column-title */ background-color: #e5e5e5; font-weight: bold; text-decoration: underline; } +.org-org-date { /* org-date */ color: #a020f0; text-decoration: underline; } +.org-org-document-info { /* org-document-info */ color: #191970; } +.org-org-document-info-keyword { /* org-document-info-keyword */ color: #7f7f7f; } +.org-org-document-title { /* org-document-title */ color: #191970; font-size: 144%; font-weight: bold; } +.org-org-done { /* org-done */ color: #228b22; font-weight: bold; } +.org-org-drawer { /* org-drawer */ color: #0000ff; } +.org-org-ellipsis { /* org-ellipsis */ color: #b8860b; text-decoration: underline; } +.org-org-footnote { /* org-footnote */ color: #a020f0; text-decoration: underline; } +.org-org-formula { /* org-formula */ color: #b22222; } +.org-org-headline-done { /* org-headline-done */ color: #bc8f8f; } +.org-org-hide { /* org-hide */ color: #ffffff; } +.org-org-latex-and-export-specials { /* org-latex-and-export-specials */ color: #8b4513; } +.org-org-level-1 { /* org-level-1 */ color: #0000ff; } +.org-org-level-2 { /* org-level-2 */ color: #a0522d; } +.org-org-level-3 { /* org-level-3 */ color: #a020f0; } +.org-org-level-4 { /* org-level-4 */ color: #b22222; } +.org-org-level-5 { /* org-level-5 */ color: #228b22; } +.org-org-level-6 { /* org-level-6 */ color: #008b8b; } +.org-org-level-7 { /* org-level-7 */ color: #7a378b; } +.org-org-level-8 { /* org-level-8 */ color: #8b2252; } +.org-org-link { /* org-link */ color: #0000ff; text-decoration: underline; } +.org-org-meta-line { /* org-meta-line */ color: #b22222; } +.org-org-mode-line-clock { /* org-mode-line-clock */ color: #000000; background-color: #bfbfbf; } +.org-org-mode-line-clock-overrun { /* org-mode-line-clock-overrun */ color: #000000; background-color: #ff0000; } +.org-org-quote { /* org-quote */ color: #7f7f7f; } +.org-org-scheduled { /* org-scheduled */ color: #006400; } +.org-org-scheduled-previously { /* org-scheduled-previously */ color: #b22222; } +.org-org-scheduled-today { /* org-scheduled-today */ color: #006400; } +.org-org-sexp-date { /* org-sexp-date */ color: #a020f0; } +.org-org-special-keyword { /* org-special-keyword */ color: #a020f0; } +.org-org-table { /* org-table */ color: #0000ff; } +.org-org-tag { /* org-tag */ font-weight: bold; } +.org-org-target { /* org-target */ text-decoration: underline; } +.org-org-time-grid { /* org-time-grid */ color: #b8860b; } +.org-org-todo { /* org-todo */ color: #ff0000; font-weight: bold; } +.org-org-upcoming-deadline { /* org-upcoming-deadline */ color: #b22222; } +.org-org-verbatim { /* org-verbatim */ color: #7f7f7f; } +.org-org-verse { /* org-verse */ color: #7f7f7f; } +.org-org-warning { /* org-warning */ color: #ff0000; font-weight: bold; } +.org-outline-1 { /* outline-1 */ color: #0000ff; } +.org-outline-2 { /* outline-2 */ color: #a0522d; } +.org-outline-3 { /* outline-3 */ color: #a020f0; } +.org-outline-4 { /* outline-4 */ color: #b22222; } +.org-outline-5 { /* outline-5 */ color: #228b22; } +.org-outline-6 { /* outline-6 */ color: #008b8b; } +.org-outline-7 { /* outline-7 */ color: #7a378b; } +.org-outline-8 { /* outline-8 */ color: #8b2252; } +.org-preprocessor { /* font-lock-preprocessor-face */ color: #7a378b; } +.org-query-replace { /* query-replace */ color: #b0e2ff; background-color: #cd00cd; } +.org-regexp-grouping-backslash { /* font-lock-regexp-grouping-backslash */ font-weight: bold; } +.org-regexp-grouping-construct { /* font-lock-regexp-grouping-construct */ font-weight: bold; } +.org-region { /* region */ background-color: #eedc82; } +.org-secondary-selection { /* secondary-selection */ background-color: #ffff00; } +.org-shadow { /* shadow */ color: #7f7f7f; } +.org-show-paren-match { /* show-paren-match */ background-color: #40e0d0; } +.org-show-paren-mismatch { /* show-paren-mismatch */ color: #ffffff; background-color: #a020f0; } +.org-string { /* font-lock-string-face */ color: #8b2252; } +.org-tool-bar { /* tool-bar */ color: #000000; background-color: #bfbfbf; } +.org-tooltip { /* tooltip */ color: #000000; background-color: #ffffe0; } +.org-trailing-whitespace { /* trailing-whitespace */ background-color: #ff0000; } +.org-type { /* font-lock-type-face */ color: #228b22; } +.org-underline { /* underline */ text-decoration: underline; } +.org-variable-name { /* font-lock-variable-name-face */ color: #a0522d; } +.org-warning { /* font-lock-warning-face */ color: #ff0000; font-weight: bold; } +.org-widget-button { /* widget-button */ font-weight: bold; } +.org-widget-button-pressed { /* widget-button-pressed */ color: #ff0000; } +.org-widget-documentation { /* widget-documentation */ color: #006400; } +.org-widget-field { /* widget-field */ background-color: #d9d9d9; } +.org-widget-inactive { /* widget-inactive */ color: #7f7f7f; } +.org-widget-single-line-field { /* widget-single-line-field */ background-color: #d9d9d9; } diff --git a/examples/export/styles/darksun/images/checked.png b/examples/export/styles/darksun/images/checked.png new file mode 100644 index 0000000000000000000000000000000000000000..ebdc03bdaba59b81824d0b0f2ac115762310d2dc GIT binary patch literal 3136 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 -^9N* zU7@RT1Wj2P7#J9nyxm>oS$PHaGB7Z37I;J!Gca%qgD@k*tT_@43=DmqE{-7*ms9(1 z3pFe7xG+!Ly*6U)rKlhO&zrEw$nWAmGlz9jEW@M;GmO0$eVZ>Gvr6v`W8VCI;a!&Z zs~7*s$=|!^zg3Kl-Mpv<6LJ5IXY@HU^7T%4O}P6*&N_d$_XPXm&fs(f=IcwR{K(}I z%Y3nK>Z(%-DY2*diWh7(m9RV`)3aE9|Kq-Y;p?vYue@&0=)ILwxKZ%%R0akH)e_f; zl9a@fRIB8o)Wnih1|tJQBV7YST|?s#BO@y#GbIKU^w z_5c6>uCA`9P9FRBZOZ>oDgWM@Gcz+h-pHt~#Q6UO`-&+{|E@B)STOWgF{DK@w6wJR z|NlSrVR!}u0|R48kY6x^!?PP{AWo9Ey9+}HtE>kD1EZs-i(`nz>9tc{PCKl?(b_B; zbK=v#{M+0aeKU>i)2gbZg;_pzdT-8E`_;8gbI}*BgTH@;%v8Tr_;YIUGTp#B50(F+ zXJRrwtvQ}!V;vfrT+a zv&M`wHq6r&aGNN5Oi@*)z4ZHGqf?zu{;k`?j+?r6mVB0(ap^1b4ZkZ_p33m;*m7Oa z!r;P7hlHyl4#yAsiMvQ-tXasm(`%#o6+J^2Tbl&VvS8PW6Af_*AKsYEyuIm`!K|dZ z8IctNE5v%m4zGImn<<4^%QAnLZMKxEmJA>3{}~RoD;&0IzmeYaX`aKHg=PAN4ZGGi z?mhRXX}-L)e!=9{?+i)P4Q~gZ;@#u@J@#FBod|~#AIs8g*19E&QvZGD*m@~{&o}wF z${M+S-?mmV|9&Jl!B@raBm)D3YKdz^NlIc#s#S7PYGO$$gOP!uiLQa6u90Pkp{bRD zv6ZR0wt=CQfx)tgIq4`Ga`RI%(<)Ikm{^&ZSs55YG?dTkJI27kz~JfX=d#Wzp$PyX C+w#5u literal 0 HcmV?d00001 diff --git a/examples/export/styles/darksun/images/halfchecked.png b/examples/export/styles/darksun/images/halfchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..afd695e4145e06f388b207903aa87c474f47e957 GIT binary patch literal 3080 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�PV*|l=S%D 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 HDWM4fo4`&G literal 0 HcmV?d00001 diff --git a/examples/export/styles/darksun/images/note.png b/examples/export/styles/darksun/images/note.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6b8eb0fb39720842be86b0343adfdcbf929982 GIT binary patch literal 4567 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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 literal 0 HcmV?d00001 diff --git a/examples/export/styles/darksun/images/tip.png b/examples/export/styles/darksun/images/tip.png new file mode 100644 index 0000000000000000000000000000000000000000..743ef89c3a48cb65d51f0efb7305be9f2beba401 GIT binary patch literal 4447 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|lQz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lMT#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�PV*|l=S%D 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�PV*|l=S%D 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 u&mTHenjp4=ImNr`%Bt|qzdirUGc=^M&R_i6;(@ literal 0 HcmV?d00001 diff --git a/examples/export/styles/darksun/js/darksun.js b/examples/export/styles/darksun/js/darksun.js new file mode 100644 index 0000000..99ce23f --- /dev/null +++ b/examples/export/styles/darksun/js/darksun.js @@ -0,0 +1,524 @@ +// darksun.js --- darksun JS file +// +// Copyright (C) 2011-2016 All Right Reserved, Fabrice Niessen +// +// This file is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// Author: Fabrice Niessen <(concat "fniessen" at-sign "pirilampo.org")> +// URL: https://github.com/fniessen/org-html-themes/ +// Version: 20140515.1841 + +$(function() { + $('p'). + html(function(index, old) { + return old.replace('FIXME', + 'FIXME'); + }); + $('p'). + html(function(index, old) { + return old.replace('XXX', + 'XXX'); + }); +}); + +// Remove leading section number +$(function() { + $('.section-number-2').text(""); + for (var i = 3; i <= 5; i++) { + $('.section-number-' + i).each(function() { + $(this).text($(this).text().replace(/^[0-9]+\./g, "")); + }); + } +}); + +$(function() { + $('
').prependTo('body'); +}); + +// generate contents of minitoc +function generateMiniToc(divId) { + $('#minitoc-content').empty().append('

In this section

    '); + $('#' + divId).find('h3').each(function(i) { + // [2018-09-03 Mon 03:48] Get text up to non-breaking space, + // to omit the tags. If there are no tags, there will be no + //  , so get all the text. (This fixes a bug in the + // original code.) + let text = null; + let pos = $(this).text().search(/ | ►/); + if (pos > 0) { + text = $(this).text().substring(0, pos); + } + else { + text = $(this).text(); + } + $("#minitoc-content>ul").append("
  • " + + text + "
  • "); + }); + // Ensure that the target is expanded (hideShow) + $('#minitoc-content a[href^="#"]').click(function() { + var href = $(this).attr('href'); + hsExpandAnchor(href); + }); +} + +// display tabs +function tabifySections() { + + // hide TOC (if present) + $('#table-of-contents').hide(); + + // grab the list of `h2' from the page + var allSections = []; + $('h2') + .each(function() { + // Remove TODO keywords and tags (contained in spans) + var tabText = $(this).clone().find('span').remove().end() + .text().trim(); + var tabId = $(this).parent().attr('id'); + if (tabText) { + // - remove heading number (all leading digits) + // - remove progress logging (between square brackets) + // - remove leading and trailing spaces + tabText = tabText.replace(/^\d+\s+/, '').replace(/\[[\d/%]+\]/, '').trim(); + + allSections.push({ + text: tabText, + id: tabId + }); + } + }); + + // create the tab links + var tabs = $('
      '); + for (i = 0; i < allSections.length; i++) { + var item = allSections[i]; + html = $('
    • ' + item.text + '
    • '); + tabs.append(html); + } + + // insert tabs menu after title (`h1'), or at the beginning of the content + if($('.title').length !== 0) { + $('.title').after(tabs); + } + else { + $('#content').prepend(tabs); + } +} + +function selectTabAndScroll(href) { + // At this point we assume that href is local (starts with #) + // alert(href); + + // Find the tab to activate + var targetTab = $(href).closest('.ui-tabs-panel'); + var targetTabId = targetTab.attr('id'); + var targetTabAriaLabel = targetTab.attr('aria-labelledby'); + + var targetTabIndex = $("#content ul li") + .index($('[aria-labelledby="' + targetTabAriaLabel + '"]')); + + // Activate target tab + $('#content').tabs('option', 'active', targetTabIndex); + + // Rebuild minitoc + generateMiniToc(targetTabId); + + // Set the location hash + // document.location.hash = href; + + // Scroll to top if href was a tab + if (href == '#' + targetTabId) { + // alert(targetTabId); + $.scrollTo(0); + } + // Scroll to href if href was not a tab + else { + $.scrollTo(href); + } +} + +$(document).ready(function() { + $('#preamble').remove(); + $('#table-of-contents').remove(); + + // Initialize hideShow + hsInit(); + + // Prepare for tabs + tabifySections(); + + // Build the tabs from the #content div + $('#content').tabs(); + + // Set default animation + $('#content').tabs('option', 'show', true); + + // Rebuild minitoc when a tab is activated + $('#content').tabs({ + activate: function(event, ui) { + var divId = ui.newTab.attr('aria-controls'); + generateMiniToc(divId); + } + }); + + // Required to get the link of the tab in URL + $('#content ul').localScroll({ + target: '#content', + duration: 0, + hash: true + }); + + // Handle hash in URL + if ($('#content') && document.location.hash) { + hsExpandAnchor(document.location.hash); + selectTabAndScroll(document.location.hash); + } + // If no hash, build the minitoc anyway for selected tab + else { + var divId = $('#content div[aria-expanded=true]').attr('id'); + generateMiniToc(divId); + } + + // Handle click on internal links + $('.ui-tabs-panel a[href^="#"]').click(function(e) { + var href = $(this).attr('href'); + hsExpandAnchor(href); + selectTabAndScroll(href); + e.preventDefault(); + }); + + // add sticky headers to tables + $('table').stickyTableHeaders(); +}); + +function copyToClipboard(text) +{ + if (window.clipboardData && window.clipboardData.setData) { // Internet Explorer + window.clipboardData.setData("Text", text); + } + else { // Fallback solution + window.prompt("Copy to clipboard: Ctrl+C, Enter", text); + } +} + +$(document).ready(function() { + // Assuming that the ZeroClipboard swf file is in the same folder than darksun, + // get the path to it (it will be relative to the current page location). + var bbScriptPath = $('script[src$="darksun.js"]').attr('src'); // the js file path + var bbPathToZeroClipboardSwf = bbScriptPath.replace('darksun.js', 'ZeroClipboard.swf'); + + // Add copy to clipboard snippets + $('.org-src-container').prepend('
      [copy]
      '); + + // Display/hide snippets on source block mouseenter/mouseleave + $(document).on('mouseenter', '.org-src-container', function () { + $(this).find('.snippet-copy-to-clipboard').show(); + + // Need to call zclip here, once the button is visible. + // Beacause when the button is not visible, zclip does nothing. + if ((window.location.protocol != 'file:') && ($(this).find('.zclip').length == 0)) { + $(this).find('.copy-to-clipboard-button').zclip({ + //path: 'http://www.steamdev.com/zclip/js/ZeroClipboard.swf', + //path: 'styles/darksun/js/ZeroClipboard.swf', + path: bbPathToZeroClipboardSwf, + copy: function() { + return $(this).parent().parent().find('.src').text(); + } + }); + } + }).on('mouseleave', '.org-src-container', function () { + $(this).find('.snippet-copy-to-clipboard').hide(); + }); + + // Handle copy to clipboard (here, for a local file only 'file://...' + if (window.location.protocol == 'file:') { // if local file use browser-specific code + $('.copy-to-clipboard-button').click(function() { + // Get the text to be copied + var text = $(this).parent().parent().find('.src').text(); + text = text.replace(/\n/g, "\r\n"); + // alert(text); + copyToClipboard(text); + }); + } +}); + +$(function() { + $('li > code :contains("[X]")') + .parent() + .addClass('checked') + .end() + .remove(); + $('li > code :contains("[-]")') + .parent() + .addClass('halfchecked') + .end() + .remove(); + $('li > code :contains("[ ]")') + .parent() + .addClass('unchecked') + .end() + .remove(); +}); + +$(function() { + $('i :contains("[#A]")') + .replaceWith('[#A]'); + $('i :contains("[#B]")') + .replaceWith('[#B]'); + $('i :contains("[#C]")') + .replaceWith('[#C]'); +}); + +function togglePanel(e) { + e.preventDefault(); + + $("#left-panel-contents").toggleClass('active').toggle(200); + $("#right-panel-contents").toggleClass('active').toggle(200); + + var slidePos = + $("#left-panel-button").css("left") == "-23px"? '182px': '-23px'; + + $("#left-panel-button"). + animate({"left": slidePos, "opacity": 0.9}, {duration: "200" }); + + // if ($("#left-panel-contents").hasClass('active')) { + // hsHideTodoKeyword('done'); + // } else { + // hsShowTodoKeyword('done'); + // } + + // return false; +} + +$(function() { + $('') + .appendTo('body'); + + $('
      ') + .appendTo('body'); + + $('#left-panel-button').click(togglePanel); +}); + +$(function() { + var ul = $('
        ').appendTo('#left-panel-contents'); + var countOfTodo = {}, listOfTodo = [], totalOfTodo = 0; + + // assign the counts (avoid double-counting elements from the ToC) + $('span.todo').not($('#table-of-contents span.todo')).each(function() { + var $thisTodo = $(this).text().trim(); + + if ($.inArray($thisTodo, listOfTodo) == -1) { + countOfTodo[$thisTodo] = 1; + listOfTodo.push($thisTodo); + } + else + countOfTodo[$thisTodo] += 1; + totalOfTodo += 1; + }); + + function scoreTodo(t) { + switch (t) { + case 'NEW': return 1; + case 'TODO': return 2; + case 'STRT': return 3; + case 'WAIT': return 4; + case 'DLGT': return 5; + case 'SDAY': return 6; + case 'DFRD': return 7; + case 'DONE': return 8; + case 'CANX': return 9; + default: return 0; + } + } + + function compareTodo(a, b) { + if (scoreTodo(a) < scoreTodo(b)) return -1; + if (scoreTodo(a) > scoreTodo(b)) return 1; + return 0; + } + + listOfTodo.sort(compareTodo); + + // display + for (i = 0; i < listOfTodo.length; i++) { + var $thisTodo = listOfTodo[i]; + $(ul).append('
      • '+ $thisTodo + '' + + ' (' + countOfTodo[$thisTodo] + ')
      • '); + } + + $('#listOfTodo') + .before('Next Actions (' + totalOfTodo + '):
        '); +}); + +$(function() { + var ul = $('
          ').appendTo('#left-panel-contents'); + var countOfDone = {}, listOfDone = [], totalOfDone = 0; + + // assign the counts (avoid double-counting elements from the ToC) + $('span.done').not($('#table-of-contents span.done')).each(function() { + var $thisDone = $(this).text().trim(); + + if ($.inArray($thisDone, listOfDone) == -1) { + countOfDone[$thisDone] = 1; + listOfDone.push($thisDone); + } + else + countOfDone[$thisDone] += 1; + totalOfDone += 1; + }); + + // display + for (i = 0; i < listOfDone.length; i++) { + var $thisDone = listOfDone[i]; + $(ul).append('
        • '+ $thisDone + '' + + ' (' + countOfDone[$thisDone] + ')
        • '); + } + + $('#listOfDone') + .before('Done Actions (' + totalOfDone + '):
          '); +}); + +$(function() { + var ul = $('
            ').appendTo('#right-panel-contents'); + var countOfTags = {}, listOfTags = [], totalOfTags = 0; + + // assign the counts (avoid double-counting elements from the ToC) + $('span.tag').not($('#table-of-contents span.tag')).each(function() { + var $thisTagGroup = $(this).text().trim().split(/\s/); + // \s matches spaces, tabs, new lines, etc. + + for (tag in $thisTagGroup) { + if ($.inArray($thisTagGroup[tag], listOfTags) == -1) { + countOfTags[$thisTagGroup[tag]] = 1; + listOfTags.push($thisTagGroup[tag]); + } + else + countOfTags[$thisTagGroup[tag]] += 1; + totalOfTags += 1; + } + }); + + listOfTags.sort(); + + // display + for (i = 0; i < listOfTags.length; i++) { + var $thisTag = listOfTags[i]; + // $(ul).append('
          • '+ + // $thisTag + ' (' + countOfTags[$thisTag] + ')
          • '); + $(ul).append('
          • ' + $thisTag + + '' + + ' (' + countOfTags[$thisTag] + ')
          • '); + } + + $('#listOfTags') + .before('Contexts (' + totalOfTags + '):
            '); +}); + +$(function() { + $('.done').parent(':header').parent().find(':header').addClass('DONEheader'); + $('.done').parent(':header').parent().css({color: '#999999'}); +}); + +$(function() { + $('span.todo').click(function(e) { + var orgKeyword = $(this).text().trim(); + $('.' + orgKeyword).toggleClass('selected'); + $('#content .' + orgKeyword).parent().parent() + .toggleClass('match' + orgKeyword); + $('#left-panel-wrapper .' + orgKeyword).parent() + .toggleClass('match' + orgKeyword); + e.preventDefault(); + }) +}); + +$(function() { + $('.tag span').click(function(e) { + var orgTag = $(this).text().trim(); + $('.' + orgTag).toggleClass('selected'); + $('#content .' + orgTag).parent().parent().parent() + .toggleClass('matchtag'); + $('#right-panel-wrapper .' + orgTag).parent().parent() + .toggleClass('matchtag'); + e.preventDefault(); + }) +}); + +function clickPreviousTab() { + var active = $('#content').tabs('option', 'active'); + // Avoid going back to last tab + if (active == 0) return; + + $('#content').tabs('option', 'active', active - 1); + + // Set the location href + var href = $('#content div[aria-expanded=true]').attr('id'); + document.location.hash = href; + $.scrollTo(0); +} + +function clickNextTab() { + var active = $('#content').tabs('option', 'active'); + $('#content').tabs('option', 'active', active + 1); + + // Set the location href + var href = $('#content div[aria-expanded=true]').attr('id'); + document.location.hash = href; + $.scrollTo(0); +} + +function orgDefkey(e) { + if (!e) + var e = window.event; + var keycode = (e.keyCode) ? e.keyCode : e.which; + var actualkey = String.fromCharCode(keycode); + switch (actualkey) { + case "?": // help (dashboard) + case "h": + togglePanel(e); + break; + case "n": // next + clickNextTab(); + break; + case "p": // previous + clickPreviousTab(); + break; + // case "b": // scroll down - should be mapped to Shift-SPC + // $(window).scrollTop($(window).scrollTop()-$(window).height()); + // break; + case "<": // scroll to top + $(window).scrollTop(0); + break; + case ">": // scroll to bottom + $(window).scrollTop($(document).height()); + break; + case "-": // collapse all + hsCollapseAll(); + break; + case "+": // expand all + hsExpandAll(); + break; + case "r": // go to next task + hsReviewTaskNext(); + break; + case "R": // go to previous task + hsReviewTaskPrev(); + break; + case "q": // quit reviewing + hsReviewTaskQuit(); + break; + case "g": // refresh the page (from the server, rather than the cache) + location.reload(true); + break; + } +} + +document.onkeypress = orgDefkey; diff --git a/examples/export/styles/darksun/js/hideshow.js b/examples/export/styles/darksun/js/hideshow.js new file mode 100644 index 0000000..e528759 --- /dev/null +++ b/examples/export/styles/darksun/js/hideshow.js @@ -0,0 +1,335 @@ +// hideshow.js --- HideShow JS file +// +// Copyright (C) 2014 All Right Reserved, Fabrice Niessen +// +// This file is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// Author: Fabrice Niessen <(concat "fniessen" at-sign "pirilampo.org")> +// URL: https://github.com/fniessen/hide-show/ +// Version: 20140912.1722 + +// var HS_COLLAPSIBLE_HEADERS = $('h3, h4, h5'); // collapsible headers + +if (typeof HS_STARTUP_FOLDED === 'undefined') { + var HS_STARTUP_FOLDED = false; // Show just the overview, or show all. +} + +if (typeof HS_SHOW_ALL_TEXT === 'undefined') { + var HS_SHOW_ALL_TEXT = 'Expand'; +} +if (typeof HS_HIDE_ALL_TEXT === 'undefined') { + var HS_HIDE_ALL_TEXT = 'Collapse'; +} + +if (typeof HS_ALWAYS_DISPLAY_ICON === 'undefined') { + var HS_ALWAYS_DISPLAY_ICON = false; // Display an icon for all states, or + // just when closed. +} + +if (typeof HS_ICON_CLOSED === 'undefined') { + var HS_ICON_CLOSED = '►'; // black right-pointing pointer +} +if (typeof HS_ICON_OPEN === 'undefined') { + var HS_ICON_OPEN = '▼'; // black down-pointing triangle +} +if (typeof HS_ICON_EMPTY === 'undefined') { + var HS_ICON_EMPTY = '■'; // black square +} + +if (typeof HS_SHOW_ALL_OPEN_DONE_TREES === 'undefined') { + var HS_SHOW_ALL_OPEN_DONE_TREES = false; // Expand all will open DONE trees. +} + +if (typeof HS_CLASS === 'undefined') { + var HS_CLASS = 'hsCollapsible'; +} + +// Expand a header +function hsExpand(header) { + hsExpand2(header, true); +} + +// Expand a header +function hsExpand2(header, expandDoneHeader) { + // Ignore non collapsible entries + if (!header.parent().hasClass(HS_CLASS)) return; + + // Do not expand DONE node if not required + if (hsIsDoneHeader(header) && !expandDoneHeader) return; + + header.children('span[class="ellipsis"]').remove(); + if (HS_ALWAYS_DISPLAY_ICON == true) { + header.append(' ' + HS_ICON_OPEN + ''); + } + header.parent().removeClass('hsCollapsed').addClass('hsExpanded'); + header.nextAll().show(); +} + +// Expand a header and all its parents +function hsExpandParents(header) { + hsExpand(header); + header.parents('.hsCollapsed').each(function() { + hsExpand($(this).children(':header')); + }); +} + +// Collapse a header and all its parents +function hsCollapseParents(header) { + hsCollapse(header); + header.parents('.hsExpanded').each(function() { + hsCollapse($(this).children(':header')); + }); +} + +// Collapse a header +function hsCollapse(header) { + // Ignore non collapsible entries + if (!header.parent().hasClass(HS_CLASS)) return; + + header.children('span[class="ellipsis"]').remove(); + header.append(' ' + HS_ICON_CLOSED + ''); + header.parent().removeClass('hsExpanded').addClass('hsCollapsed'); + // header.nextAll().hide('fast'); + header.nextAll().hide(); +} + +// Toggle a header +function hsToggleCollapsing(header) { + if (header.parent().hasClass('hsCollapsed')) + hsExpand(header); + else if (header.parent().hasClass('hsExpanded')) + hsCollapse(header); +} + +// Expand all headers +function hsExpandAll() { + $('#content .hsCollapsed').each(function() { + hsExpand2($(this).children(':header'), HS_SHOW_ALL_OPEN_DONE_TREES); + }); +} + +// Collapse all headers +function hsCollapseAll() { + $('#content .hsExpanded').each(function() { + hsCollapse($(this).children(':header')); + }); +} + +// Collapse all visible headers +function hsCollapseAllVisible() { + $('#content .hsExpanded:visible').each(function() { + hsCollapse($(this).children(':header')); + }); +} + +// Add click events to H3/H4/H5 headers which have contents. +function hsInit() { + for (var i = 3; i <= 5; i++) { + $('#content .outline-' + i).each(function() { + var header = $(this).children(':header'); + if (header.siblings().length > 0) { + $(this).addClass(HS_CLASS); + header.css({cursor: 'pointer'}); + header.click(function() { + hsToggleCollapsing($(this)); }); + + // Allow to override global Collapse/Expand default on an entry + // basis (see property `:HTML_CONTAINER_CLASS:') + if (header.parent().hasClass('hsCollapsed')) { + hsCollapse(header); + } else if (header.parent().hasClass('hsExpanded')) { + hsExpand(header); + } else { + hsSetDefaultVisibility(header); + } + } + else { + if (HS_ALWAYS_DISPLAY_ICON == true) { + header.append(' ' + HS_ICON_EMPTY + + ''); + } + $(this).addClass('hsEmpty'); + } + }); + } + + // Add div to hold minitoc content (so it can be cleared without deleting the buttons) + $('#minitoc').append($('
            ')); + + // Add buttons + $('#minitoc').append($('
            ')); + $('.buttons').append($('' + HS_SHOW_ALL_TEXT + '') + .addClass('hsButton') + .click(hsExpandAll)); + $('.buttons').append($('' + HS_HIDE_ALL_TEXT + '') + .addClass('hsButton') + .click(hsCollapseAll)); +} + +// Returns true if a header is a DONE header +function hsIsDoneHeader(header) { + return $('span.done', header).length; +} + +// Sets the default visibility state to a header +function hsSetDefaultVisibility(header) { + if (HS_STARTUP_FOLDED) { + hsCollapse(header); + } + else { + if (!hsIsDoneHeader(header) | HS_SHOW_ALL_OPEN_DONE_TREES) { + hsExpand(header); + } + else { + hsCollapse(header); + } + } +} + +// Expands an anchor, i.e. expand all parent headers +function hsExpandAnchor(id) { + // alert(id); + if (id) { + // alert($(id + '.hsNode').length); + $(id).parents('.hsCollapsed').each(function() { + hsExpand2($(this).children(':header'), true); + }); + } +} + +// Search for next task to review, starting from beginning of current tab. +// If BACKWARD is true, search in the reverse direction. +function hsReviewTaskNext(backward) { + // If no tasks to review at all, do nothing. + if ($('.outline-2 span.todo').length == 0) return; + + if ($('.hsReviewing').length == 0) { // reviewing is starting + $('body').addClass('hsReviewing'); + $('.hsReviewPanel').addClass('hsReviewing'); + $('body').prepend('
            '); + hsAddReviewingPanels(); + + hsCollapseAll(); + // Get first task to review on each tab and expand it + $('.outline-2').each(function(){ + var firstTodo = $(this).find('span.todo:first').parent().parent(); + firstTodo.addClass('hsReview'); + hsExpandParents($(firstTodo).children(':header')); + }); + } + else { + // Get all todos + var todosElements = $('.outline-2:visible span.todo').parent().parent(); + // alert(todosElements.length); + var todos = jQuery.makeArray(todosElements); + if (backward) { + todos = todos.reverse(); + } + + // Find current review item and review the next one + var foundReview = false; + var index; + for (index = 0; index < todos.length; ++index) { + var todo = todos[index]; + if (foundReview) { + $(todo).addClass('hsReview'); + hsExpandParents($(todo).children(':header')); + break; + } + if ($(todo).hasClass('hsReview')) { + foundReview = true; + if (index < todos.length - 1) { + $(todo).removeClass('hsReview'); + hsCollapseParents($(todo).children(':header')); + } + } + } + + // Update reviewing panel + if (index < todos.length) { + var reviewItem = index + 1; + if (backward) { + reviewItem = todos.length - index; + } + + $('.outline-2:visible .hsReviewingPanel .hsReviewItem').text(reviewItem); + } + } + + // Scroll to the current review item + $('html, body').animate({ + scrollTop: $(".hsReview:visible").offset().top + }, 200); +} + +// go to previous task to review +function hsReviewTaskPrev() { + hsReviewTaskNext(true); +} + +// stop reviewing tasks +function hsReviewTaskQuit() { + $('body').removeClass('hsReviewing'); + $('.hsReviewPanel').removeClass('hsReviewing'); + $('.hsReview').removeClass('hsReview'); + $('#hsOverlay').remove(); + $('.hsReviewingPanel').remove(); +} + +function hsHideTodoKeyword(kw) { + $('span.' + kw).addClass('hsHidden').parent().parent().hide(); +} + +function hsShowTodoKeyword(kw) { + $('span.' + kw).removeClass('hsHidden').parent().parent().show(); + // XXX Show if parent is not collapsed! +} + +function hsAddReviewPanels() { + $('.outline-2').each(function(e) { + var nbItems = $(this).find('span.todo').length; + if (nbItems > 0) { + $(this).prepend('
            ' + + 'Press r or click here
            ' + + 'to review ' + nbItems + ' tasks
            ' + + '(out of ' + nbReviewTotalTasks + ' tasks)
            '); + } + }); +} + +function hsAddReviewingPanels() { + $('.outline-2').each(function(e) { + var nbItems = $(this).find('span.todo').length; + if (nbItems > 0) { + $(this).prepend('
            Reviewing task 1 / ' + nbItems + '
            ' + + '(out of ' + nbReviewTotalTasks + ' tasks)
            ' + + 'Shortcuts: ' + + 'r (next) - ' + + 'R (previous)' + + '
            '); + } + else { + $(this).prepend('
            No task to review
            ' + + '(out of ' + nbReviewTotalTasks + ' tasks)
            ' + + 'Shortcuts: ' + + 'r (next) - ' + + 'R (previous)' + + '
            '); + } + }); +} + +var nbReviewTotalTasks; + +$(document).ready(function() { + nbReviewTotalTasks = $('.outline-2 span.todo').length; + hsAddReviewPanels(); +}); diff --git a/examples/export/styles/darksun/js/jquery-1.11.0.min.js b/examples/export/styles/darksun/js/jquery-1.11.0.min.js new file mode 100644 index 0000000..73f33fb --- /dev/null +++ b/examples/export/styles/darksun/js/jquery-1.11.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
            ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
            a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
            ","
            "],area:[1,"",""],param:[1,"",""],thead:[1,"","
            "],tr:[2,"","
            "],col:[2,"","
            "],td:[3,"","
            "],_default:l.htmlSerialize?[0,"",""]:[1,"X
            ","
            "]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n("