diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 7e1b0c9..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Bug Report -description: File a bug report -# labels: ["bug"] -# assignees: -# - alphapapa -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: input - id: os-platform - attributes: - label: OS/platform - description: What operating system or platform are you running Emacs on? - validations: - required: true - - type: textarea - id: emacs-provenance - attributes: - label: Emacs version and provenance - description: What version of Emacs are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: input - id: emacs-command - attributes: - label: Emacs command - description: By what method did you run Emacs? (i.e. what command did you run?) - validations: - required: true - - type: textarea - id: org-provenance - attributes: - label: Org version and provenance - description: What version of Org are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: input - id: package-provenance - attributes: - label: org-ql package version and provenance - description: What version of org-ql are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: textarea - id: actions - attributes: - label: Actions taken - description: What actions did you take, step-by-step, in order, before the problem was noticed? - validations: - required: true - - type: textarea - id: results - attributes: - label: Observed results - description: What behavior did you observe that seemed wrong? - validations: - required: true - - type: textarea - id: expected - attributes: - label: Expected results - description: What behavior did you expect to observe? - validations: - required: true - - type: textarea - id: backtrace - attributes: - label: Backtrace - description: If an error was signaled, please use `M-x toggle-debug-on-error RET` and cause the error to happen again, then paste the contents of the `*Backtrace*` buffer here. - render: elisp - - type: textarea - id: etc - attributes: - label: Etc. - description: Any other information that seems relevant - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 7e36d80..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Support questions - url: https://github.com/alphapapa/org-ql/discussions - about: Please ask and answer support questions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 7b14c94..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Feature Request -description: File a feature request -labels: ["enhancement"] -body: - - type: input - id: os-platform - attributes: - label: OS/platform - description: What operating system or platform are you running Emacs on? - validations: - required: true - - type: textarea - id: emacs-provenance - attributes: - label: Emacs version and provenance - description: What version of Emacs are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: textarea - id: org-provenance - attributes: - label: Org version and provenance - description: What version of Org are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: input - id: package-provenance - attributes: - label: org-ql package version and provenance - description: What version of org-ql are you using, where did you acquire it, and how did you install it? - validations: - required: true - - type: textarea - id: description - attributes: - label: Description - description: Describe your request. - validations: - required: true - - type: textarea - id: etc - attributes: - label: Etc. - description: Any other information that seems relevant - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2de58b4..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,81 +0,0 @@ -# * test.yml --- Test Emacs packages using makem.sh on GitHub Actions - -# URL: https://github.com/alphapapa/makem.sh -# Version: 0.3 - -# * Commentary: - -# Based on Steve Purcell's examples at -# , -# . - -# * License: - -# This program 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 program 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. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# * Code: - -name: "CI" -on: - pull_request: - push: - # Comment out this section to enable testing of all branches. - # branches: - # - master - -jobs: - build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - emacs_version: - - 27.1 - - 27.2 - - 28.1 - - 28.2 - - 29.1 - - 29.2 - - 29.3 - - 29.4 - - snapshot - steps: - - uses: purcell/setup-emacs@master - with: - version: ${{ matrix.emacs_version }} - - - uses: actions/checkout@v2 - - - name: Initialize sandbox - run: | - SANDBOX_DIR=$(mktemp -d) || exit 1 - echo "SANDBOX_DIR=$SANDBOX_DIR" >> $GITHUB_ENV - ./makem.sh -vv --sandbox=$SANDBOX_DIR --install-deps --install-linters - - # The "all" rule is not used, because it treats compilation warnings - # as failures, so linting and testing are run as separate steps. - - - name: Lint - # NOTE: Uncomment this line to treat lint failures as passing - # so the job doesn't show failure. - continue-on-error: true - run: ./makem.sh -vv --sandbox=$SANDBOX_DIR lint - - - name: Test - if: always() # Run test even if linting fails. - run: ./makem.sh -vv --sandbox=$SANDBOX_DIR test - -# Local Variables: -# eval: (outline-minor-mode) -# End: diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9355658..0000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -*.elc -.#* -/scratch.el -.cask -/sandbox/ -/.sandbox/ -/scratch.org -/scratch/ -/worktrees/ -/worktree/ -/NOTES.org diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 94a9ed0..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program 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 program 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. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/Makefile b/Makefile deleted file mode 100644 index 64c4516..0000000 --- a/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -# * makem.sh/Makefile --- Script to aid building and testing Emacs Lisp packages - -# URL: https://github.com/alphapapa/makem.sh -# Version: 0.5 - -# * Arguments - -# For consistency, we use only var=val options, not hyphen-prefixed options. - -# NOTE: I don't like duplicating the arguments here and in makem.sh, -# but I haven't been able to find a way to pass arguments which -# conflict with Make's own arguments through Make to the script. -# Using -- doesn't seem to do it. - -ifdef install-deps - INSTALL_DEPS = "--install-deps" -endif -ifdef install-linters - INSTALL_LINTERS = "--install-linters" -endif - -ifdef sandbox - ifeq ($(sandbox), t) - SANDBOX = --sandbox - else - SANDBOX = --sandbox=$(sandbox) - endif -endif - -ifdef debug - DEBUG = "--debug" -endif - -# ** Verbosity - -# Since the "-v" in "make -v" gets intercepted by Make itself, we have -# to use a variable. - -verbose = $(v) - -ifneq (,$(findstring vvv,$(verbose))) - VERBOSE = "-vvv" -else ifneq (,$(findstring vv,$(verbose))) - VERBOSE = "-vv" -else ifneq (,$(findstring v,$(verbose))) - VERBOSE = "-v" -endif - -# * Rules - -# TODO: Handle cases in which "test" or "tests" are called and a -# directory by that name exists, which can confuse Make. - -%: - @./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS) $(@) - -.DEFAULT: init -init: - @./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS) diff --git a/NOTES b/NOTES deleted file mode 100644 index b869aeb..0000000 --- a/NOTES +++ /dev/null @@ -1 +0,0 @@ -Note: Notes are kept in the file "NOTES.org" in the "meta/notes" branch. diff --git a/NOTES.org b/NOTES.org new file mode 100644 index 0000000..cd18966 --- /dev/null +++ b/NOTES.org @@ -0,0 +1,6021 @@ +#+TITLE: Org QL Notes + +* [#A] Contents +:PROPERTIES: +:TOC: :include siblings :depth 1 :ignore this :force depth +:END: +:CONTENTS: +- [[#overview][Overview]] +- [[#tasks][Tasks]] +- [[#checklists][Checklists]] +- [[#examples--testing][Examples / testing]] +- [[#in-the-wild][In the wild]] +- [[#profiling][Profiling]] +- [[#references][References]] +- [[#testing][Testing]] +:END: + +* [#A] Overview + +** Bugs ([[org-ql-search:todo%253A%2520tags%253Abug?super-groups=%2528%2528%253Aauto-property%2520%2522Milestone%2522%2529%2529&sort=%2528todo%2529&title=%2522Bugs%2522][view]]) + +#+BEGIN: org-ql :query "todo: tags:bug" :columns ((priority "P") ((property "milestone") "M") (todo "Keyword") heading) :sort (priority todo date) :ts-format "%Y-%m-%d %H:%M" +| P | M | Keyword | Heading | +|---+-----+---------+------------------------------------------------| +| | | NEXT | [[Check Org 9.4 source code for second argument][Check Org 9.4 source code for second argument]] | +| A | 0.7 | PROJECT | [[Compatibility with Org 9.4 custom link changes][Compatibility with Org 9.4 custom link changes]] | +#+END: + +** Milestones ([[org-ql-search:todo%253A?super-groups=%2528%2528%253Aauto-property%2520%2522milestone%2522%2529%2529&sort=%2528todo%2529&title=%2522Milestones%2522][view]]) + +#+BEGIN: org-ql :query "todo: property:milestone" :columns (((property "milestone") "M") (priority "P") todo heading) :sort (priority date) :take 9 +| M | P | Todo | Heading | +|-----+---+---------+--------------------------------------------------------------------------------------------------| +| 0.7 | A | PROJECT | [[Taxy-based search view][Taxy-based search view]] | +| 0.7 | A | PROJECT | [[Compatibility with Org 9.4 custom link changes][Compatibility with Org 9.4 custom link changes]] | +| 0.7 | B | TODO | [[Verify that ~(clocked)~ regexp matches happen in logbook drawers][Verify that ~(clocked)~ regexp matches happen in logbook drawers]] | +| 0.7 | B | TODO | [[Bookmarks save list of files instead of e.g. ~org-agenda-files~ when appropriate][Bookmarks save list of files instead of e.g. ~org-agenda-files~ when appropriate]] | +| 0.7 | B | TODO | [[Change ~clocked~ and ~closed~'s behavior with just-number args][Change ~clocked~ and ~closed~'s behavior with just-number args]] | +| 0.7 | B | TODO | [[Review all normalizers for potential loops][Review all normalizers for potential loops]] | +| 0.7 | B | TODO | [[Normalize query while preserving body][Normalize query while preserving body]] | +| 0.6 | B | TODO | [[Use string queries in view headers when possible][Use string queries in view headers when possible]] | +| 0.7 | B | PROJECT | [[Logbook state change predicate][Logbook state change predicate]] | +#+END: + +** Underway ([[org-ql-search:todo%253AUNDERWAY?sort=%2528priority%2529&title=%2522Underway%2522][view]]) + +#+BEGIN: org-ql :query (or (todo "UNDERWAY") (and (todo "PROJECT") (descendants (todo "UNDERWAY")))) :columns (((property "milestone") "M") (priority "P") (todo "Keyword") heading) :sort (priority date) :ts-format "%Y-%m-%d %H:%M" +| M | P | Keyword | Heading | +|-----+---+----------+--------------------------------------------------------------------| +| 0.7 | B | PROJECT | [[Optimized, date-specific timestamp regexps][Optimized, date-specific timestamp regexps]] | +| 0.7 | B | PROJECT | [[Group tag support][Group tag support]] | +| | | UNDERWAY | [[~clocked~][~clocked~]] | +| | | UNDERWAY | [[~closed~][~closed~]] | +| | | UNDERWAY | [[~deadline~][~deadline~]] | +| | | UNDERWAY | [[~planning~][~planning~]] | +| | | UNDERWAY | [[~scheduled~][~scheduled~]] | +| | | UNDERWAY | [[~ts~][~ts~]] | +| | | UNDERWAY | [[Benchmarking tags searches without and with new group-tags support][Benchmarking tags searches without and with new group-tags support]] | +#+END: + +** To-do ([[org-ql-search:todo:?super-groups=((:todo%20"NEXT")%20(:todo%20"PROJECT")%20(:auto-priority))&sort=(todo)][view]]) + +#+BEGIN: org-ql :query "todo: priority:A" :columns ((priority "P") ((property "milestone") "M") todo heading) :sort (priority date) :take 7 +| P | M | Todo | Heading | +|---+-----+---------+------------------------------------------------| +| A | 0.7 | PROJECT | [[Taxy-based search view][Taxy-based search view]] | +| A | 0.7 | PROJECT | [[Compatibility with Org 9.4 custom link changes][Compatibility with Org 9.4 custom link changes]] | +| A | | PROJECT | [[Convert simple sexp queries to non-sexp][Convert simple sexp queries to non-sexp]] | +| A | | PROJECT | [[Org%20link%20types%20%5B2/3%5D][Org link types {2/3}]] | +#+END: + +** Stuck projects ([[org-ql-search:%2528and%2520%2528todo%2520%2522PROJECT%2522%2529%2520%2528not%2520%2528descendants%2520%2528todo%2520%2522NEXT%2522%2520%2522UNDERWAY%2522%2529%2529%2529%2529?super-groups=%2528%2528%253Aauto-property%2520%2522milestone%2522%2529%2529&sort=%2528priority%2529&title=%2522Stuck%2520Projects%2522][view]]) + +#+BEGIN: org-ql :query (and (todo "PROJECT") (not (descendants (todo "NEXT" "UNDERWAY")))) :columns (((property "milestone") "M") (priority "P") heading) :sort (priority date) :take 7 +| M | P | Heading | +|--------+---+-----------------------------------------| +| 0.7 | A | [[Taxy-based search view][Taxy-based search view]] | +| | A | [[Convert simple sexp queries to non-sexp][Convert simple sexp queries to non-sexp]] | +| | A | [[Org link types][Org link types]] | +| | B | [[Sorters with arguments][Sorters with arguments]] | +| | B | [[Outline path predicate][Outline path predicate]] | +| | B | [[Document the sorting functions][Document the sorting functions]] | +| future | B | [[Recursive queries][Recursive queries]] | +#+END: + +* [#A] Tasks +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: +:CONTENTS: +- [[#sorter-for-property-values][Sorter for property values]] +- [[#verify-that-clocked-regexp-matches-happen-in-logbook-drawers][Verify that (clocked) regexp matches happen in logbook drawers]] +- [[#add-auto-keyword-to-planning-predicate][Add :auto keyword to (planning) predicate]] +- [[#bookmarks-save-list-of-files-instead-of-eg-org-agenda-files-when-appropriate][Bookmarks save list of files instead of e.g. org-agenda-files when appropriate]] +- [[#change-deadlines-auto-argument-to-auto-andor-auto-t][Change (deadline)'s auto argument to :auto and/or :auto t]] +- [[#outline-path-in-buffers-files-arg][Outline path in buffers-files arg]] +- [[#add-more-sorters][Add more sorters?]] +- [[#default-sort][Default sort]] +- [[#partial-match-for-property-queries][Partial match for property queries]] +- [[#remove-org-ql-macro][Remove org-ql macro]] +- [[#change-clocked-and-closeds-behavior-with-just-number-args][Change clocked and closed's behavior with just-number args]] +- [[#review-all-normalizers-for-potential-loops][Review all normalizers for potential loops]] +- [[#normalize-query-while-preserving-body][Normalize query while preserving body]] +- [[#example-next-upcoming-event][Example: Next upcoming event]] +- [[#org-agenda-skip-function][org-agenda-skip-function]] +- [[#update-commentary][Update commentary]] +- [[#org-ql-block-let-org-agenda-format-its-output][org-ql-block: Let org-agenda format its output]] +- [[#omnifocus-like-screencasts][OmniFocus-like screencasts]] +- [[#use-ripgrep-to-search-unopened-files][Use ripgrep to search unopened files]] +- [[#key-cache-results-by-action-function][Key cache results by action function]] +- [[#use-session-async-to-have-a-persistent-search-process][Use session-async to have a persistent search process]] +- [[#overlay-based-caching-inspired-by-org-num-mode][Overlay-based caching inspired by org-num-mode]] +- [[#fancier-searching-for-inherited-tags][Fancier searching for inherited tags]] +- [[#taxy-based-search-view][Taxy-based search view]] +- [[#compatibility-with-org-94-custom-link-changes][Compatibility with Org 9.4 custom link changes]] +- [[#convert-simple-sexp-queries-to-non-sexp][Convert simple sexp queries to non-sexp]] +- [[#logbook-state-change-predicate][Logbook state change predicate]] +- [[#recursive-sub-queries-in-non-sexp-format][Recursive sub-queries in non-sexp format]] +- [[#optimized-date-specific-timestamp-regexps][Optimized, date-specific timestamp regexps]] +- [[#multi-pass-query-normalization][Multi-pass query normalization]] +- [[#optimization-for-olp-predicate][Optimization for olp predicate]] +- [[#outline-path-predicate][Outline path predicate]] +- [[#tools-for-saving-queries-and-accessing-them-34][Tools for saving queries and accessing them {3/4}]] +- [[#group-tag-support][Group tag support]] +- [[#document-the-sorting-functions][Document the sorting functions]] +- [[#recursive-queries][Recursive queries]] +- [[#timeline-view][Timeline view]] +- [[#implement-view-with-tabulated-list-mode-or-magit-section][Implement view with tabulated-list-mode or magit-section]] +- [[#dynamic-blocks][Dynamic blocks]] +- [[#predicate-helper-functions][Predicate helper functions]] +- [[#new-transient-transient-lisp-variable-class][New Transient transient-lisp-variable class]] +- [[#normalize-queries][Normalize queries]] +- [[#update-view-screenshots][Update view screenshots]] +- [[#test-caching][Test caching]] +- [[#alternative-parsing-libraries][Alternative parsing libraries]] +- [[#links-are-broken-in-dynamic-blocks-when-the-task-has-the-cookies--or---issue-248--alphapapaorg-ql--github][Links are broken in dynamic blocks when the task has the cookies {/} or {%} · Issue #248 · alphapapa/org-ql · GitHub]] +- [[#reverse-sorting][Reverse sorting]] +- [[#timestamp-predicates-using-relative-dates-break-caching][Timestamp predicates using relative dates break caching]] +- [[#link-target-doesnt-work][~(link :target)~ doesn't work]] +- [[#link-regexp-p-doesnt-work][~(link :regexp-p)~ doesn't work]] +- [[#fix-custom-sorter-breaks-cached-results][Fix: Custom sorter breaks cached results]] +- [[#update-dash-dependencies][Update dash dependencies]] +- [[#checking-links-for-unsafe-parameters][Checking links for unsafe parameters]] +- [[#views-multiple-sorters-are-not-preserved][Views: Multiple sorters are not preserved]] +- [[#make-dynamic-blocks-warn-about-sexp-queries][Make dynamic blocks warn about sexp queries]] +- [[#add-emacs-271-to-testyml][Add Emacs 27.1 to test.yml]] +- [[#fix-org-ql-view--link-open-on-org-93][Fix org-ql-view--link-open on Org 9.3+]] +- [[#fix-query-sexp-to-string-functions-handling-of-eg-descendants][Fix query-sexp-to-string function's handling of, e.g. descendants]] +- [[#helm-command][Helm command]] +- [[#add-a-with-time-argument-to-timestamp-predicates][Add a :with-time argument to timestamp predicates]] +- [[#node-caching]["Node" caching]] +- [[#define-predicates-with-a-macro][Define predicates with a macro]] +- [[#move-this-notes-file-into-an-orphan-metanotes-branch][Move this notes file into an orphan meta/notes branch]] +- [[#quickly-change-sortinggrouping-in-search-views][Quickly change sorting/grouping in search views]] +- [[#byte-compile-lambdas][Byte-compile lambdas]] +- [[#documentfigure-out-tag-inheritance][Document/figure out tag inheritance]] +- [[#dual-matching-with-regexp-and-predicates][Dual matching with regexp and predicates]] +- [[#operate-on-list-of-heading-positions][Operate on list of heading positions]] +- [[#use-macros-for-date][Use macros for date]] +:END: + +** TODO Split org-ql-views into default and custom views + + + Maybe this will work around this weird problem I'm having in Emacs 28.1 in which this variable is being written out to my init file in a way that causes git to think it's a binary file. + +** PROJECT org-notebook idea +:PROPERTIES: +:ID: 892fb332-be66-4b7b-a031-a20d3a1b842a +:END: +:LOGBOOK: +- State "PROJECT" from "TODO" [2022-08-19 Fri 03:57] +- Refiled on [2022-08-19 Fri 03:49] +- State "TODO" from [2020-01-16 Thu 07:20] +:END: + +[2020-01-16 Thu 07:18] + +1. The popnote command used for org-notebook should only create a new heading if there isn't already a blank one at the place a new one would be created. + 1) That may require checking and updating a timestamp at the beginning of the empty heading's content as well. +2. The tree sidebar should be displayed, as well as the item sidebar listing entries grouped by date, similar to the alpha-org screenshots. +3. If the command is called twice in a row, close the notebook. + 1) This probably will require some additions to yequake to allow predicates to control the toggle action for some yequake-frames. + +[2022-08-19 Fri 03:48] [[file:~/src/emacs/org-popnote/][Local repo]] + +*** org-notely lives +:PROPERTIES: +:ID: 879d690b-05ef-4d2a-ae69-5e0531bc33a5 +:END: +:LOGBOOK: +- Refiled on [2022-08-19 Fri 03:52] +:END: + +[2022-08-15 Mon 19:33] Renamed org-notepad, after talking with benoitj in #systemcrafters. Yeah, it's another package that ends in Y, but... + +** TODO Put default org-ql-views into separate variable + +Separating the default views from user-created ones will make updating the default ones much easier. It also prevents suddenly writing all of the default views into the user's custom file when the user adds or changes one. + +** DONE Refile command +:PROPERTIES: +:milestone: 0.7 +:END: + +#+begin_src elisp + (defun org-ql-find-refile (marker) + "Refile current entry to MARKER. + Interactively, refile to one selected with `org-ql-find'." + (interactive (let* ((marker) + ;; HACK: It would be better if `org-ql-find' + ;; accepted an action function as an argument. + (org-ql-find-goto-hook + (list (lambda () + (setf marker (point-marker)))))) + (save-excursion + (save-window-excursion + ;; HACK: Working around `org-ql-find's changing buffer and window. + (org-ql-find (org-ql-search-directories-files) + :prompt "Refile to: "))) + (list marker))) + (org-refile + nil nil + ;; The RFLOC argument: + (list + ;; Name + (org-with-point-at marker + (nth 4 (org-heading-components))) + ;; File + (buffer-file-name (marker-buffer marker)) + ;; nil + nil + ;; Position + marker))) +#+end_src + +** TODO org-rifle-like predicate +:PROPERTIES: +:milestone: 0.7 +:END: + +Seems to work really well. I think it should probably be the default predicate, rather than ~regexp~, because it's so useful and intuitive. + +#+begin_src elisp + (org-ql-defpred (rifle R) (&rest args) + "Docstring" + :preambles ((`(,predicate-names . ,args) + (list :regexp (rx-to-string `(seq bow (or ,@args))) + :case-fold t :query query))) + :body (cl-loop for arg in args + always (or (regexp arg) + (outline-path arg)))) +#+end_src + +That's easily used in the new ~org-ql-find~ command like so: + +#+begin_src elisp + (defun ap/org-ql-rifle () + (interactive) + (org-ql-find (current-buffer) :query-prefix "rifle:" + :query-filter (lambda (string) + (replace-regexp-in-string (rx (1+ space)) "," string t t)))) +#+end_src + +** PROJECT [#B] Sorters with arguments + +*** TODO Document new sorters + +*** TODO [#B] New sorter validator function + +Needs to handle all valid sorting arguments. + +*** MAYBE Sorter-defining macro + +*** TODO Sorter for tags + +*** TODO Sorter for category + +*** TODO Sorter for property values :enhancement: +:PROPERTIES: +:milestone: future +:END: + +[2021-06-18 Fri 02:26] e.g. to sort entries in this file by the =milestone= property. Something like: + +#+BEGIN_SRC elisp + (org-ql-search (current-buffer) + '(todo) + :sort '((property "milestone") priority)) +#+END_SRC + +** TODO [#B] Verify that ~(clocked)~ regexp matches happen in logbook drawers +:PROPERTIES: +:milestone: 0.7 +:END: + +[2022-03-07 Mon 12:45] Just realized that it doesn't already verify this. + +** TODO [#B] Add ~:auto~ keyword to ~(planning)~ predicate + +It should act like ~(or (deadline auto) (scheduled :to today))~. + +** TODO [#B] Bookmarks save list of files instead of e.g. ~org-agenda-files~ when appropriate +:PROPERTIES: +:milestone: 0.7 +:END: + +[2020-12-05 Sat 01:24] The bookmarked plist should use the "contracted" form rather than the list of files so that, if the list of files changes before the bookmark is loaded, it will use the new list of files. + +[2021-06-18 Fri 03:12] This has the potential to cause bugs, so I'm going to defer it until at least 0.7. + +** TODO [#B] Change ~(deadline)~'s ~auto~ argument to ~:auto~ and/or ~:auto t~ + +For consistency, because plain ~auto~ looks like a variable, and even though it's in a quoted form, it could be confusing. + + +** TODO [#B] Outline path in buffers-files arg +:PROPERTIES: +:ID: 6935361a-9e1d-48ec-8d17-876a90b90f50 +:END: + +e.g. + +#+BEGIN_SRC elisp + (org-ql (olp "~/org/inbox.org" "Emacs" "Ideas") + (todo "NEXT")) +#+END_SRC + +Also, should support an ~id~ one. + +** TODO [#B] Add more sorters? + ++ [ ] =category= ++ [ ] Any date :: e.g. it would search for timestamps (active/inactive?) anywhere in an entry + +** TODO [#B] Default sort + +Would probably be useful to have a default sort option. + +** TODO [#B] Partial match for property queries + +e.g. something like [[https://200ok.ch/posts/2020-02-09_creating_org_mode_sparse_trees_in_emacs_and_organice.html][Organice has now]]. + +[2020-02-13 Thu 00:42] Something is needed to help search property values by partial matches. For example: + +#+BEGIN_SRC org + ,* [[https://github.com/fniessen/org-html-themes][org-html-themes: Framework including two themes, Bigblow and ReadTheOrg]] + ,:PROPERTIES: + ,:author: Fabrice Niessen + ,:END: + +#+END_SRC + +Searching that with a query like =property:author=Fabrice= returns nothing; the full value must be used, like ~property:author="Fabrice Niessen"~. It should be possible to do something like ~property:author=~Fabrice~ to search for partial matches. + +** TODO Remove ~org-ql~ macro +:PROPERTIES: +:milestone: 0.7 +:END: + +0.6 will be the last stable release to have it. + +** TODO [#B] Change ~clocked~ and ~closed~'s behavior with just-number args +:PROPERTIES: +:milestone: 0.7 +:END: + ++ [[file:~/src/emacs/org-ql/tests/test-org-ql.el::;;%20TODO:%20While%20it%20seems%20helpful%20for%20(clocked)%20and%20(close)%20to][To-do item]] + +#+BEGIN_SRC elisp + ;; TODO: While it seems helpful for (clocked) and (close) to + ;; implicitly look into the past (because entries can't be + ;; clocked or closed in the future), it makes the API + ;; inconsistent. It would be better to be consistent and + ;; require the user to pass these predicates a negative number. +#+END_SRC + +** TODO [#B] Review all normalizers for potential loops +:PROPERTIES: +:milestone: 0.7 +:END: +:LOGBOOK: +- Note taken on [2022-03-04 Fri 03:27] \\ + Moving to 0.7 since I tagged 0.6 already. +:END: + +Since I found (and fixed) one after I made normalizers apply repeatedly, I should check the rest, because there might be a few more that could do it. + +** TODO [#B] Normalize query while preserving body :enhancement: +:PROPERTIES: +:milestone: 0.7 +:END: + +Sometimes it would be helpful to define a predicate that normalizes to another predicate while having a different body. [[https://github.com/plundaahl/dotfiles/blob/ef4d25498ab895c3c22b588ae2506fd69bdd8755/emacs/package-defs/org-ql/pred-created.el#L19][For example]]: + +#+BEGIN_SRC elisp + (org-ql-defpred created (&key from to on) + "Search for entries with \"CREATED\" property in range or on date" + :body (if (and on (or from to)) + (error "Either specify FROM and/or TO, or ON") + (let ((heading-time (pcl/as-ts (org-entry-get (point) "CREATED"))) + (on (pcl/as-ts on)) + (from (pcl/as-ts (or on from))) + (to (pcl/as-ts (or on to)))) + (and t + (if from (ts<= from heading-time) t) + (if to (ts< heading-time (ts-adjust 'day +1 to)) t))))) +#+END_SRC + +That predicate would best be normalized to ~(property "CREATED")~, but its body would still need to be evaluated to compare the property value as a timestamp. There are two obvious possibilities: + +1. Normalize it to ~(and (property "CREATED") (...form that compares (org-entry-get (point) "CREATED") as a timestamp...))~, without a defined body. +2. Allow ~-defpred~ to normalize the query to ~(property "CREATED")~ while preserving a separate body. Maybe something like normalizing it to ~(and (property "CREATED") (created ...))~, which would still call the body; however, the normalizer must avoid looping on ~(created ...)~, so maybe a sentinel value is needed. + +** TODO [#C] Example: Next upcoming event :docs: + + +[2020-11-26 Thu 18:17] Inspired by [[https://github.com/unhammer/org-upcoming-modeline][GitHub - unhammer/org-upcoming-modeline: put upcoming org event in modeline]] ([[https://www.reddit.com/r/emacs/comments/k1e4eu/show_next_orgappointment_in_modeline/gdp6fqv/][Reddit thread]]). + +#+BEGIN_SRC elisp + (pcase-let* ((now (ts-now)) + (items (org-ql-select (org-agenda-files) + '(ts-active :from 0 :to 1) + :action '(cons (save-excursion + (car (sort (cl-loop while (re-search-forward org-tsr-regexp nil t) + collect (ts-parse-org (match-string 1))) + #'ts<))) + (point-marker)))) + (`(,time . ,marker) (car (seq-sort-by #'car #'ts< items))) + (heading (org-with-point-at marker + (org-link-display-format (nth 4 (org-heading-components))))) + (seconds-until (ts-difference time now)) + ;; NOTE: Using day of year to avoid end-of-month turnover in day number. + (days-until (- (ts-doy time) (ts-doy now))) + (time-string (cond ((<= seconds-until org-upcoming-modeline-duration-threshold) + (ts-human-format-duration seconds-until 'abbreviate)) + ((= 0 days-until) + (ts-format "%H:%M" time)) + ((= 1 days-until) + (ts-format "tomorrow %H:%M" time)) + (t ; > 1 days-until + (ts-format "%a %H:%M" time))))) + (propertize (format " ⏰ %s: %s" time-string heading) + 'face 'org-level-4 + 'help-echo (format "%s at <%s>" heading + (ts-format "%Y-%m-%d %H:%M" time)))) +#+END_SRC + +** TODO [#C] ~org-agenda-skip-function~ + +As discussed [[https://www.reddit.com/r/emacs/comments/cnrt2d/orgqlblock_integrates_orgql_into_org_agenda/ewi1q36/][here]], this is a cool feature that allows further integration into existing custom agenda commands. Example: + +#+BEGIN_SRC elisp + ;;; lima-0ac22.el --- -*- lexical-binding: t; -*- + + (defun org-ql-skip-function (query) + "Return a function for `org-agenda-skip-function' for QUERY. + Compared to using QUERY in `org-ql', this effectively turns QUERY + into (not QUERY)." + (let* ((predicate (org-ql--query-predicate '(regexp "ryo-modal")))) + (lambda () + ;; This duplicates the functionality of `org-ql--select'. + (let (orig-fns) + (--each org-ql-predicates + ;; Save original function mappings. + (let ((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))) + ;; Run query. + ;; FIXME: "If this function returns nil, the current match should not be skipped. + ;; Otherwise, the function must return a position from where the search + ;; should be continued." + (funcall predicate)) + (--each orig-fns + ;; Restore original function mappings. + (fset (plist-get it :name) (plist-get it :fn)))))))) + + (let ((org-agenda-custom-commands + '(("z" "Z" + ((tags-todo "PRIORITY=\"A\"+Emacs/!SOMEDAY")) + ((org-agenda-skip-function (org-ql-skip-function '(regexp "ryo-modal"))))) + ((org-agenda-files ("~/org/inbox.org")))))) + (org-agenda nil "z")) +#+END_SRC + +I should benchmark it to see how much difference it makes, because all those ~fset~ calls on each heading isn't free. But if a macro were used to rewrite the built-in predicates to their full versions, all of that could be avoided... + +** TODO [#C] Update commentary + +** MAYBE org-ql-block: Let org-agenda format its output + +[2020-11-16 Mon 22:12] As suggested by Kevin J. Foley. See [[https://github.com/alphapapa/org-ql/pull/113#issuecomment-728674220][#113]]. It might actually be simple to do and might work very well. + +** MAYBE OmniFocus-like screencasts + +[2020-11-18 Wed 03:03] Looking at OmniFocus's web site now, their short videos showing features demonstrate a lot of features that exist in org-ql already. After implementing section-based views, it would be cool to make some short demos showing similar features. + +** MAYBE Use ripgrep to search unopened files + +[2020-11-26 Thu 02:20] It [[https://github.com/BurntSushi/ripgrep/issues/176][supports multiline search now]], so it might be suitable now. + +** MAYBE Key cache results by action function + +Could allow reusing results for different queries. Mentioned in [[https://www.reddit.com/r/emacs/comments/kewl3a/how_are_folks_using_orgagenda_and_orgroam_together/gg8iytf/][Reddit discussion]]. + +** MAYBE Use [[https://codeberg.org/FelipeLema/session-async.el][session-async]] to have a persistent search process + +That could run queries in the other process and then send results to the main Emacs process. + +** MAYBE [#C] Overlay-based caching inspired by org-num-mode + +[2019-12-30 Mon 22:42] Newer versions of Org have =org-num-mode=, which uses =font-lock= and =after-change-functions= to update overlays in the buffer with outline numbering. Maybe a similar approach could be used to cache arbitrary values for headings in a buffer without having to discard the whole buffer's cache when the buffer changes. + +[2020-11-09 Mon 01:51] I feel like that's probably unlikely to work well. I imagine it would require storing the query at every heading, which would be very wasteful. As well, adding more overlays to an Org buffer is probably not a good idea, because there are already enough of those. + +However, there might still be a useful idea here somewhere... + +** MAYBE [#C] Fancier searching for inherited tags + +When tag inheritance is enabled, and the given tags aren't file-level tags, we could search directly to headings containing the matching tags, and then only do per-heading matching on the subtrees. Sometimes that would be much faster. However, that might make the logic special-cased and complicated. Might need a redesign of the whole matching/predicate system to do cleanly. + +** PROJECT [#A] Taxy-based search view +:PROPERTIES: +:milestone: 0.7 +:END: + +[2022-03-04 Fri 03:45] For 0.7, this should be optional, and org-super-agenda should still be supported for grouping. Maybe in the future that wouldn't be necessary, especially if org-super-agenda were also to use Taxy. + +** PROJECT [#A] Compatibility with Org 9.4 custom link changes :bug: +:PROPERTIES: +:milestone: 0.7 +:END: +:LOGBOOK: +- Note taken on [2022-03-04 Fri 03:29] \\ + Not sure if I already did this for 0.6. Retargeting for 0.7. +:END: + +[2020-11-13 Fri 22:36] From [[https://www.orgmode.org/Changes.html][the changelog]]: + +#+BEGIN_QUOTE +Calling conventions changes when opening or exporting custom links + +This changes affects export back-ends, and libraries providing new link types. + +Function used in :follow link parameter is required to accept a second argument. Likewise, function used in :export parameter needs to accept a fourth argument. See org-link-set-parameters for details. + +Eventually, the function org-export-custom-protocol-maybe is now called with a fourth argument. Even though the 3-arguments definition is still supported, at least for now, we encourage back-end developers to switch to the new signature. +#+END_QUOTE + +Unfortunately it does not say what the new, required second argument is. + +[2020-11-22 Sun 17:22] For now, I'll add an optional, ignored second argument to the follow function; if I'm lucky, it will work anyway. + +*** NEXT Check Org 9.4 source code for second argument + +** PROJECT [#A] Convert simple sexp queries to non-sexp +:PROPERTIES: +:END: + +[2020-11-11 Wed 00:28] This will be very helpful for storing links. Surely simple ones won't be too hard... + +#+BEGIN_SRC elisp + (defun org-ql--query-sexp-to-plain (query) + "Return a plain query string for sexp QUERY. + If QUERY can't be converted to a plain one, return nil." + ;; This started out pretty simple...but at least it's not just one long function, right? + (cl-labels ((complex-p (query) + (or (contains-p 'or query))) + (contains-p (symbol list) + (cl-loop for element in list + thereis (or (eq symbol element) + (and (listp element) + (contains-p symbol element))))) + (format-args + (args) (let (non-paired paired next-keyword) + (cl-loop for arg in args + do (cond (next-keyword (push (cons next-keyword arg) paired) + (setf next-keyword nil)) + ((keywordp arg) (setf next-keyword (substring (symbol-name arg) 1))) + (t (push arg non-paired)))) + (string-join (append (mapcar #'format-atom non-paired) + (nreverse (--map (format "%s=%s" (car it) (cdr it)) + paired))) + ","))) + (format-atom + (atom) (cl-typecase atom + (string (if (string-match (rx space) atom) + (format "%S" atom) + (format "%s" atom))) + (t (format "%s" atom)))) + (format-form + (form) (pcase form + (`(not . (,rest)) (concat "!" (format-form rest))) + (`(priority . ,_) (format-priority form)) + ;; FIXME: Convert (src) queries to non-sexp form...someday... + (`(src . ,_) (user-error "Converting (src ...) queries to non-sexp form is not implemented")) + (_ (pcase-let* ((`(,pred . ,args) form) + (args-string (pcase args + ('() "") + ((guard (= 1 (length args))) (format "%s" (car args))) + (_ (format-args args))))) + (format "%s:%s" pred args-string))))) + (format-and + (form) (pcase-let* ((`(and . ,rest) form)) + (string-join (mapcar #'format-form rest) " "))) + (format-priority + (form) (pcase-let* ((`(priority . ,rest) form) + (args (pcase rest + (`(,(and comparator (or < <= > >= =)) ,letter) + (priority-letters comparator letter)) + (_ rest)))) + (concat "priority:" (string-join args ",")))) + (priority-letters + (comparator letter) (let* ((char (string-to-char (upcase (symbol-name letter)))) + (numeric-priorities '(?A ?B ?C)) + ;; NOTE: The comparator inversion is intentional. + (others (pcase comparator + ('< (--select (> it char) numeric-priorities)) + ('<= (--select (>= it char) numeric-priorities)) + ('> (--select (< it char) numeric-priorities)) + ('>= (--select (<= it char) numeric-priorities)) + ('= (--select (= it char) numeric-priorities))))) + (mapcar #'char-to-string others)))) + (unless (complex-p query) + (pcase query + (`(and . ,_) (format-and query)) + (_ (format-form query)))))) + + (--map (cons it (org-ql--query-sexp-to-plain it)) + '((priority >= B) + (priority > B) + (priority < B) + (priority < A) + (priority = A) + (todo) + (todo "TODO") + (todo "TODO" "NEXT") + (ts :from -1 :to 1) + (ts :on today) + (ts-active :from "2017-01-01" :to "2018-01-01") + (heading "quoted phrase" "word") + (and (tags "book" "books") (priority "A")) + (and (tags "space") (not (regexp "moon"))) + (src :lang "elisp" :regexps ("defun"))) + + + ) +#+END_SRC + +[2020-11-11 Wed 01:45] Seems to work well. Now to integrate that into link-saving... + +[2020-11-11 Wed 02:41] Seems to work. Will [[https://github.com/alphapapa/org-ql/issues/147#issuecomment-725287074][wait for feedback]] before merging. + +[2020-11-11 Wed 19:13] Seems to be working properly. One more thing to do though, I think: + +*** TODO [#B] Use string queries in view headers when possible +:PROPERTIES: +:milestone: 0.6 +:END: + +Maybe make it an option to automatically convert them when possible, because if a user wanted to add complexity to a string query, he'd have to rewrite it as a sexp. + +** PROJECT [#B] Logbook state change predicate :enhancement: +:PROPERTIES: +:milestone: 0.7 +:END: + ++ [[https://github.com/alphapapa/org-ql/issues/192][Feature Request : Search Logbook to find all tasks that changed state on a specific date · Issue #192 · alphapapa/org-ql · GitHub]] ++ [[https://github.com/alphapapa/org-ql/pull/245][add predicate for checking DONE state changes by teeann · Pull Request #245 · alphapapa/org-ql · GitHub]] + +[2022-02-21 Mon 19:32] I'd like to use this myself right now. + +[2022-03-04 Fri 03:30] Some early plans: + ++ A simple string argument, like ~(logbook "foo")~, should match if an entry has that string in a logbook drawer. ++ A sexp argument, like ~(logbook (ts :from "2022-01-01"))~, should match if an entry matches that sexp while narrowed to its logbook. + - This might allow for optimizations such as searching for the timestamp regexp across an entire buffer and then confirming with a body predicate (which would be faster than checking every entry for a logbook). ++ State changes could be handled something like ~(logbook :from "TODO" :to "NEXT")~. + - OTOH, the ~:from~ and ~:to~ could be confused with timestamp arguments. And maybe those should be used directly, rather than using a sexp argument, e.g. ~(logbook :from "2022-01-01")~. + - Alternative: ~(logbook :state ("TODO" "NEXT"))~ would search for changes from TODO to NEXT. It could be combined with a date, like ~(logbook :state ("TODO" "NEXT") :on "2022-01-01")~, and to search for any state change on that date could be something like ~(logbook :state t :on "2022-01-01")~. + +I posted these to #192. + +[2022-03-07 Mon 12:42] New branch =wip/logbook-predicate=. + +[2022-03-07 Mon 13:50] Did some work on the predicate. Regexp-making function works. + +*** NEXT Check feedback on #192 +SCHEDULED: <2022-03-18 Fri> + +** PROJECT Recursive sub-queries in non-sexp format + +[2021-09-08 Wed 15:41] Eric Abrahamsen showed me this in =#emacs:matrix.org=: + +#+BEGIN_SRC elisp + (defvar test-pexs + '((query (+ (or compound-term term))) + (term (or subquery prefixed-term kv-term value) term-end) + (subquery "(" query ")" + `(query -- (if (= 1 (length query)) query (list query)))) + (prefixed-term (or negated-term near-term)) + (negated-term (or "not " "-") term + `(term -- (list 'not term))) + (near-term "near " term + `(term -- (list 'near term))) + (compound-term (or or-terms and-terms)) + (or-terms (or subquery prefixed-term term) "or " (or subquery prefixed-term term) + `(t1 t2 -- (list 'or t1 t2))) + (and-terms (or subquery prefixed-term term) "and " (or subquery prefixed-term term) + `(t1 t2 -- (list 'and t1 t2))) + (value (or quoted-value plain-value)) + (plain-value (substring (+ [word]))) + (quoted-value "\"" (substring (+ (not "\"") (any))) "\"") + (kv-term plain-value ":" value + `(k v -- (cons (intern k) v))) + (term-end (opt (+ [space]))))) +#+END_SRC + +#+BEGIN_QUOTE +The INFINITE RECURSIVE SUBQUERIES is particularly nice + +​BTW, I see what you mean about the peg macros, that's a real pain. I have a small patch that should fix that + +​This was half-cribbed from org-ql to begin with, so it's only right that you get something in return :) + +​The subtlety is all in the ordering of the or statements +#+END_QUOTE + +It should be pretty easy to add some of that to our query parsing. + +** PROJECT [#B] Optimized, date-specific timestamp regexps :enhancement: +:PROPERTIES: +:milestone: 0.7 +:ID: fc8ccf6e-5311-4121-a0b7-58482dbd2e85 +:END: + ++ In branch =wip/ts-optimized-regexps=. + +[2020-11-28 Sat 19:12] For example, a regexp like this would make timestamp searches very fast: + +#+BEGIN_SRC elisp + (rx "<" "2020" "-" (or "10-31" "11-01" "11-02") ">") + ;;=> "<2020-\\(?:\\(?:1\\(?:0-31\\|1-0[12]\\)\\)\\)>" +#+END_SRC + +It shouldn't be hard to generate a list of date strings that =rx-to-string= could optimize, and that would avoid parsing and testing many timestamps which wouldn't match when specific date ranges are given. + +*** TODO Limit or optimize + +[2020-12-22 Tue 04:55] Noticed that the tests affected by this change are now slower in the test suite, presumably because they span a range of years and spend more time incrementing ~ts~ structs in the regexp-building function than running the search on the small amount of test data. For smaller date ranges, and for searching larger sets of data, the performance is improved. So there needs to be some kind of heuristic to handle this. Or maybe the regexp-building function could be smarter rather than "brute-forcing" its way through every date in the range. + +(This also shows how valuable Buttercup's showing of each test's duration is, otherwise I might not have noticed this issue.) + +#+BEGIN_EXAMPLE + inactive + without arguments (preamble) (2.43ms) + without arguments (no preamble) (2.58ms) + :from a timestamp (preamble) (421.88ms) + :from a timestamp (no preamble) (8.65ms) + :from a number of days (preamble) (20.77ms) + :from a number of days (no preamble) (3.06ms) + :to a timestamp (preamble) (424.83ms) + :to a timestamp (no preamble) (8.48ms) + :to a number of days (preamble) (220.44ms) + :to a number of days (no preamble) (2.68ms) + :on a timestamp (preamble) (9.51ms) + :on a timestamp (no preamble) (8.03ms) + :on a number of days (preamble) (67.11ms) + :on a number of days (no preamble) (3.25ms) +#+END_EXAMPLE + +[2020-12-22 Tue 19:16] It should be simple to do something like this: + ++ If the range spans at least a year, include every day and month number, and each year number. ++ If the range spans less than a year but at least a month, include every day number, each month number, and the year number(s). ++ If the range spans less than a month, probably just increment and collect the numbers, like the code currently does (which will ensure the proper result in case the range spans the end of a month and/or year). + +If necessary, another step could be to divide the range into three parts: the middle being the part that spans whole months/years, and then the ends being the parts that span partial months/years. Then a regexp could be made for each part and combined into a single regexp which would match each part separately (rather than combining all numbers into one potential timestamp). + +*** UNDERWAY ~clocked~ + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +**** DONE Benchmark + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("unoptimized" + (progn + (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. + "Return non-nil if current entry was clocked in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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)) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(clocked :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))) + + ("optimized" + (progn + (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. + "Return non-nil if current entry was clocked in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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 . ,(and rest (guard (or (plist-get rest :from) + (plist-get rest :to) + (plist-get rest :on))))) + ;; Use date-optimized timestamp regexp. + (-let (((&plist :from :to :on :type) rest)) + (org-ql--from-to-on) + (list :regexp (-let* ((from (or from (ts-adjust 'day (- org-ql-ts-days-from-default) (ts-now)))) + (to (or to (ts-adjust 'day org-ql-ts-days-to-default (ts-now)))) + (ts-regexp (org-ql--ts-range-to-regexp from to :type 'inactive))) + (rx-to-string `(seq bol (0+ blank) "CLOCK:" (1+ blank) (0+ not-newline) (regexp ,ts-regexp)))) + :query query))) + (`(,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)) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(clocked :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))))) + +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------+--------------------+---------------+----------+------------------| +| optimized | 1.85 | 1.746526 | 0 | 0 | +| unoptimized | slowest | 3.239420 | 0 | 0 | + +*** UNDERWAY ~closed~ + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +**** DONE Benchmark + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("unoptimized" + (progn + (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. + "Return non-nil if current entry was closed in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(closed :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))) + + ("optimized" + (progn + (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. + "Return non-nil if current entry was closed in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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 . ,(and rest (guard (or (plist-get rest :from) + (plist-get rest :to) + (plist-get rest :on))))) + (-let (((&plist :from :to :on :type) rest)) + (org-ql--from-to-on) + (list :regexp (-let* ((from (or from (ts-adjust 'day (- org-ql-ts-days-from-default) (ts-now)))) + (to (or to (ts-adjust 'day org-ql-ts-days-to-default (ts-now)))) + (ts-regexp (org-ql--ts-range-to-regexp from to :type 'inactive))) + (rx-to-string `(seq bow (0+ blank) "CLOSED:" (1+ blank) (regexp ,ts-regexp)))) + :query query))) + (`(,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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(closed :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------+--------------------+---------------+----------+------------------| +| unoptimized | 1.04 | 0.382831 | 0 | 0 | +| optimized | slowest | 0.396930 | 0 | 0 | + +*** UNDERWAY ~deadline~ + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +**** DONE Benchmark + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("unoptimized" + (progn + (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. + "Return non-nil if current entry has deadline in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(deadline :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))) + + ("optimized" + (progn + (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. + "Return non-nil if current entry has deadline in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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 . ,(and rest (guard (or (plist-get rest :from) + (plist-get rest :to) + (plist-get rest :on))))) + ;; Use date-optimized timestamp regexp. + (-let (((&plist :from :to :on :type) rest)) + (org-ql--from-to-on) + (list :regexp (-let* ((from (or from (ts-adjust 'day (- org-ql-ts-days-from-default) (ts-now)))) + (to (or to (ts-adjust 'day org-ql-ts-days-to-default (ts-now)))) + (ts-regexp (org-ql--ts-range-to-regexp from to :type 'active))) + (rx-to-string `(seq bow (0+ blank) "DEADLINE:" (1+ blank) (regexp ,ts-regexp)))) + :query query))) + (`(,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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(deadline :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------+--------------------+---------------+----------+------------------| +| unoptimized | 1.54 | 0.258749 | 0 | 0 | +| optimized | slowest | 0.397937 | 0 | 0 | + +*** UNDERWAY ~planning~ + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +**** DONE Benchmark + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("unoptimized" + (progn + (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. + "Return non-nil if current entry has planning timestamp in given period (i.e. its deadline, scheduled, or closed timestamp). + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(planning :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))) + + ("optimized" + (progn + (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. + "Return non-nil if current entry has planning timestamp in given period (i.e. its deadline, scheduled, or closed timestamp). + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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 . ,(and rest (guard (or (plist-get rest :from) + (plist-get rest :to) + (plist-get rest :on))))) + (-let (((&plist :from :to :on :type) rest)) + (org-ql--from-to-on) + (list :regexp (-let* ((from (or from (ts-adjust 'day (- org-ql-ts-days-from-default) (ts-now)))) + (to (or to (ts-adjust 'day org-ql-ts-days-to-default (ts-now)))) + (ts-regexp (org-ql--ts-range-to-regexp from to))) + (rx-to-string `(seq bow (0+ blank) (or "CLOSED" "DEADLINE" "SCHEDULED") ":" + (1+ blank) (regexp ,ts-regexp)))) + :query query))) + (`(,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))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(planning :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------+--------------------+---------------+----------+------------------| +| optimized | 1.28 | 0.450906 | 0 | 0 | +| unoptimized | slowest | 0.576033 | 0 | 0 | + +*** UNDERWAY ~scheduled~ +:LOGBOOK: +CLOCK: [2020-12-20 Sun 07:01]--[2020-12-20 Sun 07:58] => 0:57 +:END: + +**** TODO Benchmark + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +*** UNDERWAY ~ts~ + +[2020-12-19 Sat 05:55] Seems to be working well. Not sure how best to integrate with other timestamp-related predicates. The code isn't that much, so maybe copying it into each predicate would be best. + +**** TODO Tests + +**** DONE Use optimized regexp in predicate + +**** DONE Benchmark + +[2020-12-20 Sun 07:51] This is a great improvement. I tested it on the ~scheduled~ predicate too, but it doesn't make nearly as big a difference, because just searching for the ~SCHEDULED:~ prefix avoids testing most timestamps. + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("unoptimized" + (progn + (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--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 + ;; TO are actually expected to be `ts' structs. The docstring is written for users. + "Return non-nil if current entry has a timestamp in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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. + + TYPE may be `active' to match active timestamps, `inactive' to + match inactive ones, or `both' / nil to match both types. + + LIMIT bounds the search for the timestamp REGEXP. It defaults to + 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)))) + (test-timestamps (pred-form) + `(cl-loop for next-ts = (next-timestamp) + while next-ts + thereis ,pred-form))) + (save-excursion + (cond ((not (or from to)) (re-search-forward regexp limit t)) + ((and from to) (test-timestamps (ts-in from to next-ts))) + (from (test-timestamps (ts<= from next-ts))) + (to (test-timestamps (ts<= next-ts to))))))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(ts :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))) + + ("optimized" + (progn + (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--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 + ;; TO are actually expected to be `ts' structs. The docstring is written for users. + "Return non-nil if current entry has a timestamp in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + 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. + + TYPE may be `active' to match active timestamps, `inactive' to + match inactive ones, or `both' / nil to match both types. + + LIMIT bounds the search for the timestamp REGEXP. It defaults to + 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 . ,(and rest (guard (or (plist-get rest :from) + (plist-get rest :to) + (plist-get rest :on))))) + (-let (((&plist :from :to :on :type) rest)) + (org-ql--from-to-on) + (list :regexp (-let* ((from (or from (ts-adjust 'day (- org-ql-ts-days-from-default) (ts-now)))) + (to (or to (ts-adjust 'day org-ql-ts-days-to-default (ts-now))))) + (org-ql--ts-range-to-regexp from to)) + :query query))) + (`(,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)))) + (test-timestamps (pred-form) + `(cl-loop for next-ts = (next-timestamp) + while next-ts + thereis ,pred-form))) + (save-excursion + (cond ((not (or from to)) (re-search-forward regexp limit t)) + ((and from to) (test-timestamps (ts-in from to next-ts))) + (from (test-timestamps (ts<= from next-ts))) + (to (test-timestamps (ts<= next-ts to))))))) + (setf org-ql-cache (make-hash-table :weakness 'key)) + (org-ql-select (org-agenda-files) + '(ts :from "2020-01-01" :to "2020-12-31") + :action '(substring-no-properties (org-get-heading t t))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------+--------------------+---------------+----------+------------------| +| optimized | 3.78 | 1.243185 | 0 | 0 | +| unoptimized | slowest | 4.700824 | 0 | 0 | + +** PROJECT Multi-pass query normalization + +It would allow, e.g. one query to be normalized into another, and then into another. It would be helpful for timestamp-related ones, I think. + +** PROJECT [#B] Optimization for ~olp~ predicate + +I noticed that, of course, a search like =h:bar olp:foo= is much faster than =olp:foo,bar=. Then I realized that the last argument to =olp= could be removed and replaced with a =heading= predicate with that argument, which is much faster. + +[2020-12-05 Sat 01:27] However, this does not always work correctly, as [[https://github.com/alphapapa/org-ql/issues/160#issuecomment-738524404][yantar92 reported]]: + +#+BEGIN_QUOTE +Consider the following +example: + + * No deadline + ** Learn + *** Research + **** Plasticity + ***** TODO Schwaiger [MRS-Fall] (2017) Characterizing the mechanical properties of individual phases in nanostructured composites + +I may try to match the last heading like the following "olp:dead phase". +It will not match. +#+END_QUOTE + +*** TODO Devise solution to desired ~olp~ optimization + +*** NEXT Write test for ~olp~ that catches bug that attempted optimization caused + + + + +** PROJECT [#B] Outline path predicate + +[2019-10-07 Mon 11:15] There are two potential types of matching on outline paths: matching on any part of the outline path, and matching a specific path. For example, with this file: + +#+BEGIN_SRC org + ,* Food + + ,** Fruits + + ,*** Blueberries + + ,*** Grapes + + ,** Vegetables + + ,*** Carrots + + ,*** Potatoes +#+END_SRC + +Matching could work like this: + ++ ~(outline "Food")~ :: Would return all nodes. ++ ~(outline "Fruits")~ :: Would return all fruits. + +Matching at a specific path would be something like: + ++ ~(outline-path "Food" "Fruits")~ :: Would return all fruits. But if there were another =Fruits= heading somewhere in the file, under a different outline path, it would not return its nodes. + +I'm not sure the second type of matching belongs in predicates, but rather in [[id:6935361a-9e1d-48ec-8d17-876a90b90f50][this]]. + +To implement this with good performance probably needs an outline-path cache. I can probably repurpose the tags caching, but maybe it should be generalized. + +[2019-10-07 Mon 13:09] This is basically done with =be2bf6df316b96b3ed56851b8ffe0e227796b621= and =be2bf6df316b96b3ed56851b8ffe0e227796b621=, but not the specific-path matching. I left a =MAYBE= in the code about "anchored" path matching, which would accomplish that. + +** PROJECT Tools for saving queries and accessing them [3/4] + ++ Added example to =examples.org=. + +*** PROJECT [#A] Org link types [2/3] +:PROPERTIES: +:ID: 4db73c1c-a4ed-425e-9e38-8d334ed03e1e +:END: + +This would be useful for having a menu of saved queries as Org links, or even bookmarking saved queries. + +**** TODO For saved queries + +**** DONE For searches + +[2020-11-08 Sun 22:59] Let's try a very simple implementation so I could write a link like this to search the current buffer: + +#+BEGIN_SRC org + [[org-ql-search:property:author="AUTHOR"]] +#+END_SRC + +[2020-11-08 Sun 23:22] Seems to work! + +#+BEGIN_SRC elisp :results silent + ;;;; Org link type + + ;; This section adds a custom link type to Org. See info:org#Adding hyperlink types. + + (org-link-set-parameters "org-ql-search" + :follow #'org-ql-search--link-open + :store #'org-ql-search--link-store) + + (defun org-ql-search--link-open (query) + "Open Org QL QUERY for current buffer." + (org-ql-search (current-buffer) query)) + + (defun org-ql-search--link-store () + "Store a link to current Org QL query." + ;; TODO: When we have an org-ql-view-mode, test it here instead of org-ql-view-query. + (when org-ql-view-query + (org-store-link-props :type "org-ql-search" + :link (concat "org-ql-search:" (org-ql-view--format-query org-ql-view-query)) + :description org-ql-view-title) + t)) +#+END_SRC + +Tested on these queries: + +#+BEGIN_SRC org + + [[org-ql-search:(property%20:author%20"Chris%20Wellons")][org-ql-search:(property :author "Chris Wellons")]] + + [[org-ql-search:(link%20"nullprogram")][org-ql-search:(link "nullprogram")]] + + [[org-ql-search:link:nullprogram]] +#+END_SRC + +[2020-11-10 Tue 00:35] I'd like to support other parameters to the search, like grouping and sorting, so: + +#+BEGIN_SRC elisp :results silent + ;;;; Org link type + + ;; This section adds a custom link type to Org. See info:org#Adding hyperlink types. + + (org-link-set-parameters "org-ql-search" + :follow #'org-ql-search--link-open + :store #'org-ql-search--link-store) + + (defun org-ql-search--link-open (query) + "Open Org QL QUERY for current buffer." + (require 'url-parse) + (pcase-let* ((`(,query . ,params) + (url-path-and-query (url-parse-make-urlobj "org-ql-search" nil nil nil nil + query))) + (params (url-parse-query-string params)) + ;; Hacky or elegant? + (_ (mapc (lambda (pair) + (cl-callf (lambda (it) + (intern (concat ":" it))) + (car pair)) + (cl-callf read (cdr pair))) + params)) + (params (cl-loop for (key . value) in params + append (list key value)))) + (apply #'org-ql-search (current-buffer) query params))) + + (defun org-ql-search--link-store () + "Store a link to current Org QL query." + (when org-ql-view-query + (org-store-link-props :type "org-ql-search" + :link (concat "org-ql-search:" (org-ql-view--format-query org-ql-view-query)) + :description org-ql-view-title) + t)) +#+END_SRC + +That seems to work, like: + +#+BEGIN_SRC org + [[org-ql-search:property:author="Chris%20Wellons"?super-groups=((:auto-outline-path%20t))]] +#+END_SRC + +[2020-11-10 Tue 01:34] Okay, this seems to take care of all parameters: + +#+BEGIN_SRC elisp + (defun org-ql-search--link-open (path) + "Open Org QL query for current buffer at PATH. + PATH should be the part of an \"org-ql-search:\" URL after the + protocol. See, e.g. `org-ql-search--link-store'." + (require 'url-parse) + (require 'url-util) + (pcase-let* ((`(,query . ,params) (url-path-and-query + (url-parse-make-urlobj "org-ql-search" nil nil nil nil + path))) + (query (url-unhex-string query)) + (params (when params + (url-parse-query-string params))) + ;; `url-parse-query-string' returns "improper" alists, which makes this awkward. + (sort (when (alist-get "sort" params nil nil #'string=) + (read (alist-get "sort" params nil nil #'string=)))) + (groups (when (alist-get "super-groups" params nil nil #'string=) + (read (alist-get "super-groups" params nil nil #'string=)))) + (title (when (alist-get "title" params nil nil #'string=) + (read (alist-get "title" params nil nil #'string=))))) + (org-ql-search (current-buffer) query + :sort sort + :super-groups groups + :title title))) + + (defun org-ql-search--link-store () + "Store a link to the current Org QL view. + Only views that search a single buffer may be linked to." + (require 'url-parse) + (require 'url-util) + (unless (or (bufferp org-ql-view-buffers-files) (= 1 (length org-ql-view-buffers-files))) + (user-error "Only views searching a single buffer may be linked")) + (when org-ql-view-query + (let* ((params (list (when org-ql-view-super-groups + (list "super-groups" (prin1-to-string org-ql-view-super-groups))) + (when org-ql-view-sort + (list "sort" (prin1-to-string org-ql-view-sort))) + (when org-ql-view-title + (list "title" (prin1-to-string org-ql-view-title))))) + (filename (concat (url-hexify-string (org-ql-view--format-query org-ql-view-query)) + "?" (url-build-query-string (delete nil params)))) + (url (url-recreate-url (url-parse-make-urlobj "org-ql-search" nil nil nil nil + filename)))) + (org-store-link-props + :type "org-ql-search" + :link url + :description (concat "org-ql-search: " org-ql-view-title))) + t)) +#+END_SRC + +**** DONE For all parameters + +*** DONE Bookmarks + +[2020-11-08 Sun 23:25] Already done in =e5b4cd106558790563af26a8e32ec9508f904855=. + +*** DONE Access saved query from saved query list + +*** DONE Save query from ql-agenda buffer + +** PROJECT [#B] Group tag support +:PROPERTIES: +:milestone: 0.7 +:END: + ++ [[https://github.com/alphapapa/org-ql/issues/145][Tag hierarchy support · Issue #145 · alphapapa/org-ql · GitHub]] ++ [[https://github.com/alphapapa/org-ql/pull/146][Change: (org-ql--tags-at) Support tag hierarchies by maxchaos · Pull Request #146 · alphapapa/org-ql · GitHub]] + +*** UNDERWAY Benchmarking tags searches without and with new group-tags support + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 10 :ensure-equal t + :forms (("without group-tags support" + (org-ql-select (org-ql-search-directories-files) + '(tags "Emacs") + :action #'point)) + )) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------------+--------------------+---------------+----------+------------------| +| without group-tags support | slowest | 5.512271 | 0 | 0 | + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 10 :ensure-equal t + :forms (("with group-tags support" + (org-ql-select (org-ql-search-directories-files) + '(tags "Emacs") + :action #'point)) + )) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------------+--------------------+---------------+----------+------------------| +| with group-tags support | slowest | 5.154639 | 0 | 0 | + +[2020-11-09 Mon 17:43] I think I need to enhance the benchmarking macros to make this easier. But that might require copying much of =benchmark-run-compiled=, so let me try something else: This is messy, but it ought to be fair enough (the only difference being the minor change in =org-ql--tags-at=. + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 10 :ensure-equal t + :forms (("without group-tags support" + (progn + (setf org-ql-cache (make-hash-table :weakness 'key) + org-ql-tags-cache (make-hash-table :weakness 'key) + org-ql-node-value-cache (make-hash-table :weakness 'key)) + (defun org-ql--expand-tag-hierarchy (tags &optional excluded) + "Return TAGS along with their associated group tags. + This function recursively searches for groups that each given tag belongs to, + directly or indirectly, and includes the corresponding group tags to the result. + + TAGS should be a list of tags (i.e., strings). + If non-nil, EXCLUDED should be a list of group tags that will not be + automatically added to the results unless they are already in TAGS." + (let ((groups (org-tag-alist-to-groups org-current-tag-alist)) + (excluded (append tags excluded))) + (let (group-tags) + (dolist (tag tags) + (pcase-dolist (`(,group-tag . ,group-members) groups) + (when (and (not (member group-tag excluded)) + ;; Check if one of the members in the group matches tag. + ;; Notice that each member may be a plain string or + ;; a regexp pattern (enclosed between curly brackets). + (--some (if (string-match-p "^[{].+[}]$" it) + ;; If pattern (it) is a regexp, remove the brackets and + ;; make sure that it either matches the whole tag or not. + (string-match-p (concat "^" (substring it 1 -1) "$") tag) + ;; Check if member (it) is identical to tag. + (string= it tag)) + group-members)) + (push group-tag group-tags)))) + ;; If group tags not already included have been found, + ;; then recursively expand them as well. + ;; Notice that by passing (group-tags excluded) to the next call + ;; instead of ((append tags group-tags)) ensures that we do not + ;; unnecessarily loop over the elements of TAGS more than once. + (if group-tags + (append tags (org-ql--expand-tag-hierarchy group-tags excluded)) + tags)))) + (defun org-ql--tags-at (position) + "Return tags for POSITION in current buffer. + Returns cons (INHERITED-TAGS . LOCAL-TAGS)." + ;; I'd like to use `-if-let*', but it doesn't leave non-nil variables + ;; bound in the else clause, so destructured variables that are non-nil, + ;; like found caches, are not available in the else clause. + (if-let* ((buffer-cache (gethash (current-buffer) org-ql-tags-cache)) + (modified-tick (car buffer-cache)) + (tags-cache (cdr buffer-cache)) + (buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (cached-result (gethash position tags-cache))) + ;; Found in cache: return them. + (pcase cached-result + ('org-ql-nil nil) + (_ cached-result)) + ;; Not found in cache: get tags and cache them. + (let* ((local-tags (or (when (looking-at org-ql-tag-line-re) + (split-string (match-string-no-properties 2) ":" t)) + 'org-ql-nil)) + (inherited-tags (or (when org-use-tag-inheritance + (save-excursion + (if (org-up-heading-safe) + ;; Return parent heading's tags. + (-let* (((inherited local) (org-ql--tags-at (point))) + (tags (when (or inherited local) + (cond ((and (listp inherited) + (listp local)) + (->> (append inherited local) + -non-nil -uniq)) + ((listp inherited) inherited) + ((listp local) local))))) + (cl-typecase org-use-tag-inheritance + (list (setf tags (-intersection tags org-use-tag-inheritance))) + (string (setf tags (--select (string-match org-use-tag-inheritance it) + tags)))) + (pcase org-tags-exclude-from-inheritance + ('nil tags) + (_ (-difference tags org-tags-exclude-from-inheritance)))) + ;; Top-level heading: use file tags. + org-file-tags))) + 'org-ql-nil)) + (all-tags (list inherited-tags local-tags))) + ;; Check caches again, because they may have been set now. + ;; TODO: Is there a clever way we could avoid doing this, or is it inherently necessary? + (setf buffer-cache (gethash (current-buffer) org-ql-tags-cache) + modified-tick (car buffer-cache) + tags-cache (cdr buffer-cache) + buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (unless (and buffer-cache buffer-unmodified-p) + ;; Buffer-local tags cache empty or invalid: make new one. + (setf tags-cache (make-hash-table)) + (puthash (current-buffer) + (cons (buffer-modified-tick) tags-cache) + org-ql-tags-cache)) + (puthash position all-tags tags-cache)))) + (org-ql-select (org-ql-search-directories-files) + '(tags "Emacs") + :action #'point))) + ("with group-tags support" + (progn + (setf org-ql-cache (make-hash-table :weakness 'key) + org-ql-tags-cache (make-hash-table :weakness 'key) + org-ql-node-value-cache (make-hash-table :weakness 'key)) + (defun org-ql--expand-tag-hierarchy (tags &optional excluded) + "Return TAGS along with their associated group tags. + This function recursively searches for groups that each given tag belongs to, + directly or indirectly, and includes the corresponding group tags to the result. + + TAGS should be a list of tags (i.e., strings). + If non-nil, EXCLUDED should be a list of group tags that will not be + automatically added to the results unless they are already in TAGS." + (let ((groups (org-tag-alist-to-groups org-current-tag-alist)) + (excluded (append tags excluded))) + (let (group-tags) + (dolist (tag tags) + (pcase-dolist (`(,group-tag . ,group-members) groups) + (when (and (not (member group-tag excluded)) + ;; Check if one of the members in the group matches tag. + ;; Notice that each member may be a plain string or + ;; a regexp pattern (enclosed between curly brackets). + (--some (if (string-match-p "^[{].+[}]$" it) + ;; If pattern (it) is a regexp, remove the brackets and + ;; make sure that it either matches the whole tag or not. + (string-match-p (concat "^" (substring it 1 -1) "$") tag) + ;; Check if member (it) is identical to tag. + (string= it tag)) + group-members)) + (push group-tag group-tags)))) + ;; If group tags not already included have been found, + ;; then recursively expand them as well. + ;; Notice that by passing (group-tags excluded) to the next call + ;; instead of ((append tags group-tags)) ensures that we do not + ;; unnecessarily loop over the elements of TAGS more than once. + (if group-tags + (append tags (org-ql--expand-tag-hierarchy group-tags excluded)) + tags)))) + (defun org-ql--tags-at (position) + "Return tags for POSITION in current buffer. + Returns cons (INHERITED-TAGS . LOCAL-TAGS)." + ;; I'd like to use `-if-let*', but it doesn't leave non-nil variables + ;; bound in the else clause, so destructured variables that are non-nil, + ;; like found caches, are not available in the else clause. + (if-let* ((buffer-cache (gethash (current-buffer) org-ql-tags-cache)) + (modified-tick (car buffer-cache)) + (tags-cache (cdr buffer-cache)) + (buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (cached-result (gethash position tags-cache))) + ;; Found in cache: return them. + (pcase cached-result + ('org-ql-nil nil) + (_ cached-result)) + ;; Not found in cache: get tags and cache them. + (let* ((local-tags (or (when (looking-at org-ql-tag-line-re) + (split-string (match-string-no-properties 2) ":" t)) + 'org-ql-nil)) + (inherited-tags (or (when org-use-tag-inheritance + (save-excursion + (if (org-up-heading-safe) + ;; Return parent heading's tags. + (-let* (((inherited local) (org-ql--tags-at (point))) + (tags (when (or inherited local) + (cond ((and (listp inherited) + (listp local)) + (->> (append inherited local) + -non-nil -uniq)) + ((listp inherited) inherited) + ((listp local) local))))) + (cl-typecase org-use-tag-inheritance + (list (setf tags (-intersection tags org-use-tag-inheritance))) + (string (setf tags (--select (string-match org-use-tag-inheritance it) + tags)))) + (pcase org-tags-exclude-from-inheritance + ('nil tags) + (_ (-difference tags org-tags-exclude-from-inheritance)))) + ;; Top-level heading: use file tags. + org-file-tags))) + 'org-ql-nil)) + all-tags) + (when org-group-tags + (unless (eq local-tags 'org-ql-nil) + (setq local-tags (org-ql--expand-tag-hierarchy local-tags))) + (unless (eq inherited-tags 'org-ql-nil) + (setq inherited-tags (org-ql--expand-tag-hierarchy inherited-tags)))) + (setq all-tags (list inherited-tags local-tags)) + ;; Check caches again, because they may have been set now. + ;; TODO: Is there a clever way we could avoid doing this, or is it inherently necessary? + (setf buffer-cache (gethash (current-buffer) org-ql-tags-cache) + modified-tick (car buffer-cache) + tags-cache (cdr buffer-cache) + buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (unless (and buffer-cache buffer-unmodified-p) + ;; Buffer-local tags cache empty or invalid: make new one. + (setf tags-cache (make-hash-table)) + (puthash (current-buffer) + (cons (buffer-modified-tick) tags-cache) + org-ql-tags-cache)) + (puthash position all-tags tags-cache)))) + (org-ql-select (org-ql-search-directories-files) + '(tags "Emacs") + :action #'point))) + )) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------------+--------------------+---------------+----------+------------------| +| without group-tags support | 1.01 | 52.832562 | 4 | 1.989522 | +| with group-tags support | slowest | 53.425342 | 5 | 2.479128 | + +[2020-11-09 Mon 17:57] Well, the performance difference seems smaller than I expected. For single iterations, it ought to be unnoticeable. Although I'm still a bit skeptical about this benchmark: I feel like it ought to have more of an impact than that, but maybe I'm wrong--and that would be great! + +Next steps: + ++ [X] Post benchmark code on PR and ask Panagiotis to verify ++ [X] Also ask him to run benchmark actually using group tags (since I don't actually have any, even though the boolean is t) ++ [X] Discuss caching of group tag expansion. It seems like we ought to cache the expansions as well, because sibling headings (especially at level 1) ought to get their group tags re-expanded individually, even when we've already expanded them for another heading. ++ [X] Remove unused =result= variable + +** PROJECT [#B] Document the sorting functions + +Note that the built-in sorting only works on Org elements, which is the default ~:action~. So if a different action is used, sorting will not work. In that case, the action should be mapped across the Org element results from outside the ~org-ql~ form. + + +** PROJECT [#B] Recursive queries +:PROPERTIES: +:milestone: future +:END: + +For lack of a better term. A way to query for certain headings, and then gather results of a different query at each result of the first query, displaying all results in a single view. + +This works pretty well. It needs polishing, and some refactoring so items can be indented completely (rather than leaving the keyword unindented, as it is now). + +#+BEGIN_SRC elisp + (cl-defun org-ql-agenda-recursive (buffers-or-files queries &key action narrow sort) + (cl-labels ((rec (queries element indent) + (org-with-point-at (org-element-property :org-marker element) + (when-let* ((results (progn + (org-narrow-to-subtree) + (org-ql-select (current-buffer) + (car queries) + :action 'element-with-markers + :narrow t + :sort sort)))) + ;; Indent entry for each level + (setf results (--map + (org-element-put-property it :raw-value + (concat (s-repeat (* 5 indent) " ") + (org-element-property :raw-value it))) + results)) + (cons it (if (cdr queries) + (--map (rec (cdr queries) it) + results) + results)))))) + (when-let* ((indent 0) + (results (org-ql-select buffers-or-files + (car queries) + :action 'element-with-markers + :narrow narrow + :sort sort))) + (->> (if (cdr queries) + (--map (rec (cdr queries) it (1+ indent)) + results) + results) + (-flatten-n (1- (length queries))) + -non-nil + (org-ql-agenda--agenda nil nil + :entries))))) + + (cl-defun org-ql-select-recursive (buffers-or-files queries &key action narrow sort) + (cl-labels ((rec (queries element indent) + (org-with-point-at (org-element-property :org-marker element) + (when-let* ((results (progn + (org-narrow-to-subtree) + (org-ql-select (current-buffer) + (car queries) + :action 'element-with-markers + :narrow t + :sort sort)))) + ;; Indent entry for each level + (setf results (--map + (org-element-put-property it :raw-value + (concat (s-repeat (* 5 indent) " ") + (org-element-property :raw-value it))) + results)) + (cons it (if (cdr queries) + (--map (rec (cdr queries) it) + results) + results)))))) + (when-let* ((indent 0) + (results (org-ql-select buffers-or-files + (car queries) + :action 'element-with-markers + :narrow narrow + :sort sort))) + (->> (if (cdr queries) + (--map (rec (cdr queries) it (1+ indent)) + results) + results) + (-flatten-n (1- (length queries))) + -non-nil)))) +#+END_SRC + +** PROJECT [#B] Timeline view +:PROPERTIES: +:ID: 00573552-ffe9-4608-8904-7f6c73246b6d +:milestone: future +:END: + +e.g. as mentioned by Samuel Wales at https://lists.gnu.org/archive/html/emacs-orgmode/2019-08/msg00330.html. Prototype code: + +#+BEGIN_SRC elisp + (cl-defun org-ql-timeline (buffers-files query) + (let ((results + (org-ql-select buffers-files + query :action + (lambda () + (let* ((heading-string + (->> (org-element-headline-parser + (line-end-position)) + org-ql--add-markers + org-ql-agenda--format-element)) + (timestamps + (cl-loop with limit = (org-entry-end-position) + while (re-search-forward org-ts-regexp-both + limit t) + collect (ts-parse-org (match-string 0)))) + (timestamp-strings + (->> timestamps + (-sort #'ts<) + (--map (concat " " (ts-format it)))))) + (s-join "\n" (cons heading-string timestamp-strings)))) + :sort '(date)))) + (org-ql-agenda--agenda nil nil :strings results))) + + (org-ql-timeline (org-agenda-files) + '(and "Emacs" (ts))) + + ;; More timeline-like version, organized by date rather than task. + + (cl-defun org-ql-timeline* (buffers-files query &key filter-ts) + (let* ((ts-ht (ht)) + (results (org-ql-select buffers-files + query + :action (lambda () + (let* ((heading-string + (->> (org-element-headline-parser + (line-end-position)) + org-ql--add-markers + org-ql-agenda--format-element)) + (date-timestamps + ;; Each one set to 00:00:00. + (cl-loop with limit = (org-entry-end-position) + while (re-search-forward org-ts-regexp-both + limit t) + collect (->> (match-string 0) + ts-parse-org + (ts-apply :hour 0 :minute 0 :second 0))))) + (setf date-timestamps (delete-dups date-timestamps)) + (when filter-ts + (setf date-timestamps (cl-remove-if-not filter-ts date-timestamps))) + (--each date-timestamps + (push heading-string (gethash it ts-ht))))))) + (tss-sorted (-sort #'ts< (ht-keys ts-ht))) + (strings (cl-loop for ts in tss-sorted + collect (concat "\n" + (propertize (ts-format "%Y-%m-%d" ts) + 'face 'org-agenda-structure)) + append (ht-get ts-ht ts)))) + (org-ql-agenda--agenda nil nil :strings strings))) + + (org-ql-timeline* (org-agenda-files) + '(ts :from -14) + :filter-ts `(lambda (ts) + (ts<= ,(ts-adjust 'day -14 (ts-now)) ts))) +#+END_SRC + +Another, more up-to-date implementation: + +#+BEGIN_SRC elisp + ;; NOTE: ts structs don't (sometimes? or always?) compare properly + ;; with default hash tables, e.g. this code: + + ;; (let* ((ts-a #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1572670800.0)) + ;; (ts-b #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1572584400.0))) + ;; (list :equal (equal ts-a ts-b) + ;; :sxhash-equal (equal (sxhash ts-a) (sxhash ts-b)))) ;;=> (:equal nil :sxhash-equal t) + + ;; So we must use the "contents-hash" table as described in the Elisp manual. + (define-hash-table-test 'contents-hash 'equal 'sxhash-equal) + + (cl-defun org-ql-view-timeline (buffers-files &key from to on) + "FIXME: DOcstring" + (cl-flet ((parse-ts-arg + (arg type) + ;; Parse ARG as a string or TS struct and adjust it to the beginning + ;; or end of its day, depending on whether TYPE is `:begin' or `:end'. + (-let (((hour minute second) (cl-ecase type + (:begin '(0 0 0)) + (:end '(23 59 59))))) + (->> (cl-typecase arg + (string (ts-parse arg)) + (ts arg)) + (ts-apply :hour hour :minute minute :second second))))) + (let* ((ts-predicate `(lambda (ts) + ,(cond (on `(ts-in ,(parse-ts-arg on :begin) + ts + ,(parse-ts-arg on :end))) + ((and from to) `(ts-in ,(parse-ts-arg from :begin) + ts + ,(parse-ts-arg to :end))) + (from `(ts<= ,(parse-ts-arg from :begin) ts)) + (to `(ts<= ts ,(parse-ts-arg to :end))) + (t (user-error "Huh?"))))) + (query (cond (on `(ts :from ,(parse-ts-arg on :begin) + :to ,(parse-ts-arg on :end))) + (t (append (list 'ts) + (when from + `(:from ,(parse-ts-arg from :begin))) + (when to + `(:to ,(parse-ts-arg to :end))))))) + (date-ts-table (make-hash-table :test 'contents-hash)) + (_results (org-ql-select buffers-files query + :action (lambda () + (let* ((string (->> (org-element-headline-parser + (line-end-position)) + org-ql--add-markers + org-ql-view--format-element))) + (cl-loop with limit = (org-entry-end-position) + while (re-search-forward org-ts-regexp-both limit t) + for ts = (->> (match-string 0) ts-parse-org) + when (funcall ts-predicate ts) + do (cl-pushnew (cons ts (concat (ts-format " %H:%M" ts) + string)) + (gethash (ts-apply :hour 0 :minute 0 :second 0 ts) + date-ts-table) + :test #'equal)))))) + (date-tss-sorted (->> date-ts-table hash-table-keys (-sort #'ts<))) + (string (cl-loop for date-ts in date-tss-sorted + for date-string = (propertize (ts-format "%Y-%m-%d" date-ts) + 'face 'org-agenda-structure) + concat (concat "\n" date-string) + concat (cl-loop for (ts . entry) in (->> (gethash date-ts date-ts-table) + (-sort (-on #'ts< #'car))) + concat (concat "\n" entry))))) + (org-ql-view--display :buffer "Timeline" + :header "Timeline" + :string string)))) + + ;; Used like: + ;; (org-ql-view-timeline "~/org/main.org" :from "2019-11-01") +#+END_SRC + +[2019-09-26 Thu 21:28] Would probably make sense to implement this using the view-sections someday. + +** PROJECT [#B] Implement view with tabulated-list-mode or magit-section + +[2019-09-02 Mon 05:20] Especially with some of the new packages that make =tabulated-list-mode= easier to use, like =navigel=. However, it would probably break grouping, or require some kind of adapter or extension to do grouping, so I don't know if that would work. Something like =magit-section= would be more flexible, and could be recursively grouped, like in =magit-todos=. + +[2019-09-08 Sun 10:06] Came up with a prototype yesterday, in branch =wip/view-section=. Seems to work pretty well. + +One of the things in that branch is =org-ql-item=, which is a struct used to carry data for query results. It seems to work well. + +Another idea for it is to simply store the element from =org-element-headline-parser= in one of its slots, and populate all of the other slots lazily, like =ts=. It already does that for a couple of slots, but I think it makes sense to do it for all of them, to reduce the overhead of making the struct for every query result. + +*** MAYBE [#C] Experiment with =widget= +:PROPERTIES: +:milestone: future +:END: + +The code that powers the customization UI. Has collapsible and customizable widgets. Might be perfect. Might even enable editing items in the list, with functions to make the changes in the source buffers. + +*** Code idea + +Inserting items into a view could look something like this: + +#+BEGIN_SRC elisp + (org-ql-view--insert-items + :header (ts-format "%Y-%m-%d" (ts-now)) + :items (org-ql-query + :select #'org-ql-current-item + :from (org-agenda-files) + :where '(or (deadline auto) + (scheduled :on today) + (ts-active :on today))) + :group-by '(org-ql-item-priority + org-ql-item-todo)) +#+END_SRC + +Items would be structs, and the =group-by= argument would be a list of accessors, like how =magit-todos= works. Arbitrary functions could also be passed to =group-by=, as whatever value the function returns is used to group them. =org-ql-current-item= would be a function that turns the result of =org-element-headline-parser= into the struct. + +Not sure if it should automatically add the number of items to the header, or if that should be done manually. + +*** Prior art + +**** [[https://github.com/m2ym/direx-el][GitHub - m2ym/direx-el: Directory Explorer for GNU Emacs]] + +Appears to be another implementation of magit-section-like expandable sections. Not sure which came first. Its code seems like it may be helpful. + +**** magit-section + +** PROJECT Dynamic blocks + +*** PROJECT [#B] Save view to dynamic block +:PROPERTIES: +:milestone: 0.7 +:END: + +[2020-11-10 Tue 04:31] A command would save users from having to write out the dynamic block manually. + +[2020-11-12 Thu 03:23] A command to do this would be very helpful. (Yes, I entered this idea twice. I should use my own systems better, apparently. But that's what this package is all about, right?) + +**** NEXT Lookup Org function to create dynamic blocks + +**** NEXT Write function to save view to dynamic block + +*** DONE [#A] Implement dynamic blocks + ++ *Tasks* + - [X] Merge code + - [X] Document the feature + +For example, [[https://egli.dev/posts/using-org-mode-for-meeting-minutes/][this blog article]] shows a way that Org's existing dynamic =columnview= blocks can be very useful. =org-ql= queries could be useful in them as well. + +[2020-11-09 Mon 22:00] I just realized that this is probably much easier than I realized. + ++ [[info:org#Dynamic%20blocks][info:org#Dynamic blocks]] + +#+BEGIN_SRC elisp + (cl-defun org-dblock-write:org-ql (params) + "FIXME: Docstring" + (pcase-let* (((map :query :columns :sort :ts-format) params) + (format-fns (list (cons 'heading (lambda (element) + (org-make-link-string (org-element-property :raw-value element) + (org-element-property :raw-value element)))) + (cons 'todo (lambda (element) + (or (org-element-property :todo-keyword element) + ""))) + (cons 'priority (lambda (element) + (--if-let (org-element-property :priority element) + (char-to-string it) + ""))) + (cons 'deadline (lambda (element) + (--if-let (org-element-property :deadline element) + (ts-format ts-format (ts-parse-org-element it )) + ""))) + (cons 'scheduled (lambda (element) + (--if-let (org-element-property :scheduled element) + (ts-format ts-format (ts-parse-org-element it )) + ""))))) + (elements (org-ql-query :from (current-buffer) + :where query + :select '(org-element-headline-parser (line-end-position)) + :order-by sort))) + (cl-labels ((format-element + (element) (string-join (cl-loop for column in columns + for fn = (alist-get column format-fns) + collect (funcall fn element)) + " | "))) + (insert "| " (string-join (--map (capitalize (symbol-name it)) columns) " | ") " |" "\n") + (insert "|- \n") + (dolist (element elements) + (insert "| " (format-element element) " |" "\n")) + (org-table-align)))) +#+END_SRC + +[2020-11-09 Mon 22:35] This works pretty well! For example: + +#+BEGIN_SRC org + ,#+BEGIN: org-ql :query (todo) :format (priority todo heading deadline scheduled) :sort (priority date) :ts-format "%Y-%m-%d %H:%M" + | Priority | Todo | Heading | Deadline | Scheduled | + |----------+-------+------------+------------------+------------------| + | A | TODAY | Heading 1 | 2020-11-11 00:00 | | + | B | TODO | Heading 2 | | 2020-11-09 00:00 | + + ,#+END: + + ,#+BEGIN: columnview :id global :hlines t :indent t + | ITEM | TODO | PRIORITY | TAGS | + |----------------+-------+----------+------| + | Test heading 1 | TODAY | B | | + |----------------+-------+----------+------| + | Heading 2 | TODO | B | | + ,#+END: + + ,* TODAY [#A] Heading 1 + DEADLINE: <2020-11-11 Wed> + + ,* TODO [#B] Heading 2 + SCHEDULED: <2020-11-09 Mon> + +#+END_SRC + +[2020-11-10 Tue 00:03] I think it's ready to merge now. + +*** DONE [#A] Org block to insert results of queries with links to entries +:PROPERTIES: +:ID: 422754bb-3a7c-4dbf-b303-4056d3cafb7e +:END: + +[2020-01-16 Thu 06:20] This idea just came to me when I was thinking about using the search-based paradigm vs. outline-based. This would allow both, e.g. some kind of =#+BEGIN_QUERY= block that would update when =C-c C-c= is pressed on it. + +[2020-11-13 Fri 22:57] I keep rediscovering ideas that I've had previously. This is now done as the dynamic block feature. I guess I should actually use these tools I've made. + +** PROJECT [#B] Predicate helper functions +:PROPERTIES: +:milestone: future +:END: + +[2020-11-24 Tue 16:50] This idea started off by writing a =week= predicate: + +#+BEGIN_SRC elisp + (org-ql-defpred week (&optional relative) + "Match entries with a timestamp in the calendar week RELATIVE. + RELATIVE is a number relative to the current week (i.e. 0 or nil + is this week, -1 is last week, 1 is next week)." + :normalizers ((`(,predicate-names . ,(or `(,(and (pred numberp) relative)) `nil)) + (let* ((relative (or relative 0)) + (now (ts-now)) + (week-start (->> now + (ts-adjust 'day (- 0 (ts-dow now))) + (ts-adjust 'day (* 7 relative)) + (ts-apply :hour 0 :minute 0 :second 0))) + (week-end (->> now + (ts-adjust 'day (- 7 (ts-dow now))) + (ts-adjust 'day (* 7 relative)) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(ts :from ,week-start :to ,week-end))) + (`(,predicate-names ,(and (pred stringp) date)) + (let* ((then (ts-parse date)) + (week-start (->> then + (ts-adjust 'day (- 0 (ts-dow then))) + (ts-apply :hour 0 :minute 0 :second 0))) + (week-end (->> then + (ts-adjust 'day (- 7 (ts-dow then))) + (ts-apply :hour 23 :minute 59 :second 59)))) + `(ts :from ,week-start :to ,week-end))))) +#+END_SRC + +That works pretty well: + +#+BEGIN_SRC elisp :results code + (list (org-ql--normalize-query '(week)) + (org-ql--normalize-query '(week -1)) + (org-ql--normalize-query '(week "2020-11-01"))) +#+END_SRC + +#+RESULTS: +#+BEGIN_SRC elisp + ((ts :from #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1606024800.0) + :to #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1606715999.0)) + (ts :from #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1605420000.0) + :to #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1606111199.0)) + (ts :from #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1604206800.0) + :to #s(ts nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil 1604901599.0))) +#+END_SRC + +(Hmm, the patterns in those beginning-of-week and end-of-week timestamps are interesting...) + +And that's pretty useful. But what if someone wanted to write a query like ~(closed :on (week -1))~ to match entries closed in the last week? It wouldn't help at all. + +One idea would be to add a =type= argument to the =week= predicate. But that would be awkward and a bit ugly. And it wouldn't solve the problem, anyway, because while the =ts= predicate can take a type argument for =active=, =inactive=, or =both=, it can't do, e.g. =clocked=, =closed=, =deadline=, etc. + +So what's really needed is a way to insert the week-based arguments into other selectors. But that would require there to be a ~(week)~ function defined, which would mean polluting the global namespace and potential conflicts. + +So maybe another macro could define "helper" functions which would be available in the normalizers. Or maybe the =defpred= macro could take another argument for "helpers", although that would probably require binding them around each =pcase= expression--not hard, but not especially elegant. + +The next question is, how would those work in string queries? I'm not sure there's a good way to translate them. + +** PROJECT [#C] [[https://github.com/magit/transient/issues/76][New Transient transient-lisp-variable class]] :compatibility: +:PROPERTIES: +:milestone: 0.7 +:END: + +[2020-10-19 Mon 00:23] Should try to use this instead of whatever bespoke code is currently used. + +** PROJECT [#C] Normalize queries +:PROPERTIES: +:milestone: future +:END: + +[2019-07-16 Tue 11:49] This serves two purposes: + +1. Equivalent queries will return the same results from the cache. +2. The selectors that can be converted to the fastest preamble regexps will be sorted first, so the fastest preamble will be used. Although this may not always be straightforward. For example, in a file with only a few =TODO= items, the ~(todo "TODO")~ selector would convert to a preamble that would quickly search through the file. But if there were a thousand =TODO= items, it wouldn't be as much of a benefit, and a ~(regexp "something")~ selector's preamble might be much faster, depending on how many times =something= appears in the file. + +So the second purpose might actually be a drawback, because it would prevent users from optimizing their queries with knowledge of their data. Maybe there should be an option to not normalize queries, so advanced users can order their selectors manually. + +** PROJECT [#C] Update view screenshots + +e.g. doesn't currently show the =View= header. + +** PROJECT [#C] Test caching + +See notes on 1dce9467f25428b5289d3665cd840820969ed65a. It would be good to test the caching explicitly, at least for some queries, because if I were to completely break it again, in such a way that results were stored but retrieval always failed, the tests wouldn't catch it. + +** CANCELED [#C] Alternative parsing libraries + ++ e.g. Bovine and Wisent come with Emacs, which would allow us to drop the =peg= dependency +(which doesn't use lexical binding)+ ++ [[https://github.com/cute-jumper/parsec.el][parsec]] is third-party, but looks powerful + +[2021-09-08 Wed 15:40] (=peg= does use lexical binding now, and it's in ELPA and getting a bit of attention, so no need to drop it, I think.) + +** DONE [#A] [[https://github.com/alphapapa/org-ql/issues/248][Links are broken in dynamic blocks when the task has the cookies {/} or {%} · Issue #248 · alphapapa/org-ql · GitHub]] :bug: +:PROPERTIES: +:milestone: 0.7 +:END: +:LOGBOOK: +CLOCK: [2022-03-01 Tue 02:26]--[2022-03-01 Tue 02:47] => 0:21 +:END: + +** DONE [#A] Reverse sorting +:PROPERTIES: +:milestone: 0.6 +:END: + ++ [[https://github.com/alphapapa/org-ql/issues/143][Reverse sort order? · Issue #143 · alphapapa/org-ql · GitHub]] + +*** DONE Update docs + +Need to mention incompatible change (though that isn't the best way to describe it) in the changelog. + +*** DONE [[https://github.com/alphapapa/org-ql/issues/143#issuecomment-863638651][Get feedback]] + +** DONE [#A] Timestamp predicates using relative dates break caching :bug: +:PROPERTIES: +:milestone: 0.6 +:branch: wip/fix-223 +:issue-link: [[https://github.com/alphapapa/org-ql/issues/223][Cache is not invalidated for relative date/time queries · Issue #223 · alphapapa/org-ql · GitHub]] +:END: + +Could fix this in a 0.5.3 release, but I think too much code changes are involved for a bugfix release. + +*** DONE Fix/commit checklists + ++ Closing issue: 223 + +*** DONE Move from-to-on argument processing to query normalizers + +**** DONE Update defpred docs + ++ [X] Mention that normalizers are run until they return the same result; need to guard against infinite loops. + - [X] Update docstring + - [X] Update readme + - [X] defpred tutorial + +**** DONE Remove old ~--from-to-on~ macro completely + +**** DONE closed + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +**** DONE clocked + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +**** DONE ts + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +**** DONE planning + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +**** DONE deadline + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +**** DONE scheduled + ++ [X] Use new ~--normalize-from-to-on~ macro ++ [X] Remove old ~--from-to-on~ from ~--query-predicate~ ++ [X] Add tests + - [X] Query normalization + - [X] Results + +** DONE [#A] [[https://github.com/alphapapa/org-ql/pull/220][~(link :target)~ doesn't work]] :bug: +:PROPERTIES: +:milestone: 0.5.2 +:END: +:LOGBOOK: +CLOCK: [2021-06-16 Wed 17:02]--[2021-06-16 Wed 17:41] => 0:39 +:END: + +** DONE [#A] [[https://github.com/alphapapa/org-ql/pull/220][~(link :regexp-p)~ doesn't work]] :bug: +:PROPERTIES: +:milestone: 0.5.2 +:END: + +** DONE [#A] Fix: Custom sorter breaks cached results :bug: +:PROPERTIES: +:milestone: 0.5.1 +:END: + ++ [[https://github.com/alphapapa/org-ql/issues/186][custom sort functions pollute cache · Issue #186 · alphapapa/org-ql · GitHub]] ++ [[https://github.com/alphapapa/org-ql/pull/187][replace sort with -sort. by natask · Pull Request #187 · alphapapa/org-ql · GitHub]] + +** DONE [#A] Update =dash= dependencies +:PROPERTIES: +:milestone: 0.6 +:END: + ++ [[https://github.com/alphapapa/org-ql/issues/209][Out-of-date dependences (dash-functional) · Issue #209 · alphapapa/org-ql · GitHub]] ++ [[https://github.com/alphapapa/org-ql/pull/197][Bump dash.el to fix helm-org-ql by landakram · Pull Request #197 · alphapapa/org-ql · GitHub]] + +** DONE [#A] Checking links for unsafe parameters +:PROPERTIES: +:ID: ba70e375-eddb-40df-8892-fb418c1f70d1 +:milestone: 0.5 +:END: +:LOGBOOK: +- State "PROJECT" from "UNDERWAY" [2020-11-12 Thu 00:26] +- State "UNDERWAY" from [2020-11-11 Wed 23:09] +:END: + +Theoretically one could put a sexp-based query into a link that would run arbitrary code to do something evil. Like: + +[[org-ql-search:(message "AHA")]] + +That's very unlikely to be abused, but it would be good to protect against it. Two possibilities: + +1. For sexp-based queries in links and dynamic blocks, prompt for confirmation before running. +2. Use a special variable to control whether lambdas and arbitrary sexps are allowed in queries, and disable it for links and dynamic blocks. (That might be difficult to do, since they could be buried in an ~and~ or something. A whitelist approach might be needed.) + +*** DONE [#A] Tag org-super-agenda 1.2 and bump required version in org-ql +:PROPERTIES: +:milestone: 0.5 +:END: + +That /should/ force the version of org-super-agenda with the fix to be installed when org-ql is upgraded. + +[2020-11-22 Sun 17:19] org-super-agenda 1.2 is tagged and released, so now we can depend on it. + +*** DONE Add automated tests +:LOGBOOK: +- State "UNDERWAY" from "TODO" [2020-11-12 Thu 00:24] +- State "TODO" from "MAYBE" [2020-11-11 Wed 23:16] +- State "MAYBE" from [2020-11-11 Wed 23:15] +:END: + +Maybe impractical, but maybe we could at least test that potentially unsafe ones signal errors. + +[2020-11-12 Thu 00:24] Works better than I expected. All the tests seem to correctly pass, signaling the correct errors for the correct reasons--except for the tests specific to org-super-agenda. For that, I'm currently waiting for MELPA to build the version of org-super-agenda that has the fix applied, so I can install that into the test sandbox, and then those two tests should pass also. + +*** DONE Enumerate and test parameters and potentially unsafe types +CLOSED: [2020-11-11 Wed 23:26] +:LOGBOOK: +- State "DONE" from "UNDERWAY" [2020-11-11 Wed 23:26] +- State "UNDERWAY" from [2020-11-11 Wed 23:15] +:END: + +#+CAPTION: Template for making testable links +#+BEGIN_SRC org +[[org-ql-search:todo:?]] +#+END_SRC + +#+CAPTION: Expression to insert encoded values into template (after the =?=) +#+BEGIN_SRC elisp + (insert (url-hexify-string (concat "buffers-files=" (prin1-to-string '((lambda () (message "AHA"))))))) +#+END_SRC + ++ [X] Buffers-Files: Expanded by =org-ql-view--expand-buffers-files=: + - [X] Quoted lambda: (safe) [[org-ql-search:todo:?buffers-files%3D%28lambda%20nil%20%28message%20%22AHA%22%29%29]] + - [X] Unquoted lambda: (safe) [[org-ql-search:todo:?buffers-files%3D%28lambda%20nil%20%28message%20%22AHA%22%29%29]] + - [X] Quoted lambda in list (safe): [[org-ql-search:todo:?buffers-files%3D%28%28quote%20%28lambda%20nil%20%28message%20%22AHA%22%29%29%29%29]] + - [X] Unquoted lambda in list: (safe) [[org-ql-search:todo:?buffers-files%3D%28%28lambda%20nil%20%28message%20%22AHA%22%29%29%29]] ++ [X] Groups + - [X] Quoted lambda (safe): [[org-ql-search:todo:?super-groups%3D%28lambda%20nil%20%28message%20%22AHA%22%29%29]] + - [X] Unquoted lambda (safe): [[org-ql-search:todo:?super-groups%3D%28lambda%20nil%20%28message%20%22AHA%22%29%29]] + - [X] Quoted expression (safe): [[org-ql-search:todo:?super-groups%3D%28message%20%22AHA%22%29]] + - [X] Unquoted expression (safe): [[org-ql-search:todo:?super-groups%3D%22AHA%22]] + - [X] ~:pred~ selector (UNSAFE, but caught with new org-super-agenda variable): [[org-ql-search:todo:?super-groups%3D%28%28%3Apred%20%28lambda%20%28_%29%20%28message%20%22AHA%22%29%29%29%29]] + - [X] =:auto-map= selector (UNSAFE, but caught with new org-super-agenda variable): [[org-ql-search:todo:?super-groups%3D%28%28%3Aauto-map%20%28lambda%20%28_%29%20%28message%20%22AHA%22%29%29%29%29]] ++ [X] Title + - [X] Quoted lambda (produces the same encoded value as unquoted lambda): (safe) [[org-ql-search:todo:?title%3D%28lambda%20%28_%20_%29%20%28message%20%22AHA%22%29%29]] + - [X] Unquoted lambda: (safe) [[org-ql-search:todo:?title%3D%28lambda%20%28_%20_%29%20%28message%20%22AHA%22%29%29]] + - [X] Expression: (safe) [[org-ql-search:todo:?title%3D%28message%20%22AHA%22%29]] ++ [X] Sort + - [X] Bare, quoted lambda: (maybe unsafe, but caught now): [[org-ql-search:todo:?sort%3D%28lambda%20%28_%20_%29%20%28message%20%22AHA%22%29%29]] + - [X] Bare, unquoted lambda: (UNSAFE, but caught now): [[org-ql-search:todo:?sort%3D%28lambda%20%28_%20_%29%20%28message%20%22AHA%22%29%29]] + - [X] Quoted lambda in list: (maybe unsafe, but caught now): [[org-ql-search:todo:?sort%3D%28%28quote%20%28lambda%20%28_%20_%29%20%28message%20%22AHA%22%29%29%29%29]] + - [X] Unquoted lambda in a list: (UNSAFE, but caught now): [[org-ql-search:todo:?sort=((lambda%20nil%20(message%20"AHA")))]] + +For the query expression: + +1. String queries are parsed by the PEG parsing function (which I will probably rename soon), which should only allow known Org QL predicates, not arbitrary functions. For example: + + #+BEGIN_SRC elisp + (org-ql--plain-query "message:AHA") ;;=> (regexp "message:AHA") + (org-ql--plain-query '(message "AHA")) ;;=> (wrong-type-argument stringp (message "AHA")) + (org-ql--plain-query "(message \"AHA\"") ;;=> (and (regexp "(message") (regexp "AHA")) + #+END_SRC + +2. Sexp queries already prompt for confirmation, unless the user has set =org-ql-view-ask-unsafe-links= to nil. + +[2020-11-11 Wed 23:27] That's all the parameters and all the types that I can think to test. + + +** DONE [#A] Views: Multiple sorters are not preserved :bug: +:PROPERTIES: +:milestone: 0.5 +:url: https://github.com/alphapapa/org-ql/issues/136 +:END: + +** DONE [#A] Make dynamic blocks warn about sexp queries +:PROPERTIES: +:milestone: 0.5 +:END: +:LOGBOOK: +CLOCK: [2020-11-16 Mon 22:56]--[2020-11-17 Tue 00:25] => 1:29 +:END: + +[2020-11-12 Thu 05:22] I guess to be super-extra careful, just in case someone had =org-update-all-dblocks= in the =before-save-hook= or something. + +[2020-11-17 Tue 00:25] It warns and the warning is tested. + +** DONE [#A] Add Emacs 27.1 to =test.yml= +:PROPERTIES: +:milestone: 0.5 +:END: + +[2020-11-16 Mon 05:22] Also releasing =makem.sh= 0.2 with this change. + +** DONE [#A] Fix =org-ql-view--link-open= on Org 9.3+ :compatibility:bug: +:PROPERTIES: +:milestone: 0.5 +:url: https://github.com/alphapapa/org-ql/issues/147 +:END: + +The version of Org in my personal that passes a URL-decoded string (i.e. as if run through =url-unhex-string=) as the argument to =org-ql-view--link-open=. But Org 9.3 in Emacs 27.1 passes a non-URL-decoded string, so =org-ql-view--link-open= needs to pass it through =url-unhex-string= itself. + +But I don't know which version of Org that changed in. I'm comparing the function =org-open-at-point=, but it's a 114-line function, and in neither version does it call =url-unhex-string=, so whatever code decodes the string must be elsewhere. + +I do recall something about links changing in Org 9.3 (or thereabouts), so that was probably part of it. Maybe I can find it in the release notes. I just need to know basically which version it happened in. + +I noticed because the CI tests on GitHub show the link-safety tests failing on the Emacs snapshot version. However, I think they're not currently vulnerable on that Org version, because the link parameters fail to be parsed correctly, so all the arguments to =org-ql-search= should end up being nil. + +[2020-11-14 Sat 20:41] I should probably do something like this in =org-ql-view--link-open=: + +#+BEGIN_SRC elisp + (when (version<= "9.3" (org-version)) + ;; Org 9.3+ makes a backward-incompatible change to link escaping. + ;; I don't think it would be a good idea to try to guess whether + ;; the string received by this function was made with or without + ;; that change, so we'll just test the current version of Org. + ;; Any links created with older Org versions and then opened with + ;; newer ones will have to be recreated. + (setf path (url-unhex-string path))) +#+END_SRC + +But, first, I should write tests for saving and opening links, so it can actually be tested on different versions automatically. + +[2020-11-16 Mon 05:12] Finally, all of the tests pass on my Org version and on 9.3. And I tested for all the combinations of link and bookmark saving/opening I could think of. I hope they work and are safe. + +*** DONE [#A] Write tests for storing/opening links +:PROPERTIES: +:milestone: 0.5 +:END: + +[2020-11-16 Mon 05:11] Took way longer than I expected. I hope it was worth it. + +*** DONE [#A] Check Org release notes for link changes + +[2020-11-13 Fri 22:44] From [[https://www.orgmode.org/Changes_old.html][the changelog]]: + +#+BEGIN_QUOTE +Change bracket link escaping syntax + +Org used to percent-encode sensitive characters in the URI part of the bracket links. + +Now, escaping mechanism uses the usual backslash character, according to the following rules, applied in order: + +- All consecutive \ characters at the end of the link must be escaped; +- Any ] character at the very end of the link must be escaped; +- All consecutive \ characters preceding ][ or ]] patterns must be escaped; +- Any ] character followed by either [ or ] must be escaped; +- Others ] and \ characters need not be escaped. + +When in doubt, use the function org-link-escape in order to turn a link string into its properly escaped form. + +The following function will help switching your links to the new syntax: + +(defun org-update-link-syntax (&optional no-query) + "Update syntax for links in current buffer. +Query before replacing a link, unless optional argument NO-QUERY +is non-nil." + (interactive "P") + (org-with-point-at 1 + (let ((case-fold-search t)) + (while (re-search-forward "\\[\\[[^]]*?%\\(?:2[05]\\|5[BD]\\)" nil t) + (let ((object (save-match-data (org-element-context)))) + (when (and (eq 'link (org-element-type object)) + (= (match-beginning 0) + (org-element-property :begin object))) + (goto-char (org-element-property :end object)) + (let* ((uri-start (+ 2 (match-beginning 0))) + (uri-end (save-excursion + (goto-char uri-start) + (re-search-forward "\\][][]" nil t) + (match-beginning 0))) + (uri (buffer-substring-no-properties uri-start uri-end))) + (when (or no-query + (y-or-n-p + (format "Possibly obsolete URI syntax: %S. Fix? " + uri))) + (setf (buffer-substring uri-start uri-end) + (org-link-escape (org-link-decode uri))))))))))) + +The old org-link-escape and org-link-unescape functions have been renamed into org-link-encode and org-link-decode. +#+END_QUOTE + +This is exactly the kind of breaking, backwards-incompatible change that I've said should mandate a major-version increment. It's not only a change in Org's code, and a change that affects third-party packages, but it's a change in the file format! + +Is it even possible to support both Org 9.3+ and earlier versions at the same time? + +This change doesn't even seem to make sense to me. Percent-encoding solves so many problems in a simple way: pass a string to the encoding function on the way in, and to the decoding function on the way out. Now, instead of a simple, standard way of encoding links, there's a list of Org-specific rules and Org-specific encoding/decoding functions. What is gained this way? + + +** DONE [#A] Fix query-sexp-to-string function's handling of, e.g. =descendants= :bug: +:PROPERTIES: +:milestone: 0.5 +:END: + +[2020-11-14 Sat 20:45] Fixed in =89ff02a1501b53b4e20cdc66a8fcaa37e7d15734=. + +** DONE [#A] Helm command + +In branch =wip/helm-org-ql=. Works really well, should add it and demonstrate it. + +*** DONE Add + +*** DONE Demonstrate + +*** DONE Parsing non-Lisp queries + +[2019-09-12 Thu 12:56] Lisp is so much easier to deal with, but some people don't like parentheses. So I'm trying to add a non-Lisp-style query syntax. It gets complicated. The =peg= library helps, but its documentation is sparse and incomplete. This seems to work fairly well for single-token queries, but I'm not sure if I can or should cram it all into one parser, or use separate ones for certain keywords. + +#+BEGIN_SRC elisp + (-let* ((input "todo:check|someday") + (input "tags:universe+space") + (input "heading:\"spaced phrase\"") + (input "") + (input "heading:\"spaced phrase\"+another") + combinator + (parsed (peg-parse-string ((predicate (substring keyword) ":" (opt args)) + (keyword (or "heading" "tags" "todo" "property")) + (args (+ (and (or quoted-arg unquoted-arg) (opt separator)))) + (quoted-arg "\"" unquoted-arg "\"") + (unquoted-arg (substring (+ (not (or separator "\"")) (any)))) + (separator (or (and "|" (action (setf combinator 'or))) + (and "+" (action (setf combinator 'and))) + (and ":" (action (setf combinator 'arg)))))) + input 'noerror)) + ((predicate . args) (nreverse parsed))) + (when predicate + (list :predicate predicate :args args :combinator combinator))) + ;;=> (:predicate "heading" :args ("spaced phrase" "another" t) :combinator and) +#+END_SRC + +I don't know where the =t= is coming from. + +The next step is to make it work with multi-token queries. It needs to handle all of the tokens in one parser so it can handle quoted phrases (if we split on spaces, it would split quoted phrases). But that makes getting the arguments out of it more difficult. Probably need to do something like this: + +#+BEGIN_SRC elisp + (-let* ((input "todo:check|someday") + (input "tags:universe+space") + (input "heading:\"spaced phrase\"") + (input "") + (input "heading:\"spaced phrase\"+another") + combinator + (parsed (peg-parse-string ((query (+ (or (and predicate `(pred args -- (list :predicate pred :args args))) + (and plain-string `(s -- (list :predicate 'regexp :args s)))) + (opt (syntax-class whitespace)))) + (plain-string (substring (+ (not (syntax-class whitespace)) (any)))) + (predicate (substring keyword) ":" (opt args)) + (keyword (or "heading" "tags" "todo" "property")) + (args (+ (and (or quoted-arg unquoted-arg) (opt separator)))) + (quoted-arg "\"" unquoted-arg "\"") + (unquoted-arg (substring (+ (not (or separator "\"")) (any)))) + (separator (or (and "|" (action (setf combinator 'or))) + (and "+" (action (setf combinator 'and))) + (and ":" (action (setf combinator 'arg)))))) + input 'noerror))) + parsed) +#+END_SRC + +In which lists are pushed onto the stack and returned, rather than strings. But I don't understand yet exactly how to use the =var= forms to consume input from the "value stack"; I need to study the examples more. I'm also not sure if that will even work with a variable number of arguments. + +This seems to work, but we'll have to parse the args again in a separate step: + +#+BEGIN_SRC elisp + (-let* ((input "todo:check|someday") + (input "tags:universe+space") + (input "heading:\"spaced phrase\"") + (input "") + (input "heading:\"spaced phrase\"+another") + (input "heading:\"spaced phrase\"+another todo:check") + combinator + (parsed (peg-parse-string ((query (+ (or (and predicate `(pred args -- (list :predicate pred :args args))) + (and plain-string `(s -- (list :predicate 'regexp :args s)))) + (opt (+ (syntax-class whitespace) (any))))) + (plain-string (substring (+ (not (syntax-class whitespace)) (any)))) + (predicate (substring keyword) ":" (opt args)) + (keyword (or "heading" "tags" "todo" "property")) + (args (substring (+ (and (or quoted-arg unquoted-arg) (opt separator))))) + (quoted-arg "\"" (+ (not (or separator "\"")) (any)) "\"") + (unquoted-arg (+ (not (or separator "\"" (syntax-class whitespace))) (any))) + (separator (or (and "|" (action (setf combinator 'or))) + (and "+" (action (setf combinator 'and))) + (and ":" (action (setf combinator 'arg)))))) + input 'noerror))) + parsed) + ;;=> (t (:predicate "todo" :args "check") (:predicate "heading" :args "\"spaced phrase\"+another")) +#+END_SRC + +Well, a bit of fiddling (lots of trial-and-error required) produced this: + +#+BEGIN_SRC elisp + (-let* ((input "todo:check|someday") + (input "tags:universe+space") + (input "heading:\"spaced phrase\"") + (input "") + (input "heading:\"spaced phrase\"+another") + (input "heading:\"spaced phrase\"+another todo:check") + combinator + (parsed (peg-parse-string ((query (+ (or (and predicate `(pred args -- (list :predicate pred :args args))) + (and plain-string `(s -- (list :predicate 'regexp :args s)))) + (opt (+ (syntax-class whitespace) (any))))) + (plain-string (substring (+ (not (syntax-class whitespace)) (any)))) + (predicate (substring keyword) ":" (opt args)) + (keyword (or "heading" "tags" "todo" "property")) + (args (list (+ (and (substring (or quoted-arg unquoted-arg)) (opt separator))))) + (quoted-arg "\"" (+ (not (or separator "\"")) (any)) "\"") + (unquoted-arg (+ (not (or separator "\"" (syntax-class whitespace))) (any))) + (separator (or (and "|" (action (setf combinator 'or))) + (and "+" (action (setf combinator 'and))) + (and ":" (action (setf combinator 'arg)))))) + input 'noerror))) + parsed) + ;;=> (t (:predicate "todo" :args ("check")) (:predicate "heading" :args ("\"spaced phrase\"" "another"))) +#+END_SRC + +That seems pretty usable! + +** DONE [#B] Add a ~:with-time~ argument to timestamp predicates +:PROPERTIES: +:milestone: 0.6 +:ID: eada7d28-2580-4553-824e-ddab991a30d5 +:END: + ++ In branch =wip/with-time=. + +Which would obviate the need to [[https://gist.github.com/mskorzhinskiy/fac0662a39a7b1bf5b306f175943bc97#file-org-ql-rasmi-el-L59][use the ~org-scheduled-time-hour-regexp~]] to find entries scheduled for a certain time of day. + +Probably should do this after [[id:fc8ccf6e-5311-4121-a0b7-58482dbd2e85][Optimized, date-specific timestamp regexps]]. + +Also relevant to [[https://github.com/alphapapa/org-ql/pull/73][examples: add two new examples by mskorzhinskiy · Pull Request #73 · alphapapa/org-ql · GitHub]]. + +[2021-06-17 Thu 20:01] Implemented for ~scheduled~ in branch =wip/with-time2= (I should check for my own prior work before starting, haha). It's fairly simple, and since I haven't finished the date-specific timestamp regexps branch yet, I might as well do this first, because it's useful. The other branch has some needed work too, so I should fold it into the second branch. + +[2021-06-17 Thu 20:11] Merged second branch into first and deleted second. + +[2021-06-19 Sat 03:30] Everything seems to work. Merged and pushed to master. + +*** DONE Merge to master + ++ [X] Probably squash merge. ++ [X] Push + +*** DONE Update docs + +**** DONE Readme + +***** DONE deadline + +***** DONE ts + +***** DONE scheduled + +***** DONE planning + +***** DONE General date/time predicate info + +**** DONE Docstrings + +***** DONE deadline + +***** DONE ts + +***** DONE scheduled + +***** DONE planning + +*** DONE Ensure changes to these predicates are consistent +:LOGBOOK: +CLOCK: [2021-06-19 Sat 00:12]--[2021-06-19 Sat 00:36] => 0:24 +:END: + +*** DONE ~deadline~ + +**** DONE Tests + +*** DONE ~scheduled~ + ++ [X] Like ~planning~, select the regexp in ~org-ql--query-predicate~. ++ [X] Use new regexps. + +**** DONE Tests + +*** DONE ~planning~ + +**** DONE Tests + +**** DONE regexp defvar + +*** DONE ~ts~ + +[2021-06-18 Fri 00:00] Had to add a bunch of extra regexps, but probably needed to do that anyway. Seems to work... + +**** DONE Tests + +[2021-06-17 Thu 23:59] The tests pass and seem to work correctly. + +** DONE [#B] "Node" caching + +[2019-09-05 Thu 12:30] At each node checked by a predicate, make a struct that stores attributes we can query for, as well as parent node position. This would let us speed up ancestor-based queries, like =(ancestor (todo "WAITING"))=. Ideally it would also serve as the tag hierarchy cache. + +It would probably be an all-encompassing system, because predicates would need to refer to the cached node when available. So maybe the struct should be like =ts-defstruct=, with lazy, caching accessors, which would move some of the predicates' code into the accessors. + +Maybe a good improvement to make later, after the project is more developed. + +[2019-10-07 Mon 13:08] This has basically been implemented in =be2bf6df316b96b3ed56851b8ffe0e227796b621=, but as functions and values rather than with structs. It remains to be seen how this works with =ancestor= queries, but I suspect it will help a lot. + +*** Struct PoC code + +This works okay (except the priority accessor needs to be fixed, because Org priorities are awkward to get). I'm guessing all the extra function calls would make it undesirable in cases of returning many results, but it's a flexible concept that makes sorting easy. + +#+BEGIN_SRC elisp + (ts-defstruct org-ql-node + file position marker + (level + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + (org-outline-level))) + (heading + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + ;; TODO: Org 9.2+ adds 2 more args to `org-get-heading'. + (org-get-heading t t))) + (priority + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + (org-get-priority ))) + (tags + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + (->> (org-ql--tags-at (point)) + -flatten + (delq 'org-ql-nil)))) + (todo + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + (org-get-todo-state))) + (outline-path + nil :accessor-init (org-with-point-at (org-ql-node-marker struct) + (org-split-string (org-format-outline-path (org-get-outline-path) + nil nil "") + "")))) + + (defcustom helm-org-ql-sort + '(org-ql-node-priority org-ql-node-todo) + "FIXME" + ) + + (cl-defun helm-org-ql (buffers-files &optional no-and) + "Display results in BUFFERS-FILES for an `org-ql' query using Helm. + Interactively, search the current buffer. + + NOTE: Atoms in the query are turned into strings where + appropriate, which makes it unnecessary to type quotation marks + around words that are intended to be searched for as indepenent + strings. + + Also, unless NO-AND is non-nil (interactively, with prefix), all + query tokens are wrapped in an implied (and) form. This is + because a query must be a sexp, so when typing multiple clauses, + either (and) or (or) would be required around them, and (and) is + typically more useful, because it narrows down results. + + For example, this raw input: + + Emacs git + + Is transformed into this query: + + (and \"Emacs\" \"git\") + + However, quoted strings remain quoted, so this input: + + \"something else\" (tags \"funny\") + + Is transformed into this query: + + (and \"something else\" (tags \"funny\"))" + (interactive (list (current-buffer) current-prefix-arg)) + (let ((helm-input-idle-delay helm-org-ql-input-idle-delay)) + (helm :sources + (helm-build-sync-source "helm-org-ql-agenda-files" + :candidates (lambda () + (let* ((query (helm-org-ql--input-to-query helm-pattern no-and)) + (window-width (window-width (helm-window)))) + (when query + (let ((results (org-ql-select buffers-files + query + :action '(make-org-ql-node :marker (point-marker))))) + (when helm-org-ql-sort + (dolist (sorter (reverse helm-org-ql-sort)) + (setf results (sort results sorter)))) + (cl-loop for it in-ref results + do (setf it (concat (buffer-name (org-ql-node-file it)) ":" + (or (org-ql-node-todo it) "") + (or (org-ql-node-priority it) "") + (org-ql-node-heading it) "\\" + (org-ql-node-outline-path it)))) + results)))) + :match #'identity + :fuzzy-match nil + :multimatch nil + :volatile t + :action #'helm-org-goto-marker)))) +#+END_SRC + +** DONE [#B] Define predicates with a macro +:PROPERTIES: +:milestone: 0.6 +:END: + +[2020-11-21 Sat 16:40] i.e. the macro defines the predicate, preamble, and normalizer in one form. WIP on the =wip/define-predicate= branch. Seems to be working well so far. + +[2020-11-22 Sun 17:17] Everything is converted, everything works, and all the tests pass. Worked out just as I hoped. Will merge for 0.6. + +*** DONE [#A] Merge new defpred into master + +*** DONE Test defining custom predicates in user config + +*** DONE Show how to define custom predicates + +Tutorial is published. + +*** DONE Run tests + +*** DONE Convert all predicates to new macro + +** DONE [#B] Move this notes file into an orphan =meta/notes= branch + +[2020-11-12 Thu 03:17] Will probably have to merge or delete some WIP branches first, otherwise they'll probably get conflicts. + +[2020-11-22 Sun 19:40] Did this a day or two ago. Didn't rebase all the WIP branches, but they shouldn't be any trouble. + +** DONE [#B] Quickly change sorting/grouping in search views + +With caching, the search doesn't need to be repeated, so resorting/regrouping can be very fast. + +** DONE Byte-compile lambdas +CLOSED: [2018-05-09 Wed 17:30] +:LOGBOOK: +- State "DONE" from [2018-05-09 Wed 17:30] +:END: + +=elfeed-search--update-list= byte-compiles lambdas returned by =elfeed-search-compile-filter=. Maybe I could do something like this too. + +If I can get this working, I should profile it to see what difference it makes. + +*** Profiling + +Going to try byte-compiling the predicate function: + +#+BEGIN_SRC elisp + (elp-profile 10 nil (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (and (or (date :date '= (org-today)) + (date :deadline '<= (+ org-deadline-warning-days (org-today))) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda))))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------------------+--------------+--------------+--------------| +| org-agenda-ng--agenda | 10 | 0.8370581039 | 0.0837058104 | +| org-agenda-finalize-entries | 10 | 0.652886608 | 0.0652886608 | +| org-super-agenda--filter-finalize-entries | 10 | 0.641794501 | 0.0641794501 | +| org-super-agenda--group-items | 10 | 0.636057006 | 0.0636057006 | +| org-super-agenda--group-dispatch | 130 | 0.631911849 | 0.0048608603 | +| org-super-agenda--group-tag | 50 | 0.592883869 | 0.0118576773 | +| list | 2720 | 0.5792795169 | 0.0002129704 | +| mapcar | 331 | 0.2333591920 | 0.0007050126 | +| org-agenda-ng--filter-buffer | 10 | 0.09247626 | 0.009247626 | +| org-agenda-ng--format-element | 150 | 0.0649320479 | 0.0004328803 | +| org-entry-get | 860 | 0.0408285349 | 4.747...e-05 | +| org-agenda-ng--date-p | 910 | 0.0385646249 | 4.237...e-05 | +| org-element-headline-parser | 150 | 0.0374417470 | 0.0002496116 | +| org-is-habit-p | 270 | 0.0290107389 | 0.0001074471 | +| org--property-local-values | 270 | 0.0268615979 | 9.948...e-05 | +| org-get-property-block | 270 | 0.0244613309 | 9.059...e-05 | +| org-get-tags-at | 150 | 0.017875864 | 0.0001191724 | +| org-super-agenda--group-habit | 10 | 0.015910656 | 0.0015910655 | +| mapc | 2540 | 0.0158616290 | 6.244...e-06 | +| org-agenda-ng--add-faces | 150 | 0.0143329670 | 9.555...e-05 | + + +Now the same thing without byte-compiling: + +#+BEGIN_SRC elisp + (elp-profile 10 nil (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (and (or (date :date '= (org-today)) + (date :deadline '<= (+ org-deadline-warning-days (org-today))) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda))))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------------------+--------------+--------------+--------------| +| org-agenda-ng--agenda | 10 | 0.846645537 | 0.0846645537 | +| org-agenda-finalize-entries | 10 | 0.662896805 | 0.0662896805 | +| sort | 40 | 0.591123256 | 0.0147780814 | +| org-entries-lessp | 400 | 0.5901201620 | 0.0014753004 | +| mapcar | 201 | 0.2318270599 | 0.0011533684 | +| org-agenda-ng--filter-buffer | 10 | 0.092519787 | 0.0092519787 | +| org-super-agenda--filter-finalize-entries | 10 | 0.0664278040 | 0.0066427804 | +| org-agenda-ng--format-element | 150 | 0.064658994 | 0.0004310599 | +| org-super-agenda--group-items | 10 | 0.0602504089 | 0.0060250408 | +| org-super-agenda--group-dispatch | 130 | 0.0561904470 | 0.0004322342 | +| org-entry-get | 860 | 0.0437458889 | 5.086...e-05 | +| org-agenda-ng--date-p | 910 | 0.0382623409 | 4.204...e-05 | +| org-element-headline-parser | 150 | 0.0374662920 | 0.0002497752 | +| org-is-habit-p | 270 | 0.0320861079 | 0.0001188374 | +| org--property-local-values | 270 | 0.0298690430 | 0.0001106260 | +| org-get-property-block | 270 | 0.0274716649 | 0.0001017469 | +| org-super-agenda--group-habit | 10 | 0.019117901 | 0.0019117901 | +| org-get-tags-at | 150 | 0.0178958930 | 0.0001193059 | +| mapc | 2470 | 0.0150361130 | 6.087...e-06 | +| org-agenda-ng--add-faces | 150 | 0.0143092169 | 9.539...e-05 | + +Virtually indistinguishable. Going to try moving the =byte-compile= call from the =org-agenda-ng= macro to other places... + +#+BEGIN_SRC elisp + (elp-profile 10 nil (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (and (or (date :date '= (org-today)) + (date :deadline '<= (+ org-deadline-warning-days (org-today))) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda))))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------------------+--------------+--------------+--------------| +| org-agenda-ng--agenda | 10 | 0.8476316779 | 0.0847631678 | +| mapcar | 331 | 0.8159452220 | 0.0024650913 | +| org-agenda-ng--filter-buffer | 10 | 0.674217912 | 0.0674217912 | +| org-element-headline-parser | 150 | 0.6171195889 | 0.0041141305 | +| line-beginning-position | 620 | 0.5802579680 | 0.0009358999 | +| org-agenda-finalize-entries | 10 | 0.082065157 | 0.0082065157 | +| org-super-agenda--filter-finalize-entries | 10 | 0.0708772279 | 0.0070877227 | +| org-super-agenda--group-items | 10 | 0.065523103 | 0.0065523103 | +| org-agenda-ng--format-element | 150 | 0.0652783740 | 0.0004351891 | +| org-super-agenda--group-dispatch | 130 | 0.0614253589 | 0.0004725027 | +| org-entry-get | 860 | 0.0494023029 | 5.744...e-05 | +| org-agenda-ng--date-p | 910 | 0.0388435519 | 4.268...e-05 | +| org-is-habit-p | 270 | 0.0375687549 | 0.0001391435 | +| org--property-local-values | 270 | 0.0353892929 | 0.0001310714 | +| org-get-property-block | 270 | 0.0329700440 | 0.0001221112 | +| org-super-agenda--group-habit | 10 | 0.024468601 | 0.0024468601 | +| re-search-backward | 1500 | 0.0186344089 | 1.242...e-05 | +| org-get-tags-at | 150 | 0.0180038809 | 0.0001200258 | +| mapc | 2540 | 0.0156518099 | 6.162...e-06 | +| org-agenda-ng--add-faces | 150 | 0.0144141080 | 9.609...e-05 | + +Doesn't seem to make any difference. + +** DONE Document/figure out tag inheritance + +I think it should probably be enabled in most cases, to avoid missing results that users would expect to find, but it will reduce performance in some cases, so users should be able to turn it off when they don't need it. + +[2018-06-12 Tue 14:32] The docstring for ~org-map-entries~ says: + +#+BEGIN_QUOTE +If your function needs to retrieve the tags including inherited tags at the *current* entry, you can use the value of the variable ‘org-scanner-tags’ which will be much faster than getting the value with ‘org-get-tags-at’. If your function gets properties with ‘org-entry-properties’ at the *current* entry, bind ‘org-trust-scanner-tags’ to t around the call to ‘org-entry-properties’ to get the same speedup. Note that if your function moves around to retrieve tags and properties at a *different* entry, you cannot use these techniques. +#+END_QUOTE + +[2019-09-26 Thu 21:31] Handled with the tag caching recently implemented. + +** DONE [#B] Dual matching with regexp and predicates +:PROPERTIES: +:ID: 39972bb5-fdd0-4754-93ba-c85796a67ccf +:END: + +/Note: This is underway in the =preamble-re= branch./ + +Searching and matching could be sped up by constructing a regexp that searches directly to the next possible match, and then matching with predicate functions. + +For example, a search like: + +#+BEGIN_SRC elisp + (org-ql (org-agenda-files) + (and (regexp "lisp") + (scheduled < today))) +#+END_SRC + +Only entries that contain the word =lisp= can be matches, and searching each entry for that word is wasteful. Instead, we could search the buffer for the next occurrence of =lisp=, then check the scheduled date for that entry. + +This would require processing the predicate to pull out matchers that can be done as buffer-wide regexps, e.g. =regexp=, =heading-regexp=, =todo=, and possibly =tags=. Org has some regexp-building functions that might make this fairly easy, and then we could probably use ~rx~ to make an optimized version of the regexp. It would also require some refactoring to the searching that would go directly to regexp matches when possible, rather than checking every entry with the predicate. + +[2019-07-16 Tue 11:14] Made new branch =preamble-re-new= based on current =master=. Seems to work well. Here's some code for testing and comparing performance (~bench-multi-lets~ is from [[https://github.com/alphapapa/emacs-package-dev-handbook#bench-multi-lets][here]]). + +[2019-07-16 Tue 11:56] Going to merge to =master= as 0.2, so marking this as done, even though there's a bit more that can be done from here. + +*** Benchmark code + +#+BEGIN_SRC elisp + (cl-defmacro org-ql-preamble-bench (&key query (file "tests/data.org") (times 10)) + `(bench-multi-lets :times ,times :ensure-equal t + :lets (("preamble" ((org-ql-use-preamble t))) + ("no preamble" ((org-ql-use-preamble nil)))) + :forms ((,(prin1-to-string query) (org-ql-select,file + ',query + :action (lambda () (org-get-heading t t))))))) +#+END_SRC + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :query (regexp "Emacs") :times 100) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------------------+--------------------+---------------+----------+------------------| +| preamble: (regexp "Emacs") | 1.22 | 0.141767 | 0 | 0 | +| no preamble: (regexp "Emacs") | slowest | 0.172398 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :file "~/org/inbox.org" :query (regexp "Emacs") :times 5) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------------------+--------------------+---------------+----------+------------------| +| preamble: (regexp "Emacs") | 1.59 | 2.011043 | 0 | 0 | +| no preamble: (regexp "Emacs") | slowest | 3.206370 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :file "~/org/inbox.org" :query (and (regexp "Emacs") (todo)) :times 5) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (and (regexp "Emacs") (todo)) | 1.59 | 2.211503 | 0 | 0 | +| no preamble: (and (regexp "Emacs") (todo)) | slowest | 3.512741 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :file "~/org/inbox.org" :query (and (regexp "Emacs") (todo) (scheduled)) :times 5) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (and (regexp "Emacs") (todo) (scheduled)) | 1.69 | 2.042456 | 0 | 0 | +| no preamble: (and (regexp "Emacs") (todo) (scheduled)) | slowest | 3.453756 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :file "~/org/inbox.org" :query (todo "WAITING") :times 2) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------------------+--------------------+---------------+----------+------------------| +| preamble: (todo "WAITING") | 15.60 | 0.070684 | 0 | 0 | +| no preamble: (todo "WAITING") | slowest | 1.102722 | 0 | 0 | + +Wow, that's a huge improvement! + +** DONE Operate on list of heading positions +CLOSED: [2018-05-10 Thu 15:02] +:LOGBOOK: +- State "DONE" from [2018-05-10 Thu 15:02] +:END: + +[2017-12-31 Sun 17:54] I wonder if, instead of parsing the whole buffer with =org-element-parse-buffer=, we could simply work on a list of heading positions, e.g. a loop would search forward to the next heading position, then call whatever predicates it needed at the heading's position, using =save-excursion= around each function call. The predicates would need to be updated to get their data from the buffer, instead of using =org-element-property=, but that wouldn't be hard. + +[2018-05-10 Thu 15:01] I already changed to using buffer-parsing predicates instead of =org-element-parse-buffer=. + +** DONE Use macros for =date= +CLOSED: [2018-05-10 Thu 14:59] +:LOGBOOK: +- State "DONE" from [2018-05-10 Thu 14:59] +:END: + +If I made the =date= selector a macro, I could avoid the need to quote the comparator. + +Also, maybe instead of having a single =date= selector, I should have =scheduled=, =deadline=, etc. + +** NEXT [#A] Add org-super-agenda's ~:ancestor-with-todo~ selector to group completions + ++ [2024-04-03 Wed 18:12] + +* Checklists + +** Commits +:PROPERTIES: +:ID: b164b539-dfac-4f47-a48f-7e060705468a +:END: + +To complete before and after pushing any commit. + +*** Additions + +When committing an additional feature: + ++ [ ] Make WIP branch ++ [ ] Check repo for closing issues ++ [ ] Check magit-todos list of items branched from master ++ [ ] Lint ++ [ ] Test + - [ ] Locally + - [ ] On different Emacs versions with GitHub CI (very important) ++ [ ] Update docs ++ [ ] Update changelog + - [ ] Mention closing issues (optionally) ++ [ ] Commit + - [ ] Mention closing issues in commit message ++ [ ] Merge to master ++ [ ] Push master ++ [ ] Close related tasks in this file ++ [ ] Delete WIP branch + +*** Fixes + +When committing a fix: + ++ [ ] Check repo for closing issues ++ [ ] Check magit-todos list of items branched from master ++ [ ] Lint ++ [ ] Test + - [ ] Locally + - [ ] On different Emacs versions with GitHub CI (very important) ++ [ ] Update changelog + - [ ] Mention changes + - [ ] Mention closing issues ++ [ ] Commit + - [ ] Mention closing issues in commit message ++ [ ] Push ++ [ ] Close related tasks in this file + +** Release template + ++ [ ] Make WIP branch ++ [ ] =Meta= pre-release commit + - [ ] Update version numbers + + [ ] =org-ql.el= + + [ ] =helm-org-ql.el= + + [ ] =README.org= ++ [ ] Complete [[id:b164b539-dfac-4f47-a48f-7e060705468a][commit checklist]]. ++ [ ] Changelog entry ++ [ ] Merge to stable branch + - [ ] Non-fast-forward merge WIP branch into stable branch + - [ ] Tag and sign merge commit ++ [ ] Push stable branch ++ [ ] Push tags ++ [ ] Merge to master or make stable branch ++ [ ] Push master/stable ++ [ ] Delete WIP branch ++ [ ] Make GitHub release ++ [ ] Announce + - [ ] Reddit + - [ ] org-mode mailing list + +** Archive + +*** DONE Release 0.4.9 + ++ [X] Complete [[id:d8d7b88e-5737-437e-af76-2253f8340de3][commit checklist]] ++ [X] Changelog entry ++ [X] Update version numbers + - [X] =org-ql.el= + - [X] =helm-org-ql.el= (N/A) + - [X] =README.org= ++ [X] Tag and sign ++ [X] Push ++ [X] Merge to master + +*** DONE Release 0.5 + ++ [X] Complete [[id:d8d7b88e-5737-437e-af76-2253f8340de3][commit checklist]] ++ [ ] Changelog entry ++ [X] Update version numbers + - [X] =org-ql.el= + - [X] =helm-org-ql.el= (N/A yet) + - [X] =README.org= ++ [X] Tag and sign ++ [X] Push ++ [X] Merge to master or make stable branch ++ [X] Push master/stable + +*** DONE Release 0.5.1 + ++ [X] Complete [[id:d8d7b88e-5737-437e-af76-2253f8340de3][commit checklist]] ++ [X] Changelog entry ++ [X] Update version numbers + - [X] =org-ql.el= + - [X] =helm-org-ql.el= + - [X] =README.org= ++ [X] Tag and sign ++ [X] Push ++ [X] Merge to master or make stable branch ++ [X] Push master/stable + +*** DONE Release 0.5.2 +:PROPERTIES: +:ID: 5b9fd52c-dae2-473c-8f00-0fd840327b75 +:END: + ++ [X] Make WIP branch ++ [X] =Meta= pre-release commit + - [X] Update version numbers + + [X] =org-ql.el= + + [X] =helm-org-ql.el= + + [X] =README.org= ++ [X] Complete [[id:d8d7b88e-5737-437e-af76-2253f8340de3][commit checklist]] ++ [X] Changelog entry ++ [X] Merge to stable branch + - [X] Non-fast-forward merge WIP branch into stable branch + - [X] Tag and sign merge commit ++ [X] Push stable branch ++ [X] Merge to master or make stable branch ++ [X] Push master/stable ++ [X] Delete WIP branch + +*** DONE Release 0.6.1 + ++ [X] Make WIP branch ++ [X] =Meta= pre-release commit + - [X] Update version numbers + + [X] =org-ql.el= + + [X] =helm-org-ql.el= + + [X] =README.org= ++ [X] Complete [[id:b164b539-dfac-4f47-a48f-7e060705468a][commit checklist]]. ++ [X] Changelog entry ++ [-] Merge to stable branch + - [ ] Non-fast-forward merge WIP branch into stable branch + - [X] Tag and sign merge commit ++ [X] Push stable branch ++ [X] Push tags ++ [X] Merge to master or make stable branch ++ [X] Push master/stable ++ [X] Delete WIP branch ++ [X] Make GitHub release ++ [ ] Announce + - [ ] Reddit + - [ ] org-mode mailing list + +*** DONE Release: 0.6 +:PROPERTIES: +:ID: db2ed038-b8b4-45c1-8b12-d450a95d69b1 +:END: + ++ [X] Make WIP branch ++ [X] =Meta= pre-release commit + - [X] Update version numbers + + [X] =org-ql.el= + + [X] =helm-org-ql.el= + + [X] =README.org= ++ [X] Complete [[id:d8d7b88e-5737-437e-af76-2253f8340de3][commit checklist]] ++ [X] Changelog entry ++ [ ] Merge to stable branch + - [ ] Non-fast-forward merge WIP branch into stable branch + - [ ] Tag and sign merge commit ++ [X] Push stable branch ++ [X] Push tags ++ [X] Merge to master or make stable branch ++ [X] Push master/stable ++ [X] Delete WIP branch ++ [X] Make GitHub release ++ [X] Announce + - [X] Reddit + - [X] org-mode mailing list + +* Examples / testing +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: +:CONTENTS: +- [[#property-matching][Property matching]] +- [[#regexp-matching][Regexp matching]] +- [[#screenshot-code][Screenshot code]] +- [[#sorting][Sorting]] +:END: + +#+BEGIN_SRC elisp + (org-agenda-ng org-agenda-files + (and (or (date :deadline '<= (org-today)) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda))) + ((group (tags "bills")) + (group (todo "SOMEDAY")))) + + (org-agenda-ng org-agenda-files + (and (or (date :deadline '<= (org-today)) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda)))) + + (org-agenda-ng "~/org/main.org" + (and (or (date :deadline '<= (org-today)) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda)))) + + (org-ql org-agenda-files + (and (todo "SOMEDAY") + (tags "Emacs"))) + (org-ql org-agenda-files + (and (todo "SOMEDAY") + (tags "Emacs") + (priority >= "B"))) + (org-ql "~/org/main.org" + (and (or (tags "Emacs") + (priority >= "B")) + (not (done)))) + (org-ql "~/org/main.org" + (and (or (tags "Emacs") + (priority >= "B")) + (done))) +#+END_SRC + +** Property matching + +#+BEGIN_SRC elisp + (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (property "agenda-group")) + + (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (property "agenda-group" "plans")) +#+END_SRC + +** Regexp matching + +#+BEGIN_SRC elisp + (org-ql "~/src/emacs/org-super-agenda/test/test.org" + (regexp "over")) + + (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + (regexp "over")) +#+END_SRC + +** Screenshot code + +#+BEGIN_SRC elisp + (org-super-agenda--test-with-org-today-date "2017-07-08 00:00" + (org-ql "~/src/emacs/org-super-agenda/test/test.org" + (and (or (date = today) + (deadline <=) + (scheduled <= today)) + (not (done))))) +#+END_SRC + +** Sorting + +#+BEGIN_SRC elisp + (org-ql "~/src/emacs/org-super-agenda/test/test.org" + (regexp "over") + :sort (priority deadline scheduled)) + + (org-ql "~/src/emacs/org-super-agenda/test/test.org" + (regexp "over") + :sort (date)) + + (org-ql "~/src/emacs/org-super-agenda/test/test.org" + (todo) + :sort (todo)) +#+END_SRC + +* In the wild +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: +:CONTENTS: +- [[#alois-janicek][Alois Janicek]] +- [[#andrea-giugliano][Andrea Giugliano]] +- [[#benson-chu][Benson Chu]] +- [[#kevin-brubeck-unhammer][Kevin Brubeck Unhammer]] +- [[#mikhail-skorzhinskiy][Mikhail Skorzhinskiy]] +- [[#trey-peacock][Trey Peacock]] +:END: + +** [[https://github.com/AloisJanicek/.doom.d-2nd][Alois Janicek]] + +*** [[https://github.com/AloisJanicek/.doom.d-2nd/commit/41ed1080f6f90463fc1f1d7e47cef9864756867c][further tweaking org-ql views · AloisJanicek/.doom.d-2nd@41ed108 · GitHub]] + +#+BEGIN_SRC elisp :eval never +;; Hydras +(defhydra gtd-agenda (:color blue + :body-pre + (if (aj/has-heading-p +INBOX) + (org-ql-search `(,+INBOX) "*" + :sort '(date)) + (org-ql-search (org-agenda-files) + '(todo "NEXT") + :sort '(date priority todo) + :groups '((:auto-category t)))) + ) + "agenda" + ("a" (org-ql-agenda (org-agenda-files) + (and (or (ts-active :on today) + (deadline auto) + (scheduled :to today)) + (not (done)))) "agenda") + + ("t" (org-ql-search (org-agenda-files) + '(todo "TODO") + :sort '(date priority todo) + :groups '((:auto-category t))) "todo") + + ("n" (org-ql-search (org-agenda-files) + '(todo "NEXT") + :sort '(date priority todo) + :groups '((:auto-category t))) "next") + + ("h" (org-ql-search (org-agenda-files) + '(todo "HOLD") + :sort '(date priority todo) + :groups '((:auto-category t))) "hold") + + ("s" (org-ql-search (org-agenda-files) + '(tags "someday") + :sort '(date priority todo) + :groups '((:auto-category t))) "someday") + + ("r" (org-ql-search (org-agenda-files) + '(ts :from -7 :to today) + :sort '(date priority todo) + :groups '((:auto-ts t))) "recent") + + ("i" (org-ql-search `(,+INBOX) "*" + :sort '(date)) "inbox") + ) +#+END_SRC + +** [[https://ag91.github.io/][Andrea Giugliano]] + +*** [[https://ag91.github.io/blog/2020/09/27/org-agenda-and-your-future-or-how-to-keep-score-of-your-long-term-goals-with-org-mode/][Org Agenda and Your Future, or how to keep score of your long term goals with Org Mode - Where parallels cross]] + +** [[https://github.com/pestctrl/emacs-config][Benson Chu]] + +*** [[https://github.com/pestctrl/emacs-config/commit/fa3068003373a0c93e23c728b5dbad2d7c11e2e1][Experimental agenda view with org-ql · pestctrl/emacs-config@fa30680 · GitHub]] + +#+BEGIN_SRC elisp :eval never + ("f" "fastdev?" + ((org-ql-block '(tags "refile") + ((org-agenda-overriding-header "Refile tasks"))) + (tags-todo ,(concat dev-tag "/!" (mapconcat #'identity my/active-projects-and-tasks "|")) + ((org-agenda-overriding-header "Stuck Projects") + (org-agenda-skip-function 'my/dev-show-stuck-projects) + (org-tags-match-list-sublevels 'indented) + (org-agenda-sorting-strategy + '((agenda category-keep))))) + (tags-todo ,(concat dev-tag "-short" "/!" (mapconcat #'identity my/active-projects-and-tasks "|")) + ((org-agenda-overriding-header "Active Projects") + (org-agenda-skip-function 'my/dev-show-active-projects) + (org-tags-match-list-sublevels 'indented) + (org-agenda-sorting-strategy + '((agenda category-keep))))) + (org-ql-block '(and (tags "dev") + (todo "WAIT")) + ((org-agenda-overriding-header "Waiting tasks"))) + (org-ql-block '(and (tags "dev") + (todo "NEXT")) + ((org-agenda-overriding-header "Things to do"))) + (agenda "" + ((org-agenda-skip-function 'my/agenda-custom-skip) + (org-agenda-span 'day) + (org-agenda-tag-filter-preset (quote (,dev-tag))) + (org-agenda-skip-deadline-if-done t) + (org-agenda-skip-scheduled-if-done t) + (org-super-agenda-groups '((:name "Overdue" :and (:deadline past :log nil)) + (:name "Upcoming" :deadline future) + (:name "Should do" :and (:scheduled past :log nil)) + (:name "Today" :time-grid t + :and (:not (:and (:not (:scheduled today) + :not (:deadline today))))))))))) +#+END_SRC + +** [[https://github.com/unhammer/org-upcoming-modeline][Kevin Brubeck Unhammer]] + +*** [[https://github.com/unhammer/org-upcoming-modeline][org-upcoming-modeline: put upcoming org event in modeline]] + ++ [[https://www.reddit.com/r/emacs/comments/k1e4eu/show_next_orgappointment_in_modeline/][Show next (org-)appointment in modeline? : r/emacs]] + +** [[https://gist.github.com/mskorzhinskiy/fac0662a39a7b1bf5b306f175943bc97][Mikhail Skorzhinskiy]] + +[2020-12-18 Fri 03:41] Shows a number of complex custom queries and grouping, including his "dashboard" view. Shared at [[https://www.reddit.com/r/emacs/comments/kewl3a/how_are_folks_using_orgagenda_and_orgroam_together/gg8j251/][How are folks using org-agenda and org-roam TOGETHER? : emacs]]. + +#+BEGIN_SRC elisp + (setq + org-ql-views + '(("stuck" lambda nil + (interactive) + (org-ql-search + (org-agenda-files) + '(and (tags "story") + (not (tags "ignore")) + (not (done)) ;; Finished stories should be excluded + (not (descendants (todo "NEXT"))) ;; If there are already + ;; something in progress + ;; it will shown + (and (not (descendants (done))) ;; There are not scheduled not finished items + (not (descendants (scheduled))))) + :narrow nil :super-groups + '((:name "Waiting" :order 8 :todo "WAIT") + (:name "Important" :order 1 :deadline t :priority>= "B") + (:name "Work" :order 2 :tag "work") + (:name "Study" :order 2 :tag "study") + (:name "Stucked" :order 3 :tag "story")) + :title "stuck-projects")) + ("reports" lambda nil + (interactive) + (org-ql-search + (org-agenda-files) + '(and (or (tags-local "weekly") + (tags-local "monthly")) + (not (tags "ignore"))) + :narrow nil :super-groups + '((:name "Weekly reports" :tag "weekly") + (:name "Monthly reports" :tag "monthly")) + :title "Introspection reports")) + ("next" lambda nil + (interactive) + (org-ql-search + (org-agenda-files) + '(and (or (tags-local "refile") + (todo "PROG") + (todo "WAIT") + (todo "NEXT")) + (not (tags "ignore")) + (not (property "linked")) + (not (done))) + :sort '(date) + :narrow nil + :super-groups + `((:name "In progress" :order 1 :todo "PROG") + (:name "Daily" :order 2 :regexp ,org-repeat-re) + (:name "Waiting" :order 3 :todo "WAIT") + (:name "Refile" :order 3 :tag "refile") + (:name "Important" :order 3 :priority>= "B") + (:auto-tags t :order 5)) + :title "Next actions")) + ("calendar" lambda nil + (interactive) + (org-ql-search + (org-agenda-files) + `(and (ts-active) + (regexp ,org-scheduled-time-hour-regexp) + (not (done))) + :sort '(date) + :narrow nil + :super-groups + '((:auto-planning t)) + :title "Calendar")) + ("dashboard" lambda nil + (interactive) + (org-ql-search + (org-agenda-files) + '(and (or (ts-active :to today) + (deadline auto) + (todo "PROG") + (and (tags "journal") + (not (tags "weekly")) + (not (tags "monthly")) + (not (tags "yearly")) + (todo))) + (not (todo "WAIT")) + (not (tags "ignore")) + (not (property "linked")) + (not (done))) + :sort '(date) + :narrow nil + :super-groups + `((:name "In progress" :order 1 + :tag "monthly" :tag "weekly" :todo "PROG") + (:name "Agenda" :order 2 + :deadline t :regexp ,org-scheduled-time-hour-regexp) + (:name "Daily" :order 2 + :and (:todo nil :regexp ,org-repeat-re)) + (:name "Today" :order 3 :tag "journal") + (:name "Important" :order 3 :priority>= "B") + (:auto-tags t :order 5)) + :title "Dashboard")))) +#+END_SRC + +** [[https://github.com/tpeacock19/org-ql-config][Trey Peacock]] + +[2021-07-08 Thu 23:29] + +#+BEGIN_QUOTE +A recent post on reddit asked the question, Why does the recent zettelkasten craze use one file per note rather than one headline per note? Naturally, it brought many differing perspectives and approaches to org-mode usage. I wanted to show my own configuration that largely leverages alphapapa’s wonderful package org-ql. Along with some built-in functionality of org-mode I’m able to search headlines across all of my org files and visit them in indirect buffers automatically. Additionally, you can search backlinks with similar functionality. + +This is not meant to be a standalone package by any means, but simply an example of what can be achieved using org-ql. My personal emacs configuration is named baal, so you can safely ignore or rename any references to it. +#+END_QUOTE + +*** TODO Add to examples in docs + +Should probably ask him first. + +* Profiling +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: + +:CONTENTS: +- [[#2019-08-29-thu-0624--benchmarking-org-ql-compared-to-re-search-forward-for-getting-headings-in-buffer][{2019-08-29 Thu 06:24} Benchmarking org-ql compared to re-search-forward for getting headings in buffer]] +- [[#caching-of-inherited-tags][Caching of inherited tags]] +- [[#intersecting-query-results][Intersecting query results]] +- [[#more-profiling][More profiling]] +- [[#preambles][Preambles]] +- [[#profiling-flet-across-all-agenda-files][Profiling flet across all agenda files]] +- [[#profiling-flet-on-a-single-file][Profiling flet on a single file]] +- [[#profiling-org-trust-scanner-tags][Profiling org-trust-scanner-tags]] +- [[#profiling-position-based][Profiling position-based]] +- [[#profiling-tags-matching][Profiling tags matching]] +- [[#using-org-element-parse-buffer][Using org-element-parse-buffer]] +- [[#withwithout-tsel][with/without ts.el]] +:END: + + +** [2019-08-29 Thu 06:24] Benchmarking org-ql compared to re-search-forward for getting headings in buffer +:PROPERTIES: +:ID: fcc09229-ed24-42eb-a1fd-31d8f7d4c8d5 +:END: + +Minimal difference, and that's a very large file, too. On smaller files it's thousandths of a second. + +#+BEGIN_SRC elisp + (with-current-buffer (find-buffer-visiting "~/org/inbox.org") + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("org-ql" (org-ql-select (current-buffer) + '(level 1) + :action '(progn + (font-lock-ensure (line-beginning-position) (line-end-position)) + (cons (org-get-heading t) (point))))) + ("re-search-forward" (org-with-wide-buffer + (goto-char (point-min)) + (when (org-before-first-heading-p) + (outline-next-heading)) + (cl-loop while (re-search-forward (rx bol "*" (1+ blank)) nil t) + do (font-lock-ensure (line-beginning-position) (line-end-position)) + collect (cons (org-get-heading t) (match-beginning 0)))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------+--------------------+---------------+----------+------------------| +| re-search-forward | 1.17 | 0.520375 | 0 | 0 | +| org-ql | slowest | 0.608281 | 0 | 0 | + +** Caching of inherited tags + +[2019-09-05 Thu 07:59] Implemented a per-buffer tags cache that seems to significantly speed up tags queries that use tag inheritance. It persists as long as the buffer remains unmodified, and it's usable from any code as a single function that automatically uses caching. It also returns inherited tags and local tags separately, which could be useful for having separate selectors, one for inherited tags, one for local tags, and one for both. + +#+BEGIN_SRC elisp + (defvar org-ql-tags-cache (ht) + "Per-buffer tags cache. + Keyed by buffer. Each value is a cons of the buffer's modified + tick, and another hash table keyed on buffer position, whose + values are a list of two lists, inherited tags and local tags, as + strings.") + + (defun org-ql--tags-at (position) + "Return tags for POSITION in current buffer. + Returns cons (INHERITED-TAGS . LOCAL-TAGS)." + ;; I'd like to use `-if-let*', but it doesn't leave non-nil variables + ;; bound in the else clause, so destructured variables that are non-nil, + ;; like found caches, are not available in the else clause. + (if-let* ((buffer-cache (gethash (current-buffer) org-ql-tags-cache)) + (modified-tick (car buffer-cache)) + (tags-cache (cdr buffer-cache)) + (buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (cached-result (gethash position tags-cache))) + ;; Found in cache: return them. + (pcase cached-result + ('org-ql-nil nil) + (_ cached-result)) + ;; Not found in cache: get tags and cache them. + (let* ((local-tags (or (org-get-tags position 'local) + 'org-ql-nil)) + (inherited-tags (or (save-excursion + (when (org-up-heading-safe) + (-let* (((inherited local) (org-ql--tags-at (point))) + (inherited-tags (when (or inherited local) + (cond ((and (listp inherited) + (listp local)) + (append inherited local)) + ((cond ((listp inherited) inherited) + ((listp local) local))))))) + (when inherited-tags + (->> inherited-tags -non-nil -uniq))))) + 'org-ql-nil)) + (all-tags (list inherited-tags local-tags))) + ;; Check caches again, because they may have been set now. + ;; TODO: Is there a clever way we could avoid doing this, or is it inherently necessary? + (setf buffer-cache (gethash (current-buffer) org-ql-tags-cache) + modified-tick (car buffer-cache) + tags-cache (cdr buffer-cache) + buffer-unmodified-p (eq (buffer-modified-tick) modified-tick)) + (cond ((or (not buffer-cache) + (not buffer-unmodified-p)) + ;; Buffer-local tags cache empty or invalid: make new one. + (puthash (current-buffer) + (cons (buffer-modified-tick) + (let ((table (make-hash-table))) + (puthash position all-tags table) + table)) + org-ql-tags-cache) + ;; Return tags, not the cons put on the buffer-cache. + all-tags) + ;; Buffer-local tags cache found, but no result: store this one. + (t (puthash position all-tags tags-cache)))))) + + (org-ql--defpred tags-cached (&rest tags) + "Return non-nil if current heading has one or more of TAGS (a list of strings)." + ;; TODO: Try to use `org-make-tags-matcher' to improve performance. It would be nice to not have + ;; to run `org-get-tags' for every heading, especially with inheritance. + (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)))))))) +#+END_SRC + +Benchmark results: + +#+BEGIN_SRC elisp + (let* ((buffers '("~/org/main.org")) + (tags '("Emacs"))) + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("uncached" (let ((org-ql-cache (ht))) + (org-ql-select buffers + `(tags ,@tags)))) + ("cached" (let ((org-ql-cache (ht)) + (org-ql-tags-cache (ht))) + (org-ql-select buffers + `(tags-cached ,@tags))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| cached | 6.51 | 0.519871 | 0 | 0 | +| uncached | slowest | 3.386679 | 0 | 0 | + +** Intersecting query results + +An idea that /might/ be helpful for performance in /some/ cases, depending on the query, the data, and whether the query has a preamble. But it looks like it would very rarely be helpful. + +#+BEGIN_SRC elisp + (cl-defun org-ql-agenda-intersection (buffers-files queries &key entries sort buffer narrow super-groups) + "Like `org-ql-agenda', but intersects multiple queries." + (declare (indent defun)) + (let* ((org-ql-cache (ht)) + (entries (->> queries + (--map (org-ql-select buffers-files + it + :action 'element-with-markers + :narrow narrow + :sort sort)) + (-reduce #'-intersection)))) + (org-ql-agenda--agenda buffers-files queries + :entries entries :super-groups super-groups))) + + (bench-multi-lexical :times 1 + :forms (("intersection" (let ((org-use-tag-inheritance nil)) + (org-ql-agenda-intersection (org-agenda-files) + '((todo "TODO") + (tags "Emacs")) + :sort '(priority deadline) + :super-groups org-super-agenda-groups))) + ("normal" (let ((org-use-tag-inheritance nil)) + (org-ql-agenda (org-agenda-files) + (and (todo "TODO") + (tags "Emacs")) + :sort (priority deadline) + :super-groups org-super-agenda-groups))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------+--------------------+---------------+----------+------------------| +| normal | 3.70 | 0.233147 | 0 | 0 | +| intersection | slowest | 0.862512 | 0 | 0 | + +*** Alternative approach + +[2019-09-01 Sun 08:17] This is very experimental, but the results are surprising. When the action function returns a fairly simple list, the intersection is very slightly faster. When returning full elements, the intersection is much slower, so that it more than doubles the runtime. I wonder if the element list comparison is short-circuiting, or if it looks at the whole lists, because it seems like it shouldn't take more than 4-5 list elements before it realizes that two lists don't match. + +Anyway, looks like this approach isn't viable, at least not without a much more complicated implementation, which probably wouldn't be worth it. + +#+BEGIN_SRC elisp + (let* ((action-fn (lambda () + (list (current-buffer) + (point) + (substring-no-properties (org-get-heading t t))))) + (files '("~/org/main.org"))) + ;; NOTE: Careful to use the same files and action in each one. I duplicated + ;; the variable in each form to make individual testing easier. + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("normal" (->> (let ((org-ql-cache (ht)) + (action-fn (lambda () + (list (current-buffer) + (point) + (substring-no-properties (org-get-heading t t))))) + (files '("~/org/main.org"))) + (org-ql-select files + '(and (not (done)) + (or (habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + :action action-fn)))) + ("Testing" (let* ((org-ql-cache (ht)) + (files '("~/org/main.org")) + (action-fn (lambda () + (list (current-buffer) + (point) + (substring-no-properties (org-get-heading t t))))) + (and-queries '(not (done))) + (or-queries '((habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + (and-results (org-ql-select files + and-queries + :action action-fn)) + (or-results (cl-loop for query in or-queries + append (org-ql-select files + query + :action action-fn)))) + (seq-intersection and-results + (->> or-results + -uniq))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------+--------------------+---------------+----------+------------------| +| Testing | 1.15 | 0.248376 | 0 | 0 | +| normal | slowest | 0.284897 | 0 | 0 | + +#+BEGIN_SRC elisp + +;; With caching enabled + (let* ((action-fn (lambda () + (list (current-buffer) + (point) + (substring-no-properties (org-get-heading t t))))) + (files '("~/org/main.org"))) + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("normal" (->> (org-ql-select files + '(and (not (done)) + (or (habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + :action action-fn))) + ("Testing" (let* ((files '("~/org/main.org")) + (and-queries '(not (done))) + (or-queries '((habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + (and-results (org-ql-select files + and-queries + :action action-fn)) + (or-results (cl-loop for query in or-queries + append (org-ql-select files + query + :action action-fn)))) + (seq-intersection and-results + (->> or-results + -uniq))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------+--------------------+---------------+----------+------------------| +| normal | 13.72 | 0.002311 | 0 | 0 | +| Testing | slowest | 0.031707 | 0 | 0 | + +Using full views: + +#+BEGIN_SRC elisp + (let* ((action-fn (lambda () + (list (current-buffer) + (point) + (substring-no-properties (org-get-heading t t))))) + (files '("~/org/main.org"))) + (bench-multi-lexical :times 1 + :forms (("normal" (->> (let ((org-ql-cache (ht)) + (files '("~/org/main.org"))) + (org-ql-search files + '(and (not (done)) + (or (habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))))))) + ("Testing" (let* ((org-ql-cache (ht)) + (files '("~/org/main.org")) + (and-queries '(not (done))) + (or-queries '((habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + (and-results (org-ql-select files + and-queries + :action 'element-with-markers)) + (or-results (cl-loop for query in or-queries + append (org-ql-select files + query + :action 'element-with-markers))) + (final-results (seq-intersection and-results + (->> or-results + -uniq)))) + (org-ql-agenda--agenda nil nil + :entries final-results) + + ))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------+--------------------+---------------+----------+------------------| +| normal | 1.74 | 0.534742 | 0 | 0 | +| Testing | slowest | 0.931897 | 0 | 0 | + +Just gathering results, but using elements: + +#+BEGIN_SRC elisp + (let* ((action-fn 'element-with-markers) + (files '("~/org/main.org"))) + ;; NOTE: Careful to use the same files and action in each one. I duplicated + ;; the variable in each form to make individual testing easier. + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("normal" (->> (let ((org-ql-cache (ht))) + (org-ql-select files + '(and (not (done)) + (or (habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + :action action-fn)))) + ("Testing" (let* ((org-ql-cache (ht)) + (and-queries '(not (done))) + (or-queries '((habit) + (deadline auto) + (scheduled :to today) + (ts-active :on today) + (closed :on today))) + (and-results (org-ql-select files + and-queries + :action action-fn)) + (or-results (cl-loop for query in or-queries + append (org-ql-select files + query + :action action-fn)))) + (seq-intersection and-results + (->> or-results + -uniq))))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------+--------------------+---------------+----------+------------------| +| normal | 2.27 | 0.314218 | 0 | 0 | +| Testing | slowest | 0.714587 | 0 | 0 | + + +** More profiling + +[2018-05-10 Thu 15:02] I think these are decent improvements. + +#+BEGIN_SRC elisp + (elp-profile 1 nil (org-agenda-ng "~/org/main.org" + (or (habit) + (and (or (date '= (org-today)) + (deadline '<=) + (scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda))) + (and (todo "DONE" "CANCELLED") + (closed '= (org-today)))))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------+--------------+--------------+--------------| +| mapcar | 164 | 1.5004585290 | 0.0091491373 | +| org-agenda-ng--agenda | 1 | 1.348231247 | 1.348231247 | +| org-agenda-ng--filter-buffer | 1 | 1.1391189879 | 1.1391189879 | +| org-agenda-ng--date-plain-p | 1267 | 0.6198571040 | 0.0004892321 | +| org-entry-get | 3983 | 0.2979337370 | 7.480...e-05 | +| org-is-habit-p | 1365 | 0.2049101109 | 0.0001501172 | +| org--property-local-values | 1365 | 0.1940614150 | 0.0001421695 | +| org-agenda-ng--habit-p | 1272 | 0.1911009179 | 0.0001502365 | +| org-agenda-ng--format-element | 52 | 0.177965411 | 0.0034224117 | +| org-get-property-block | 1365 | 0.1760004519 | 0.0001289380 | +| org-get-tags-at | 52 | 0.1362824969 | 0.0026208172 | +| org-agenda-ng--date-p | 3880 | 0.1351176629 | 3.482...e-05 | +| org-up-heading-safe | 226 | 0.1276747609 | 0.0005649325 | +| re-search-backward | 2028 | 0.1144211070 | 5.642...e-05 | +| org-entry-properties | 2618 | 0.0848660999 | 3.241...e-05 | +| org-agenda-ng--todo-p | 1319 | 0.081952653 | 6.213...e-05 | +| org-get-todo-state | 1319 | 0.0796836810 | 6.041...e-05 | +| re-search-forward | 3754 | 0.0739803739 | 1.970...e-05 | +| org-inlinetask-in-task-p | 1365 | 0.0657829330 | 4.819...e-05 | +| org-agenda-ng--scheduled-p | 1247 | 0.0619497850 | 4.967...e-05 | + +** Preambles + +Not sure if clearing the cache is necessary here, because it seemed to make nearly no difference in the results, but I don't know why. + +#+BEGIN_SRC elisp :results silent + (cl-defmacro org-ql-preamble-bench (&key query (file "tests/data.org") (times 10)) + `(bench-multi-lets :times ,times :ensure-equal t + :lets (("preamble" ((org-ql-use-preamble t) + (org-ql-cache (ht)))) + ("no preamble" ((org-ql-use-preamble nil) + (org-ql-cache (ht))))) + :forms ((,(prin1-to-string query) (org-ql-select ,file + ',query + :action '(org-get-heading t t)))))) +#+END_SRC + +*** =closed= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (closed)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-----------------------+--------------------+---------------+----------+------------------| +| preamble: (closed) | 4.80 | 0.086553 | 0 | 0 | +| no preamble: (closed) | slowest | 0.415165 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (closed <= "2019-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (closed <= "2019-01-01") | 4.21 | 0.105782 | 0 | 0 | +| no preamble: (closed <= "2019-01-01") | slowest | 0.445374 | 0 | 0 | + +*** =deadline= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (deadline)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------------+--------------------+---------------+----------+------------------| +| preamble: (deadline) | 27.63 | 0.014656 | 0 | 0 | +| no preamble: (deadline) | slowest | 0.404952 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (deadline <= "2019-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-----------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (deadline <= "2019-01-01") | 27.91 | 0.014606 | 0 | 0 | +| no preamble: (deadline <= "2019-01-01") | slowest | 0.407682 | 0 | 0 | + +*** =habit= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (habit)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------+--------------------+---------------+----------+------------------| +| preamble: (habit) | 70.09 | 0.016489 | 0 | 0 | +| no preamble: (habit) | slowest | 1.155649 | 0 | 0 | + +*** =level= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (level 1)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|------------------------+--------------------+---------------+----------+------------------| +| preamble: (level 1) | 1.34 | 0.562950 | 0 | 0 | +| no preamble: (level 1) | slowest | 0.754050 | 0 | 0 | + +*** =property= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (property "agenda-group")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (property "agenda-group") | 70.44 | 0.016571 | 0 | 0 | +| no preamble: (property "agenda-group") | slowest | 1.167203 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (property "ID")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|------------------------------+--------------------+---------------+----------+------------------| +| preamble: (property "ID") | 3.51 | 0.369830 | 0 | 0 | +| no preamble: (property "ID") | slowest | 1.299684 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (property "agenda-group" "plans")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|------------------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (property "agenda-group" "plans") | 72.54 | 0.016862 | 0 | 0 | +| no preamble: (property "agenda-group" "plans") | slowest | 1.223197 | 0 | 0 | + +*** =scheduled= + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (scheduled)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------+--------------------+---------------+----------+------------------| +| preamble: (scheduled) | 4.45 | 0.100968 | 0 | 0 | +| no preamble: (scheduled) | slowest | 0.449321 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (scheduled <= "2019-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|------------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (scheduled <= "2019-01-01") | 4.13 | 0.111067 | 0 | 0 | +| no preamble: (scheduled <= "2019-01-01") | slowest | 0.458726 | 0 | 0 | + +*** =tags= + +If tag inheritance is enabled, we have to check tags on every heading. When it's disabled, we can search directly to headings with the given tags. + +#+BEGIN_SRC elisp + (let ((org-use-tag-inheritance t)) + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (tags "Emacs"))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-----------------------------+--------------------+---------------+----------+------------------| +| no preamble: (tags "Emacs") | 1.01 | 1.899647 | 0 | 0 | +| preamble: (tags "Emacs") | slowest | 1.921799 | 0 | 0 | + +#+BEGIN_SRC elisp + (let ((org-use-tag-inheritance nil)) + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (tags "Emacs"))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-----------------------------+--------------------+---------------+----------+------------------| +| preamble: (tags "Emacs") | 2.08 | 0.274555 | 0 | 0 | +| no preamble: (tags "Emacs") | slowest | 0.570116 | 0 | 0 | + +*** ~ts~ + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|-------------------+--------------------+---------------+----------+------------------| +| preamble: (ts) | 1.13 | 0.475646 | 0 | 0 | +| no preamble: (ts) | slowest | 0.535950 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts :from "2019-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts :from "2019-01-01") | 1.11 | 0.537445 | 0 | 0 | +| preamble: (ts :from "2019-01-01") | slowest | 0.594534 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts :from "2017-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts :from "2017-01-01") | 1.13 | 0.526891 | 0 | 0 | +| preamble: (ts :from "2017-01-01") | slowest | 0.594360 | 0 | 0 | + +Not sure why that one is slower with preamble. + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 10 + :query (ts :from "2017-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts :from "2017-01-01") | 1.04 | 0.025688 | 0 | 0 | +| preamble: (ts :from "2017-01-01") | slowest | 0.026642 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts :to "2010-01-01")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts :to "2010-01-01") | 1.10 | 0.538603 | 0 | 0 | +| preamble: (ts :to "2010-01-01") | slowest | 0.593466 | 0 | 0 | + +*** ~ts-active~ + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-a)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------------------+--------------------+---------------+----------+------------------| +| preamble: (ts-a) | 4.77 | 0.071489 | 0 | 0 | +| no preamble: (ts-a) | slowest | 0.340896 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-a :from "2017-07-06")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (ts-a :from "2017-07-06") | 1.78 | 0.188369 | 0 | 0 | +| no preamble: (ts-a :from "2017-07-06") | slowest | 0.335975 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-a :to "2017-07-06")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (ts-a :to "2017-07-06") | 4.64 | 0.075307 | 0 | 0 | +| no preamble: (ts-a :to "2017-07-06") | slowest | 0.349445 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-a :on "2017-07-06")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| preamble: (ts-a :on "2017-07-06") | 4.33 | 0.076075 | 0 | 0 | +| no preamble: (ts-a :on "2017-07-06") | slowest | 0.329106 | 0 | 0 | + +*** ~ts-inactive~ + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-i)) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|---------------------+--------------------+---------------+----------+------------------| +| preamble: (ts-i) | 1.21 | 0.459152 | 0 | 0 | +| no preamble: (ts-i) | slowest | 0.555632 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-i :from "2019-07-06")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts-i :from "2019-07-06") | 1.09 | 0.531976 | 0 | 0 | +| preamble: (ts-i :from "2019-07-06") | slowest | 0.579745 | 0 | 0 | + +#+BEGIN_SRC elisp + (org-ql-preamble-bench :times 1 + :file "~/org/inbox.org" + :query (ts-i :to "2019-07-06")) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|--------------------------------------+--------------------+---------------+----------+------------------| +| no preamble: (ts-i :to "2019-07-06") | 1.34 | 0.553428 | 0 | 0 | +| preamble: (ts-i :to "2019-07-06") | slowest | 0.743881 | 0 | 0 | + +** Profiling flet across all agenda files + +*** With flet + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--agenda + :files org-agenda-files + :pred (lambda () + (and (todo) + (or (date :deadline '<= (org-today)) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda)))))) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 711 25.608392569 0.0360174297 +org-agenda-ng--agenda 5 24.019318793 4.8038637586 +org-agenda-ng--filter-buffer 40 14.160293256 0.3540073313 +org-agenda-ng--date-p 21595 4.2111783960 0.0001950071 +org-agenda-finalize-entries 5 4.0930243110 0.8186048622 +org-super-agenda--filter-finalize-entries 5 3.937522006 0.7875044012 +org-agenda-ng--todo-p 37080 3.5687476730 9.624...e-05 +org-get-todo-state 37080 3.4737076600 9.368...e-05 +outline-next-heading 34625 3.4689080650 0.0001001850 +re-search-forward 42280 3.0743315830 7.271...e-05 +org-agenda-ng--format-element 1180 2.9511605820 0.0025009835 +org-element-headline-parser 1180 2.6757063699 0.0022675477 +org-super-agenda--group-items 5 2.187362092 0.4374724183 +org-super-agenda--group-dispatch 70 2.184685662 0.0312097951 +org-entry-get 22730 2.0711872869 9.112...e-05 +org-entry-properties 21595 1.8958912070 8.779...e-05 +org-super-agenda--group-tag 25 1.8498977799 0.0739959111 +org-element-timestamp-parser 3785 1.8234333229 0.0004817525 +org-parse-time-string 7560 1.7121709579 0.0002264776 +org-element--get-time-properties 1180 1.1814058020 0.0010011913 +#+end_example + +*** Without flet + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--agenda + :files org-agenda-files + :pred (lambda () + (and (org-agenda-ng--todo-p) + (or (org-agenda-ng--date-p :deadline '<= (org-today)) + (org-agenda-ng--date-p :scheduled '<= (org-today))) + (not (apply #'org-agenda-ng--todo-p org-done-keywords-for-agenda)))))) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 711 26.910164986 0.0378483333 +org-agenda-ng--agenda 5 21.012501837 4.2025003674 +org-agenda-ng--filter-buffer 40 13.751964650 0.3437991162 +org-agenda-ng--todo-p 37080 5.8788306440 0.0001585445 +org-agenda-ng--format-element 1180 4.5712275970 0.0038739216 +org-get-todo-state 37080 4.1661659069 0.0001123561 +org-agenda-ng--date-p 21595 4.1442710769 0.0001919088 +org-entry-get 22730 2.8275069239 0.0001243953 +org-entry-properties 21595 2.6558403739 0.0001229840 +outline-next-heading 34625 2.0894695999 6.034...e-05 +org-element-headline-parser 1180 1.9110445780 0.0016195293 +re-search-forward 42280 1.6994989150 4.019...e-05 +org-agenda-ng--add-faces 1180 1.6172592580 0.0013705586 +org-agenda-ng--add-scheduled-face 1180 1.607386145 0.0013621916 +org-get-tags-at 1180 1.1521010509 0.0009763568 +org-back-to-heading 64530 1.1005834200 1.705...e-05 +org-up-heading-safe 2360 1.0182265390 0.0004314519 +outline-back-to-heading 64530 1.0086056729 1.563...e-05 +org-parse-time-string 7560 0.8314918499 0.0001099856 +org-time-string-to-absolute 3780 0.8277485280 0.0002189810 +#+end_example + + +** Profiling flet on a single file + +This shows that the difference between them, if any, is so small as to be irrelevant. The convenience and clarity are a big win. + +*** With flet + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--agenda + :files "~/org/main.org" + :pred (lambda () + (and (todo) + (or (date :deadline '<= (org-today)) + (date :scheduled '<= (org-today))) + (not (apply #'todo org-done-keywords-for-agenda)))))) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 526 3.7898506779 0.0072050393 +org-agenda-ng--agenda 5 2.7695176850 0.5539035370 +org-agenda-ng--filter-buffer 5 1.414347774 0.2828695548 +org-agenda-ng--format-element 265 0.8871611419 0.0033477778 +org-get-tags-at 265 0.7891641319 0.0029779778 +org-up-heading-safe 1150 0.7581951110 0.0006593000 +re-search-backward 3700 0.5948686769 0.0001607753 +org-agenda-ng--todo-p 6690 0.5840980579 8.730...e-05 +org-get-todo-state 6690 0.5666448919 8.470...e-05 +org-agenda-ng--date-p 5940 0.5196037069 8.747...e-05 +org-entry-get 6195 0.4144106150 6.689...e-05 +org-entry-properties 5940 0.3640680380 6.129...e-05 +org-element-headline-parser 265 0.2810144920 0.0010604320 +outline-next-heading 6195 0.2495287770 4.027...e-05 +org-back-to-heading 14565 0.1959557380 1.345...e-05 +re-search-forward 7850 0.1933439489 2.462...e-05 +outline-back-to-heading 14565 0.1753121230 1.203...e-05 +org-outline-level 2300 0.1676228200 7.287...e-05 +org-agenda-finalize-entries 5 0.1607656930 0.0321531386 +org-super-agenda--filter-finalize-entries 5 0.1316961509 0.0263392301 +#+end_example + +*** Without flet + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--agenda + :files "~/org/main.org" + :pred (lambda () + (and (org-agenda-ng--todo-p) + (or (org-agenda-ng--date-p :deadline '<= (org-today)) + (org-agenda-ng--date-p :scheduled '<= (org-today))) + (not (apply #'org-agenda-ng--todo-p org-done-keywords-for-agenda)))))) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 526 3.7766218089 0.0071798893 +org-agenda-ng--agenda 5 2.75718831 0.551437662 +org-agenda-ng--filter-buffer 5 1.402551392 0.2805102784 +org-agenda-ng--format-element 265 0.8864161399 0.0033449665 +org-get-tags-at 265 0.7896260759 0.0029797210 +org-up-heading-safe 1150 0.7589292910 0.0006599385 +re-search-backward 3700 0.5956338739 0.0001609821 +org-agenda-ng--todo-p 6690 0.5781650060 8.642...e-05 +org-get-todo-state 6690 0.5603983020 8.376...e-05 +org-agenda-ng--date-p 5940 0.5209897369 8.770...e-05 +org-entry-get 6195 0.4158440950 6.712...e-05 +org-entry-properties 5940 0.3640524090 6.128...e-05 +org-element-headline-parser 265 0.2810144710 0.0010604319 +outline-next-heading 6195 0.2485497380 4.012...e-05 +org-back-to-heading 14565 0.1957209180 1.343...e-05 +re-search-forward 7850 0.1927130979 2.454...e-05 +outline-back-to-heading 14565 0.1751091780 1.202...e-05 +org-outline-level 2300 0.1680958539 7.308...e-05 +org-agenda-finalize-entries 5 0.1610422239 0.0322084448 +org-super-agenda--filter-finalize-entries 5 0.132423043 0.0264846085 +#+end_example + + +** Profiling =org-trust-scanner-tags= + +[2018-05-10 Thu 12:59] Turned on =org-trust-scanner-tags=, going to try profiling again: + +#+BEGIN_SRC elisp + ;; (elp-profile 1 nil (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + ;; (tags "world"))) + + (elp-profile 10 nil (org-agenda-ng org-agenda-files + (tags "Emacs"))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------------------+--------------+--------------+--------------| +| org-agenda-ng--agenda | 10 | 44.092598282 | 4.4092598282 | +| mapcar | 282 | 40.234516707 | 0.1426755911 | +| org-agenda-ng--filter-buffer | 80 | 26.895492471 | 0.3361936558 | +| org-element-headline-parser | 3980 | 10.387614362 | 0.0026099533 | +| org-agenda-finalize-entries | 10 | 9.194458252 | 0.9194458252 | +| org-agenda-ng--tags-p | 70250 | 8.1897379849 | 0.0001165799 | +| org-agenda-ng--format-element | 3980 | 6.5944325679 | 0.0016568926 | +| outline-next-heading | 70320 | 6.1190180490 | 8.701...e-05 | +| re-search-forward | 97050 | 5.8706467829 | 6.049...e-05 | +| org-get-tags-at | 74230 | 5.4078158059 | 7.285...e-05 | +| org-super-agenda--filter-finalize-entries | 10 | 5.2320123400 | 0.5232012340 | +| org-super-agenda--group-items | 10 | 5.1260959210 | 0.5126095921 | +| org-super-agenda--group-dispatch | 130 | 5.119333624 | 0.0393794894 | +| sort | 20 | 3.8204368569 | 0.1910218428 | +| org-element--parse-objects | 6180 | 3.5386578929 | 0.0005725983 | +| org-is-habit-p | 5970 | 3.2497755920 | 0.0005443510 | +| org-entry-get | 5970 | 3.2347964049 | 0.0005418419 | +| org--property-local-values | 5970 | 3.1796357319 | 0.0005326023 | +| org-get-property-block | 5970 | 3.0767919680 | 0.0005153755 | +| org-entries-lessp | 20020 | 2.6563960079 | 0.0001326871 | + +Now trying again without it: + +#+BEGIN_SRC elisp + ;; (elp-profile 1 nil (org-agenda-ng "~/src/emacs/org-super-agenda/test/test.org" + ;; (tags "world"))) + + (elp-profile 10 nil (org-agenda-ng org-agenda-files + (tags "Emacs"))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-------------------------------------------+--------------+--------------+--------------| +| mapcar | 1791 | 57.096304538 | 0.0318795670 | +| org-agenda-ng--agenda | 10 | 54.232133506 | 5.4232133505 | +| org-agenda-ng--filter-buffer | 80 | 30.065167040 | 0.3758145880 | +| org-get-tags-at | 74230 | 13.840202495 | 0.0001864502 | +| org-agenda-ng--format-element | 3980 | 13.429297797 | 0.0033741954 | +| org-element-headline-parser | 3980 | 12.771776652 | 0.0032089891 | +| org-agenda-finalize-entries | 10 | 9.1439433990 | 0.9143943399 | +| org-agenda-ng--tags-p | 70250 | 9.0249653730 | 0.0001284692 | +| org-super-agenda--filter-finalize-entries | 10 | 7.300515859 | 0.7300515859 | +| outline-next-heading | 70320 | 7.2384435649 | 0.0001029357 | +| org-super-agenda--group-items | 10 | 4.918585855 | 0.4918585855 | +| org-super-agenda--group-dispatch | 130 | 4.9125893509 | 0.0377891488 | +| re-search-forward | 101020 | 4.6294823850 | 4.582...e-05 | +| org-up-heading-safe | 7370 | 4.4629885620 | 0.0006055615 | +| org-is-habit-p | 5960 | 4.2772351910 | 0.0007176569 | +| org-entry-get | 5960 | 4.2595350800 | 0.0007146870 | +| org-super-agenda--group-tag | 50 | 3.8942044929 | 0.0778840898 | +| re-search-backward | 26150 | 3.3660083490 | 0.0001287192 | +| org--property-local-values | 5960 | 3.1793476329 | 0.0005334475 | +| org-get-property-block | 5960 | 3.0662425979 | 0.0005144702 | + +Wow, using =org-trust-scanner-tags= saves a /lot/ of time. + +** Profiling position-based + +*** Macro + +#+BEGIN_SRC elisp + (defmacro elp-profile (times &rest body) + (declare (indent defun)) + `(let ((prefixes '("org-" "string-" "s-" "buffer-" "append" "delq" "map" + "list" "car" "save-" "outline-" "delete-dups" + "sort" "line-" "nth" "concat" "char-to-string" + "rx-" "goto-" "when" "search-" "re-")) + output) + (dolist (prefix prefixes) + (elp-instrument-package prefix)) + (dotimes (x ,times) + ,@body) + (elp-results) + (elp-restore-all) + (point-min) + (forward-line 20) + (delete-region (point) (point-max)) + (setq output (buffer-substring-no-properties (point-min) (point-max))) + (kill-buffer) + (delete-window) + output)) +#+END_SRC + + +*** ng-flet + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--test-agenda-today)) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 121 0.1292609089 0.0010682719 +org-agenda-ng--test-agenda-today 5 0.0860146149 0.017202923 +org-agenda-ng--agenda 5 0.0858901769 0.0171780353 +org-agenda-ng--format-element 75 0.0308815090 0.0004117534 +org-agenda-ng--filter-buffer 5 0.026709027 0.0053418054 +org-agenda-ng--date-p 455 0.0210552310 4.627...e-05 +org-element-headline-parser 75 0.016209908 0.0002161321 +org-get-tags-at 75 0.008953666 0.0001193822 +org-agenda-ng--add-faces 75 0.0072834109 9.711...e-05 +org-element-timestamp-interpreter 150 0.0068781430 4.585...e-05 +org-entry-get 290 0.0060815609 2.097...e-05 +org-up-heading-safe 210 0.005708647 2.718...e-05 +org-agenda-finalize-entries 5 0.005201221 0.0010402442 +org-element-timestamp-parser 150 0.005191617 3.461078e-05 +org-entry-properties 290 0.0048787450 1.682...e-05 +org-element--get-time-properties 75 0.004112675 5.483...e-05 +org-agenda-ng--add-deadline-face 75 0.0039314910 5.241...e-05 +org-back-to-heading 725 0.0037559990 5.180...e-06 +org-agenda-ng--add-scheduled-face 75 0.0031766149 4.235...e-05 +org-parse-time-string 300 0.0031740200 1.058...e-05 +#+end_example + +*** ng-funcall + +#+BEGIN_SRC elisp + (elp-profile 5 (org-agenda-ng--test-agenda-today)) +#+END_SRC + +#+RESULTS: +#+begin_example +mapcar 121 0.1296645480 0.0010716078 +org-agenda-ng--test-agenda-today 5 0.086714029 0.0173428058 +org-agenda-ng--agenda 5 0.086584611 0.0173169222 +org-agenda-ng--format-element 75 0.0307461019 0.0004099480 +org-agenda-ng--filter-buffer 5 0.027136826 0.0054273652 +org-agenda-ng--date-p 455 0.0213037090 4.682...e-05 +org-element-headline-parser 75 0.016251755 0.0002166900 +org-get-tags-at 75 0.008959605 0.0001194614 +org-agenda-ng--add-faces 75 0.0072381410 9.650...e-05 +org-element-timestamp-interpreter 150 0.0069832960 4.655...e-05 +org-entry-get 290 0.0061220340 2.111...e-05 +org-up-heading-safe 210 0.0057036860 2.716...e-05 +org-agenda-finalize-entries 5 0.005372899 0.0010745798 +org-element-timestamp-parser 150 0.0050518689 3.367...e-05 +org-entry-properties 290 0.0049517209 1.707...e-05 +org-agenda-ng--add-deadline-face 75 0.0039273909 5.236...e-05 +org-element--get-time-properties 75 0.0039059429 5.207...e-05 +org-back-to-heading 725 0.0037793259 5.212...e-06 +org-parse-time-string 300 0.0032196259 1.073...e-05 +org-agenda-ng--add-scheduled-face 75 0.0031410580 4.188...e-05 +#+end_example + +*** orig + +Make sure to kill any existing agenda buffers first. + +#+BEGIN_SRC elisp + (elp-profile 1 (org-agenda-list nil nil 'week)) +#+END_SRC + +#+RESULTS: +#+begin_example +org-agenda-list 1 9.693596196 9.693596196 +org-agenda-get-day-entries 56 8.630330659 0.1541130474 +org-agenda-get-scheduled 56 6.6207980570 0.1182285367 +org-is-habit-p 2792 2.2907458449 0.0008204677 +org-entry-get 2798 2.287390186 0.0008175090 +org-agenda--timestamp-to-absolute 7708 2.0970420100 0.0002720604 +org-agenda-get-deadlines 56 1.6941886389 0.0302533685 +org-at-planning-p 4399 1.3993312159 0.0003181021 +org--property-local-values 2793 1.2699226760 0.0004546805 +org-get-property-block 2794 1.2182695930 0.0004360306 +org-time-string-to-absolute 7708 1.1513844880 0.0001493752 +org-inlinetask-in-task-p 6969 1.139932302 0.0001635718 +org-parse-time-string 7880 1.0635759220 0.0001349715 +org-closest-date 3864 1.0383435800 0.0002687224 +re-search-forward 15199 0.9607921779 6.321...e-05 +org-back-to-heading 12667 0.8564486210 6.761...e-05 +outline-back-to-heading 12667 0.8362207570 6.601...e-05 +line-beginning-position 10333 0.7998346869 7.740...e-05 +org-agenda-format-item 279 0.7552402350 0.0027069542 +re-search-backward 16726 0.5694224969 3.404...e-05 +#+end_example + +** Profiling tags matching + +*** ng + +#+BEGIN_SRC elisp + (elp-profile 1 nil + (org-agenda-ng "~/org/main.org" + (tags "computer"))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|--------------------------------+--------------+--------------+--------------| +| mapcar | 4217 | 12.612716455 | 0.0029909216 | +| org-agenda-ng--agenda | 1 | 9.721410651 | 9.721410651 | +| org-get-tags-at | 1845 | 7.4793860389 | 0.0040538677 | +| org-up-heading-safe | 9361 | 6.4622674019 | 0.0006903394 | +| re-search-backward | 25001 | 5.3399866239 | 0.0002135909 | +| org-agenda-ng--filter-buffer | 1 | 4.874598854 | 4.874598854 | +| org-agenda-ng--tags-p | 1238 | 4.8067623430 | 0.0038826836 | +| org-agenda-ng--format-element | 607 | 3.6325626609 | 0.0059844524 | +| org-outline-level | 17484 | 1.0298924459 | 5.890...e-05 | +| org-add-props | 2074 | 0.8305549259 | 0.0004004604 | +| org-element-headline-parser | 607 | 0.2092664829 | 0.0003447553 | +| org-back-to-heading | 11813 | 0.1252112960 | 1.059...e-05 | +| outline-back-to-heading | 11813 | 0.1100693780 | 9.317...e-06 | +| org-end-of-subtree | 607 | 0.0721986340 | 0.0001189433 | +| outline-on-heading-p | 11813 | 0.0675261030 | 5.716...e-06 | +| outline-next-heading | 1239 | 0.0627980999 | 5.068...e-05 | +| re-search-forward | 3273 | 0.0612446620 | 1.871...e-05 | +| org-agenda-finalize-entries | 1 | 0.041846274 | 0.041846274 | +| buffer-substring-no-properties | 6329 | 0.0308716979 | 4.877...e-06 | +| line-end-position | 903 | 0.0280484950 | 3.106...e-05 | + +*** ng without inheritance + +#+BEGIN_SRC elisp + (elp-profile 1 nil + (org-agenda-ng "~/org/main.org" + (tags "computer"))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|--------------------------------+--------------+--------------+--------------| +| mapcar | 4217 | 12.580246839 | 0.0029832219 | +| org-agenda-ng--agenda | 1 | 8.777776059 | 8.777776059 | +| org-get-tags-at | 1845 | 8.2853503299 | 0.0044907047 | +| org-up-heading-safe | 9361 | 7.2710981889 | 0.0007767437 | +| re-search-backward | 25001 | 5.3360082060 | 0.0002134317 | +| org-agenda-ng--filter-buffer | 1 | 4.865602689 | 4.865602689 | +| org-agenda-ng--tags-p | 1238 | 4.7983754310 | 0.0038759090 | +| org-agenda-ng--format-element | 607 | 3.6273825100 | 0.0059759184 | +| org-outline-level | 17484 | 1.0284417919 | 5.882...e-05 | +| org-back-to-heading | 11813 | 0.9390534479 | 7.949...e-05 | +| org-split-string | 4940 | 0.833825087 | 0.0001687905 | +| string-match | 9102 | 0.8231629100 | 9.043...e-05 | +| org-element-headline-parser | 607 | 0.2034305819 | 0.0003351409 | +| outline-back-to-heading | 11813 | 0.1096120189 | 9.278...e-06 | +| org-end-of-subtree | 607 | 0.0710802559 | 0.0001171009 | +| outline-on-heading-p | 11813 | 0.0670029359 | 5.671...e-06 | +| outline-next-heading | 1239 | 0.0622323519 | 5.022...e-05 | +| re-search-forward | 3273 | 0.0603102519 | 1.842...e-05 | +| org-agenda-finalize-entries | 1 | 0.037286496 | 0.037286496 | +| buffer-substring-no-properties | 6329 | 0.0285818689 | 4.516...e-06 | + + +*** original + +#+BEGIN_SRC elisp + (elp-profile 1 nil + (with-current-buffer "main.org" +(org-tags-view nil "computer"))) +#+END_SRC + +#+RESULTS: +| Function | Times called | Total time | Average time | +|-----------------------------+--------------+--------------+--------------| +| org-tags-view | 1 | 2.620578129 | 2.620578129 | +| org-scan-tags | 1 | 1.448883817 | 1.448883817 | +| org-agenda-format-item | 607 | 0.9273893060 | 0.0015278242 | +| org-add-props | 2042 | 0.8877267209 | 0.0004347339 | +| org-agenda-finalize | 1 | 0.144506782 | 0.144506782 | +| re-search-forward | 2154 | 0.1367046650 | 6.346...e-05 | +| string-match | 8742 | 0.1002517259 | 1.146...e-05 | +| org-get-priority | 607 | 0.0961996220 | 0.0001584837 | +| org-agenda-align-tags | 1 | 0.095166495 | 0.095166495 | +| org-agenda-prepare | 1 | 0.081724472 | 0.081724472 | +| org-outline-level | 1246 | 0.0771033170 | 6.188...e-05 | +| org-agenda-finalize-entries | 1 | 0.071707404 | 0.071707404 | +| org-agenda-prepare-buffers | 1 | 0.057903921 | 0.057903921 | +| org-get-heading | 607 | 0.0517784369 | 8.530...e-05 | +| mapcar | 3738 | 0.0418641110 | 1.119...e-05 | +| org-agenda-highlight-todo | 607 | 0.0273123070 | 4.499...e-05 | +| mapconcat | 609 | 0.024743305 | 4.062...e-05 | +| sort | 2 | 0.02117069 | 0.010585345 | +| org-activate-plain-links | 132 | 0.0203558980 | 0.0001542113 | +| org-activate-bracket-links | 78 | 0.0198589680 | 0.0002546021 | + +** Using =org-element-parse-buffer= + +This basically works, as a very basic kind of agenda view, but we can already see that it's much slower (at least, for single-day views) because =org-element-parse-buffer= is slow compared to the agenda code. + +[2018-05-10 Thu 15:03] *Note:* This is the old, much slower code that used =org-element-parse-buffer=. + +*** Macro + +#+BEGIN_SRC elisp + (defmacro elp-profile (times prefixes &rest body) + (declare (indent defun)) + (let ((prefixes (append '(org- string- s- buffer- append delq map + list car save- outline- delete-dups + sort line- nth concat char-to-string + rx- goto- when search- re-) + prefixes))) + `(let (output) + (dolist (prefix ',prefixes) + (elp-instrument-package (symbol-name prefix))) + (dotimes (x ,times) + ,@body) + (elp-results) + (elp-restore-all) + (point-min) + (forward-line 20) + (delete-region (point) (point-max)) + (setq output (buffer-substring-no-properties (point-min) (point-max))) + (kill-buffer) + (delete-window) + (let ((rows (s-lines output))) + (append (list (list "Function" "Times called" "Total time" "Average time") + 'hline) + (cl-loop for row in rows + collect (s-split (rx (1+ space)) row 'omit-nulls))))))) +#+END_SRC + +[2018-05-09 Wed 17:31] *Note*: I seem to have misplaced the =org-agenda-ng--test-agenda-today= function I used in these tests. + +*** ng + +#+BEGIN_SRC elisp + (elp-profile 1 (org-agenda-ng--test-agenda-today)) +#+END_SRC + +#+RESULTS: +#+begin_example +org-element--parse-elements 5832 18.501891926 0.0031724780 +mapcar 98 6.3412930759 0.0647070722 +org-agenda-ng--test-agenda-today 1 6.30112088 6.30112088 +org-agenda-ng--agenda-multi 1 6.301086333 6.301086333 +org-agenda-ng--get-entries 8 6.249823971 0.7812279963 +mapc 2803 5.9078545849 0.0021076898 +org-element-parse-buffer 8 4.796204625 0.5995255781 +org-element--current-element 6557 3.7164850469 0.0005667965 +org-element-headline-parser 6557 3.5548915590 0.0005421521 +org-end-of-subtree 6557 1.3663438270 0.0002083794 +org-agenda-ng--filter-tree 8 1.3661297829 0.1707662228 +org-element-map 8 1.365995685 0.1707494606 +line-end-position 9503 0.4900104040 5.156...e-05 +org-at-heading-p 12389 0.4574876539 3.692...e-05 +re-search-forward 20193 0.4566419319 2.261...e-05 +outline-on-heading-p 19286 0.4559736580 2.364...e-05 +org-outline-level 6775 0.3970647569 5.860...e-05 +org-back-to-heading 6897 0.3689033409 5.348...e-05 +outline-back-to-heading 6897 0.3587580889 5.201...e-05 +line-beginning-position 11702 0.2887473860 2.467...e-05 +#+end_example + +*** orig + +Make sure to kill any existing agenda buffers first. + +#+BEGIN_SRC elisp + (elp-profile 1 (org-agenda-list nil nil 'week)) +#+END_SRC + +#+RESULTS: +#+begin_example +org-agenda-list 1 1.717467214 1.717467214 +org-agenda-get-day-entries 7 1.124906724 0.1607009605 +org-agenda-get-scheduled 7 0.9354116170 0.1336302310 +org-get-tags-at 62 0.5598817790 0.0090303512 +org-up-heading-safe 262 0.5531687240 0.0021113310 +org-back-to-heading 2646 0.5316139889 0.0002009123 +org-agenda-finalize 1 0.485185818 0.485185818 +org-agenda-skip 749 0.4059732339 0.0005420203 +org-at-planning-p 921 0.2137141959 0.0002320458 +org-is-habit-p 574 0.1953095319 0.0003402605 +org-entry-get 579 0.195275877 0.0003372640 +org--property-local-values 574 0.1899641870 0.0003309480 +org-get-property-block 574 0.1789142650 0.0003116973 +re-search-backward 3586 0.1769497870 4.934...e-05 +org-inlinetask-in-task-p 1495 0.1639709820 0.0001096795 +outline-back-to-heading 2646 0.1387423440 5.243...e-05 +re-search-forward 3177 0.1353034909 4.258...e-05 +org-agenda-get-deadlines 7 0.1341101260 0.0191585894 +line-beginning-position 2041 0.1000089939 4.899...e-05 +org-get-todo-state 749 0.070545971 9.418...e-05 +org-agenda-prepare 1 0.052788647 0.052788647 +org-agenda-prepare-buffers 1 0.050402675 0.050402675 +org-agenda-get-timestamps 7 0.0428353419 0.0061193345 +org-agenda--timestamp-to-absolute 1498 0.0414448099 2.766...e-05 +org-time-string-to-absolute 1498 0.0385546620 2.573...e-05 +org-agenda-finalize-entries 7 0.03400573 0.0048579614 +org-super-agenda--finalize-entries 7 0.0339819269 0.0048545609 +org-outline-level 505 0.0335176070 6.637...e-05 +org-super-agenda--group-items 7 0.0268096709 0.0038299529 +org-super-agenda--group-dispatch 84 0.024216379 0.0002882902 +org-parse-time-string 1572 0.0211881319 1.347...e-05 +org-closest-date 749 0.0178231690 2.379...e-05 +org-before-first-heading-p 578 0.0142076310 2.458...e-05 +org-refresh-category-properties 1 0.013815263 0.013815263 +org-in-src-block-p 753 0.0135235599 1.795...e-05 +org-refresh-stats-properties 1 0.012230309 0.012230309 +org-habit-parse-todo 5 0.0121474539 0.0024294907 +org-get-limited-outline-regexp 1502 0.0100960829 6.721...e-06 +mapcar 309 0.009495786 3.073...e-05 +org-super-agenda--group-habit 7 0.009318616 0.0013312308 +string-match 6253 0.0090207890 1.442...e-06 +org-super-agenda--group-dispatch-and 7 0.0067444 0.0009634857 +org-agenda-get-blocks 7 0.0060974240 0.0008710605 +outline-on-heading-p 2655 0.0060741090 2.287...e-06 +org-agenda-get-sexps 7 0.0057924309 0.0008274901 +org-super-agenda--group-regexp 7 0.005592509 0.0007989298 +org-refresh-properties 2 0.005461118 0.002730559 +org-super-agenda--get-item-entry 31 0.004812809 0.0001552519 +org-agenda-align-tags 1 0.004677699 0.004677699 +org-set-regexps-and-options 1 0.004533983 0.004533983 +org--setup-collect-keywords 2 0.004499168 0.002249584 +org-agenda-format-item 31 0.0039898020 0.0001287032 +org-date-to-gregorian 420 0.0039648049 9.440...e-06 +org-agenda-highlight-todo 31 0.0039479179 0.0001273521 +string-to-number 8202 0.0031924329 3.892...e-07 +org-end-of-subtree 12 0.003047285 0.0002539404 +org-super-agenda--group-tag 35 0.002941374 8.403...e-05 +org-inlinetask-outline-regexp 1495 0.002887697 1.931...e-06 +org-get-wdays 749 0.0027946290 3.731...e-06 +org-refresh-effort-properties 1 0.002750279 0.002750279 +org-entry-beginning-position 31 0.002345909 7.567...e-05 +sort 25 0.0021000059 8.400...e-05 +mapc 445 0.0018132390 4.074...e-06 +line-end-position 72 0.00177188 2.460...e-05 +mapconcat 341 0.0016574119 4.860...e-06 +org-entries-lessp 60 0.0016203450 2.700...e-05 +concat 336 0.0016188640 4.818...e-06 +org-habit-insert-consistency-graphs 1 0.001368346 0.001368346 +org-add-props 137 0.001291149 9.424...e-06 +org-element-at-point 1 0.001266403 0.001266403 +org-agenda-new-marker 62 0.001253987 2.022...e-05 +outline-next-heading 42 0.001123596 2.675...e-05 +org-split-string 148 0.0011049120 7.465...e-06 +org-element--parse-to 1 0.001074227 0.001074227 +org-get-scheduled-time 5 0.0010200300 0.000204006 +org-super-agenda--group-todo 21 0.001003225 4.777...e-05 +org-time-string-to-time 69 0.0009947770 1.441...e-05 +org-element--current-element 4 0.0009597410 0.0002399352 +org-entry-properties 5 0.0008807149 0.0001761429 +org-at-date-range-p 172 0.0008412679 4.891...e-06 +org-agenda-mode 1 0.000787997 0.000787997 +org-super-agenda--group-log 14 0.0007874939 5.624...e-05 +buffer-substring 31 0.0007650360 2.467...e-05 +org-heading-components 9 0.00071644 7.960...e-05 +org-entry-end-position 31 0.0007046580 2.273...e-05 +string-prefix-p 789 0.0006732279 8.532...e-07 +org-agenda-skip-eval 1498 0.0006586869 4.397...e-07 +org-get-repeat 5 0.000656577 0.0001313153 +org-activate-bracket-links 3 0.000648713 0.0002162376 +org-agenda-fix-displayed-tags 31 0.000636446 2.053...e-05 +s-join 252 0.0006324150 2.509...e-06 +org-element-keyword-parser 4 0.000530311 0.0001325777 +org-in-commented-heading-p 4 0.00051077 0.0001276925 +buffer-substring-no-properties 308 0.0004763350 1.546...e-06 +org-habit-build-graph 5 0.0004689199 9.3784e-05 +car 1626 0.0004469320 2.748...e-07 +org-activate-plain-links 3 0.000382191 0.000127397 +org-super-agenda--transform-groups 7 0.0003747289 5.353...e-05 +org-get-priority 26 0.0003598600 1.384...e-05 +org-agenda-fontify-priorities 1 0.000357076 0.000357076 +org-super-agenda--group-priority 14 0.00035224 2.516e-05 +org-agenda-prepare-window 1 0.00033704 0.00033704 +org-find-text-property-in-string 316 0.0003191880 1.010...e-06 +org-not-nil 821 0.0003016530 3.674...e-07 +list 940 0.0002995150 3.186...e-07 +org-super-agenda--group-time-grid 7 0.0002954999 4.221...e-05 +org-agenda-format-date-aligned 7 0.0002794120 3.991...e-05 +org-super-agenda--get-tags 103 0.0002654610 2.577...e-06 +delq 513 0.0002634709 5.135...e-07 +org-replace-escapes 5 0.0002382050 4.7641e-05 +car-safe 911 0.0002314199 2.540...e-07 +org-today 48 0.0002232539 4.651...e-06 +org-get-time-of-day 19 0.000219044 1.152...e-05 +org-days-to-iso-week 9 0.0002125040 2.361...e-05 +append 433 0.0002083290 4.811...e-07 +org-get-category 31 0.000198604 6.406...e-06 +string-match-p 31 0.0001787880 5.767...e-06 +org-agenda-today-p 21 0.0001658670 7.898...e-06 +org-super-agenda--get-marker 54 0.0001629399 3.017...e-06 +delete-dups 111 0.0001343219 1.210...e-06 +org-at-heading-p 9 0.000128871 1.4319e-05 +string-equal 361 0.0001168969 3.238...e-07 +org-super-agenda--make-agenda-header 13 0.0001151760 8.859...e-06 +org-add-prop-inherited 142 0.0001142290 8.044...e-07 +org-check-agenda-file 8 0.000110448 1.3806e-05 +org-agenda-get-day-face 7 0.000110437 1.577...e-05 +org-get-agenda-file-buffer 8 0.000107414 1.342675e-05 +org-agenda-files 3 0.000102652 3.421...e-05 +org-downcase-keep-props 90 0.0001008230 1.120...e-06 +org-super-agenda--get-priority-cookie 15 9.460...e-05 6.307e-06 +org-get-todo-face 31 9.423...e-05 3.039...e-06 +org-find-base-buffer-visiting 8 8.7492e-05 1.09365e-05 +org-habit-get-faces 140 8.080...e-05 5.771...e-07 +org-remove-uninherited-tags 176 7.580...e-05 4.307...e-07 +goto-char 31 7.1014e-05 2.290...e-06 +org-super-agenda--transform-group-order 7 6.5543e-05 9.363...e-06 +org-agenda-add-time-grid-maybe 7 5.703...e-05 8.147...e-06 +org-compile-prefix-format 1 5.2164e-05 5.2164e-05 +org-link-unescape 4 4.4823e-05 1.120575e-05 +listp 133 3.960...e-05 2.977...e-07 +org-habit-duration-to-days 5 3.9446e-05 7.8892e-06 +org-make-options-regexp 1 3.6054e-05 3.6054e-05 +org-agenda-mark-header-line 1 3.3066e-05 3.3066e-05 +org-element--collect-affiliated-keywords 4 3.2661e-05 8.16525e-06 +org-habit-get-priority 5 2.9857e-05 5.9714e-06 +org-agenda-get-category-icon 31 2.673...e-05 8.624...e-07 +org-face-from-face-or-color 61 2.1803e-05 3.574...e-07 +org-reduced-level 40 2.1692e-05 5.423e-07 +org-link-get-parameter 26 2.062...e-05 7.930...e-07 +buffer-name 57 1.9875e-05 3.486...e-07 +org-defkey 5 1.9835e-05 3.966...e-06 +string-to-char 56 1.929...e-05 3.446...e-07 +org-string-nw-p 5 1.7894e-05 3.578...e-06 +s-wrap 7 1.6955e-05 2.422...e-06 +org-link-expand-abbrev 2 1.5491e-05 7.7455e-06 +map-keymap 2 1.4987e-05 7.4935e-06 +search-forward 7 1.403...e-05 2.004...e-06 +string-lessp 41 1.394...e-05 3.400...e-07 +org-agenda-reset-markers 1 1.3927e-05 1.3927e-05 +org-agenda-deadline-face 7 1.2592e-05 1.798...e-06 +search-backward 5 1.1253e-05 2.2506e-06 +org-agenda-span-name 5 9.758e-06 1.9516e-06 +outline-previous-heading 1 8.678e-06 8.678e-06 +buffer-modified-p 10 4.618e-06 4.617...e-07 +buffer-file-name 13 4.608...e-06 3.544...e-07 +buffer-live-p 11 4.473e-06 4.066...e-07 +buffer-local-value 10 4.278e-06 4.278...e-07 +buffer-base-buffer 10 3.858e-06 3.858...e-07 +org-agenda-set-mode-name 1 3.118e-06 3.118e-06 +org-remove-flyspell-overlays-in 4 2.926...e-06 7.315...e-07 +buffer-size 8 2.791...e-06 3.489...e-07 +org-key 5 2.707e-06 5.414e-07 +org-unbracket-string 1 2.692e-06 2.692e-06 +save-place-to-alist 1 2.276e-06 2.276e-06 +org-element--cache-put 4 1.907e-06 4.7675e-07 +org-property-inherit-p 2 1.414e-06 7.07e-07 +org-agenda-ndays-to-span 2 1.293...e-06 6.465...e-07 +maphash 1 1.171e-06 1.171e-06 +org-set-sorting-strategy 1 1.165e-06 1.165e-06 +org-file-menu-entry 1 1.12e-06 1.12e-06 +string-width 2 1.093e-06 5.465e-07 +org-time-stamp-format 1 1.054e-06 1.054e-06 +org-agenda-fit-window-to-buffer 1 9.31e-07 9.31e-07 +org-font-lock-add-tag-faces 1 9.13e-07 9.13e-07 +org-agenda-span-to-ndays 1 8.3e-07 8.3e-07 +org-element-property 2 8.12e-07 4.06e-07 +org-agenda-mark-clocking-task 1 8.03e-07 8.03e-07 +org-tag-alist-to-groups 1 7.55e-07 7.55e-07 +org-element-type 1 6.14e-07 6.14e-07 +org-agenda-use-sticky-p 1 4.73e-07 4.73e-07 +mapatoms 1 0 0.0 +#+end_example + +*** Profile org-element-map + +#+BEGIN_SRC elisp + (elp-profile 1 (with-current-buffer (find-buffer-visiting "~/org/main.org") + (org-element-parse-buffer 'headline))) +#+END_SRC + +#+RESULTS: +#+begin_example +org-element--parse-elements 1002 4.1612395469 0.0041529336 +org-element-parse-buffer 1 0.859981956 0.859981956 +org-element--current-element 1225 0.8236391779 0.0006723585 +org-element-headline-parser 1225 0.7952382879 0.0006491741 +org-end-of-subtree 1225 0.5557043549 0.0004536362 +line-end-position 1995 0.0751104350 3.764...e-05 +re-search-forward 3743 0.0516547359 1.380...e-05 +org-outline-level 1225 0.0477962079 3.901...e-05 +org-back-to-heading 1225 0.0469223529 3.830...e-05 +outline-back-to-heading 1225 0.0450936199 3.681...e-05 +org-element--get-node-properties 1225 0.0434517140 3.547...e-05 +line-beginning-position 2003 0.0334129610 1.668...e-05 +org-element--get-time-properties 1225 0.0306394040 2.501...e-05 +org-get-limited-outline-regexp 2072 0.0140703559 6.790...e-06 +org-element-timestamp-parser 249 0.0124751890 5.010...e-05 +outline-next-heading 847 0.0115396399 1.362...e-05 +org-at-heading-p 2227 0.0109473260 4.915...e-06 +outline-on-heading-p 3452 0.0101107139 2.928...e-06 +string-match 4594 0.0064121759 1.395...e-06 +mapcar 30 0.0041008740 0.0001366958 +#+end_example + +** with/without ts.el + +[2019-08-11 Sun 15:39] These results seem to show a minor performance improvement by using ~ts~, and the code is simpler. + +#+BEGIN_SRC elisp + ;; (require 'ts) + + (org-ql--defpred ts-ts (&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. + "Return non-nil if current entry has a timestamp in given period. + If no arguments are specified, return non-nil if entry has any + timestamp. + + If FROM, return non-nil if entry has a timestamp on or after + FROM. + + If TO, return non-nil if entry has a timestamp on or before TO. + + If ON, return non-nil if entry has a timestamp on date ON. + + FROM, TO, and ON should be strings parseable by + `parse-time-string' but may omit the time value." + ;; TODO: DRY this with the clocked predicate. + ;; NOTE: FROM and TO are actually expected to be Unix timestamps. The docstring is written + ;; for end users, for which the arguments are pre-processed by `org-ql-select'. + ;; FIXME: This assumes every "clocked" entry is a range. Unclosed clock entries are not handled. + (cl-macrolet ((next-timestamp () + `(when (re-search-forward org-element--timestamp-regexp end-pos t) + (ts-parse-org (match-string 0)))) + (test-timestamps (pred-form) + `(cl-loop for next-ts = (next-timestamp) + while next-ts + thereis ,pred-form))) + (save-excursion + (let ((end-pos (org-entry-end-position))) + (cond ((not (or from to)) (re-search-forward org-element--timestamp-regexp end-pos t)) + ((and from to) (test-timestamps (and (ts<= from next-ts) + (ts<= next-ts to)))) + (from (test-timestamps (ts<= from next-ts))) + (to (test-timestamps (ts<= next-ts to)))))))) + +#+END_SRC + +*** :from + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("old ts" (org-ql "~/org/inbox.org" + (ts :from "2017-01-01"))) + ("ts.el ts" (org-ql "~/org/inbox.org" + (ts-ts :from "2017-01-01"))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| ts.el ts | 1.32 | 1.299966 | 0 | 0 | +| old ts | slowest | 1.713027 | 0 | 0 | + +*** :on + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("old ts" (org-ql "~/org/inbox.org" + (ts :on "2019-05-14"))) + ("ts.el ts" (org-ql "~/org/inbox.org" + (ts-ts :on "2019-05-14"))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| ts.el ts | 1.17 | 0.557281 | 0 | 0 | +| old ts | slowest | 0.652149 | 0 | 0 | + +*** :to + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("old ts" (org-ql "~/org/inbox.org" + (ts :to "2019-01-01"))) + ("ts.el ts" (org-ql "~/org/inbox.org" + (ts-ts :to "2019-01-01"))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| ts.el ts | 1.01 | 1.300084 | 0 | 0 | +| old ts | slowest | 1.312208 | 0 | 0 | + +*** Without timestamp argument + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 1 :ensure-equal t + :forms (("old ts" (org-ql "~/org/inbox.org" + (ts))) + ("ts.el ts" (org-ql "~/org/inbox.org" + (ts-ts))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| ts.el ts | 1.14 | 2.251801 | 0 | 0 | +| old ts | slowest | 2.560280 | 0 | 0 | + +#+BEGIN_SRC elisp + (bench-multi-lexical :times 20 :ensure-equal t + :forms (("old ts" (org-ql "~/src/emacs/org-ql/tests/data.org" + (ts))) + ("ts.el ts" (org-ql "~/src/emacs/org-ql/tests/data.org" + (ts-ts))))) +#+END_SRC + +#+RESULTS: +| Form | x faster than next | Total runtime | # of GCs | Total GC runtime | +|----------+--------------------+---------------+----------+------------------| +| ts.el ts | 1.05 | 0.103714 | 0 | 0 | +| old ts | slowest | 0.108663 | 0 | 0 | + +* References +:PROPERTIES: +:TOC: :include descendants :depth 1 +:END: +:CONTENTS: +- [[#github---ndwarshuisorg-sql-sql-backend-for-emacs-org-mode][GitHub - ndwarshuis/org-sql: SQL backend for Emacs Org-Mode]] +- [[#john-kitchin-on-rewriting-the-org-agenda-code][John Kitchin on rewriting the Org agenda code]] +- [[#nicolas-goazious-org-element-cache-implementation][Nicolas Goaziou's org-element cache implementation]] +- [[#uniform-structured-syntax-metaprogramming-and-run-time-compilation][Uniform Structured Syntax, Metaprogramming and Run-time Compilation]] +:END: + +** [[https://github.com/ndwarshuis/org-sql][GitHub - ndwarshuis/org-sql: SQL backend for Emacs Org-Mode]] + +[2020-01-04 Sat 09:03] + +** [[gnus:gmane.emacs.orgmode#CAJ51EToLCm5zDLKu8XeuqEWrLhHZF+OoNkviPSivZbFttzF8=A@mail.gmail.com][John Kitchin on rewriting the Org agenda code]] :Emacs:Org: +:PROPERTIES: +:archive.is: http://archive.is/33R9M +:END: + +[2019-10-28 Mon 08:28] Originally from [[id:90a68535-2403-4ba9-a117-60be1862628f][this entry in my notes]]. + +#+BEGIN_QUOTE +From: John Kitchin +Subject: Re: [Orgmode] Slow speed of week and month views +Newsgroups: gmane.emacs.orgmode +To: Karl Voit +Cc: "emacs-orgmode@gnu.org" +Date: Sat, 5 Aug 2017 18:17:09 -0400 (4 hours, 11 minutes, 38 seconds ago) + +I can think of two possibilities for a future approach (besides a deep dive on profiling the current elisp to improve the speed there). They both involve some substantial coding though, and would probably add dependencies. I am curious what anyone things about these, or if there are other ideas. + +One is to use the new dynamic module capability to write an org parser in C, or a dedicated agenda function, which would presumably be faster than in elisp. This seems hard, and for me would certainly be a multiyear project I am sure! The downside of this is the need to compile the module. I don't know how easy it would be to make this work across platforms with the relatively easy install org-mode currently has. This could have a side benefit though of a c-lib that could be used by others to expand where org-mode is used. + +The other way that might work is to rely more heavily on a cached version of the files, perhaps in a different format than elisp, that is faster to work with. The approach I have explored in this is to index org files into a sqlite database. The idea then would be to generate the agenda from a sql query. I use something like this already to "find stuff in orgmode anywhere". One of the reasons I wrote this is the org-agenda list of files isn't practical for me because my files are so scattered on my file system. I had a need to be able to find TODOs in research projects in a pretty wide range of locations. + +The code I use is at https://github.com/jkitchin/scimax/blob/master/org-db.el, and from one database I can find headlines, contacts, locations, TODO headlines across my file system, all the files that contain a particular link, and my own recent org files. This approach relies on emacsql, and a set of hook functions to update the database whenever a file is changed. It is not robust, e.g. the file could be out of sync with the db if it is modified outside emacs, but this works well enough for me so far. Updated files get reindexed whenever emacs is idle. It was a compromise on walking the file system all the time or daily, or trying to use inotify and you can always run a command to prune/sync all the files any time you want. + +sqlite is ok, but with emacsql you cannot put strings in it directly (at least when I wrote the org-db code), which has limited it for full-text search so far. Also with text, the db got up to about 0.5 GB in size, and started slowing down. So it doesn't have text in it for now. It has all the other limitations of sqlite too, limited support for locking, single process.... + +I am moderately motivated to switch from sqlite to MongoDB, but the support for Mongo in emacs is pretty crummy (I tried writing a few traditional interfaces, but the performance was not that good, and limited since Mongo uses bson, and it is just not the same as json!). Why Mongo? Mostly because the Mongo query language is basically json and easy to generate in Emacs, unlike sql. Also, it is flexible and easy to adapt to new things, e.g. indexing src-blocks or tables or whatever org-element you want. (And I want to use Mongo for something else too ;). Obviously these all add dependencies, and might not be suitable for the core org-mode distribution. But I do think it is important to think about ways to scale org-mode while maintaining compatibility with the core. + +The main point of the database was to get a query language, persistence and good performance. I have also used caches to speed up using bibtex files, and my org-contacts with reasonable performance. These have been all elisp, with no additional dependencies. Maybe one could do something similar to keep an agenda cache that is persistent and updated via hook functions. + +Thoughts? + +John +#+END_QUOTE + ++ [[https://github.com/m00natic/cl-fdbq][GitHub - m00natic/cl-fdbq: SQL-like operations over fixed field DBs]] + +** Nicolas Goaziou's =org-element= cache implementation + +[2020-01-04 Sat 08:31] https://code.orgmode.org/bzg/org-mode/src/master/lisp/org-element.el#L4817 I guess I haven't been keeping up, because I'm not sure when exactly he made or committed this, or what his plans for it are. It's currently disabled by default. + +** [[https://m00natic.github.io/lisp/manual-jit.html][Uniform Structured Syntax, Metaprogramming and Run-time Compilation]] + +** [[https://github.com/commercial-emacs/xlsp/blob/a0c47d48127f3cf53c53749028e22a04a2f61fdc/xlsp-xref.el#L97][dickmao's xlsp completion table function]] + +This looks like a helpful example for providing the shared-common-prefix functionality that programmed completion tables expect. + +* Testing + +[2020-01-08 Wed 07:15] Subtree moved from =tests/data.org=. + +#+BEGIN_SRC elisp + (cl-defun ap/org-tweak-timestamps (&key (offset 0) epoch-ts) + "Advance all timestamps in the current buffer as if the earliest one was on today. + OFFSET changes which timestamp (in chronological order) is set to + today. Or, if EPOCH-TS is non-nil, use it as the new zero-point + for today." + (let* ((tss (->> (org-with-wide-buffer + (goto-char (point-min)) + (cl-loop while (re-search-forward org-ts-regexp-both nil t) + collect (ts-parse-org (match-string 0)))) + (-sort #'ts<))) + (epoch-ts (if epoch-ts + (ts-parse-org epoch-ts) + (nth offset tss))) + (difference-secs (ts-diff (ts-now) epoch-ts)) + (days (floor (/ difference-secs 86400)))) + (org-with-wide-buffer + (goto-char (point-min)) + (while (re-search-forward org-ts-regexp-both nil t) + (let* ((ts (ts-parse-org (match-string 0))) + (timed-p (string-match-p (rx (repeat 2 digit) ":" (repeat 2 digit) (or ">" "]")) (match-string 0))) + (type (cond ((string-prefix-p "<" (match-string 0)) 'active) + ((string-prefix-p "[" (match-string 0)) 'inactive) + (t (error "Unknown ts type")))) + (brackets (cl-ecase type + ('active (cons "<" ">")) + ('inactive (cons "[" "]")))) + (format-string (concat (car brackets) + (if timed-p + "%Y-%m-%d %a %H:%M" + "%Y-%m-%d %a") + (cdr brackets))) + (new-ts (ts-adjust 'day days ts)) + (new-ts-string (ts-format format-string new-ts))) + (replace-match new-ts-string t t nil 0)))) + (message "Tweaked from epoch: %s" (ts-format epoch-ts)))) +#+END_SRC + +#+BEGIN_SRC elisp +(org-time-string-to-absolute (org-entry-get (point) "SCHEDULED")) +#+END_SRC + + #+BEGIN_SRC elisp :results none + ;; Setup code + (require 'org-super-agenda) + (org-super-agenda-mode 1) + (require 'org-habit) + (setq org-todo-keywords + '((sequence "TODO(t!)" "TODAY(a!)" "NEXT(n!)" "STARTED(s!)" "IN-PROGRESS(p!)" "UNDERWAY(u!)" "WAITING(w@)" "SOMEDAY(o!)" "MAYBE(m!)" "|" "DONE(d@)" "CANCELED(c@)") + (sequence "CHECK(k!)" "|" "DONE(d@)") + (sequence "TO-READ(r!)" "READING(R!)" "|" "HAVE-READ(d@)") + (sequence "TO-WATCH(!)" "WATCHING(!)" "SEEN(!)"))) + (with-current-buffer "test.org" (revert-buffer)) + + (defmacro with-org-today-date (date &rest body) + "Run BODY with the `org-today' function set to return simply DATE. + DATE should be a date-time string (both date and time must be included)." + (declare (indent defun)) + `(let ((day (date-to-day ,date)) + (orig (symbol-function 'org-today))) + (unwind-protect + (progn + (fset 'org-today (lambda () day)) + ,@body) + (fset 'org-today orig)))) + #+END_SRC + +#+BEGIN_SRC elisp :results none + (defun diary-sunrise () + (let ((dss (diary-sunrise-sunset))) + (with-temp-buffer + (insert dss) + (goto-char (point-min)) + (search-forward ",") + (buffer-substring (point-min) (match-beginning 0))))) + + (defun diary-sunset () + (let ((dss (diary-sunrise-sunset)) + start end) + (with-temp-buffer + (insert dss) + (goto-char (point-min)) + (search-forward ", ") + (setq start (match-end 0)) + (search-forward " at") + (setq end (match-beginning 0)) + (goto-char start) + (capitalize-word 1) + (buffer-substring start end)))) +#+END_SRC + +*Note:* Removing tests from here as they're added to =test.el=. + + #+BEGIN_SRC elisp + + (org-super-agenda--test-with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/emacs/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:name "Time grid items in all-uppercase with RosyBrown1 foreground" + :time-grid t + :transformer (--> it + (upcase it) + (propertize it 'face '(:foreground "RosyBrown1")))) + (:name "Priority >= C items underlined, on black background" + :face (:background "black" :underline t) + :not (:priority>= "C") + :order 100)))) + (org-agenda nil "a"))) + (org-super-agenda--test-with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/emacs/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:name none + :time-grid t) + (:name "Should be all-uppercase RosyBrown1 on black" + :face (:background "black" :foreground "RosyBrown1") + :transformer #'upcase + :not (:priority>= "C") + :order 100)))) + (org-agenda nil "a"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:name none + :time-grid t) + (:name none + :not (:priority>= "C") + :order 100)))) + (org-agenda nil "a"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:name "Items with child TODOs" + :children todo)))) + (org-agenda nil "a"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:name "Items with child TODOs" + :children "CHECK")))) + (org-agenda nil "a"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-agenda-custom-commands + '(("u" "Super view" + ((agenda "" ((org-super-agenda-groups + '((:name "Today" + :time-grid t + :scheduled today + :deadline today))))) + (todo "" ((org-super-agenda-groups + '((:name "Projects" + :children t) + (:discard (:anything t))))))))))) + (org-agenda nil "u"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-agenda-custom-commands + '(("u" "Super view" + ((agenda "" ((org-super-agenda-groups + '((:name "Today" + :time-grid today))))) + (todo "" ((org-super-agenda-groups + '((:name "Projects" + :children t) + (:discard (:anything t))))))))))) + (org-agenda nil "u"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-agenda-custom-commands + '(("u" "Super view" + ((agenda "" ((org-super-agenda-groups + '((:name "Schedule" + :time-grid t + :date today) + (:name "Due today" + :deadline today) + (:name "Due soon" + :deadline t))))) + (todo "" ((org-agenda-overriding-header "") + (org-super-agenda-groups + '((:name "Projects" + :children t) + (:discard (:anything t))))))))))) + (org-agenda nil "u"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + (org-super-agenda-groups + '((:scheduled (before "2017-07-06"))))) + (org-agenda nil "a"))) +#+END_SRC + +#+BEGIN_SRC elisp + + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:todo "WAITING"))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) + (org-todo-list))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:todo "SOMEDAY"))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) + (org-tags-view nil "Emacs"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:todo "CHECK"))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) +;; org-search-view doesn't seem to set the todo-state property, so the matcher doesn't work + (org-search-view nil "Emacs"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:regexp ("moon" "mars")))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) + (org-search-view nil "space"))) + + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:todo "SOMEDAY"))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) + (org-agenda-list nil nil 'day))) + +#+END_SRC + +** Agenda censoring + +For sharing screenshots of the agenda without revealing private data. + +#+BEGIN_SRC elisp + (defun org-agenda-sharpie () + "Censor the text of items in the agenda." + (interactive) + (let (regexp old-heading new-heading properties) + ;; Save face properties of line in agenda to reapply to changed text + (setq properties (text-properties-at (point))) + + ;; Go to source buffer + (org-with-point-at (org-find-text-property-in-string 'org-marker + (buffer-substring (line-beginning-position) + (line-end-position))) + ;; Save old heading text and ask for new text + (line-beginning-position) + (unless (org-at-heading-p) + ;; Not sure if necessary + (org-back-to-heading)) + (setq old-heading (when (looking-at org-complex-heading-regexp) + (match-string 4)))) + (setq new-heading (read-from-minibuffer "Overwrite visible heading with: ")) + (add-text-properties 0 (length new-heading) properties new-heading) + ;; Back to agenda buffer + (save-excursion + (when (and old-heading new-heading) + ;; Replace agenda text + (let ((inhibit-read-only t)) + (goto-char (line-beginning-position)) + (when (search-forward old-heading (line-end-position)) + (replace-match new-heading 'fixedcase 'literal))))))) +#+END_SRC + +** Agenda examining + +This helps a lot. + +#+BEGIN_SRC elisp + (defun data-debug-show-string-with-properties (s) + (with-current-buffer (get-buffer-create "argh") + (erase-buffer) + (print s (current-buffer)) + + ;; Convert string reader representations to plain lists that can be set + (cl-loop for (match replace) in '(("#(" "'(") + ("#<" "'(") + (">" ")")) + do (progn + (goto-char (point-min)) + (while (search-forward match nil 'noerror) + (replace-match replace 'fixedcase 'literal)))) + + ;; Surround content in a list which `argh' is set to, then eval + ;; the buffer to do it + (goto-char (point-min)) + (insert "(setq argh (list '") + (delete-forward-char 2) + (goto-char (point-max)) + (insert "))") + + ;; Okay, sure, eval'ing the buffer is dangerous and bad and wrong. + ;; But this is the only way I can find to make this work. (Maybe + ;; `text-properties-at' could be used to get actual lists...) + (eval-buffer) + + (data-debug-show-stuff argh "argh") + ;; (switch-to-buffer (current-buffer)) + )) + + (defun data-debug-show-current-line-with-properties () + (interactive) + (data-debug-show-string-with-properties (buffer-substring (line-beginning-position) (line-end-position)))) + + (with-current-buffer "*Org Agenda*" + (data-debug-show-string-with-properties (seq-subseq (split-string (buffer-string) "\n") + 0 5))) +#+END_SRC + +** Auto categories + +#+BEGIN_SRC elisp + (let ((org-super-agenda-groups + '((:auto-category t)))) + (org-agenda-list nil nil 'day)) +#+END_SRC + +** Auto grouping + + +#+BEGIN_SRC elisp + (with-org-today-date "2017-07-05 00:00" + (let ((org-super-agenda-groups + '((:auto-group t))) + (org-agenda-files (list "~/src/org-super-agenda/test/test.org"))) + (org-agenda-list nil nil 'day))) +#+END_SRC + +** Date + +#+BEGIN_SRC elisp + (with-org-today-date "2017-07-05 00:00" + (-let* ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + ((sec minute hour day month year dow dst utcoff) (list 0 0 0 5 7 2017 3 t nil)) + (last-day-of-month + ;; A hack that seems to work fine + (1+ (calendar-last-day-of-month month year))) + (target-date (format "%d-%02d-%02d" year month last-day-of-month)) + (org-super-agenda-groups + `((:deadline (before ,target-date)) + (:discard (:anything t))))) + (org-todo-list))) + + (with-org-today-date "2017-07-05 00:00" + (-let* ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-agenda-span 'day) + ((sec minute hour day month year dow dst utcoff) (list 0 0 0 5 7 2017 3 t nil)) + (last-day-of-month (calendar-last-day-of-month month year)) + (target-date (format "%d-%02d-%02d" year month last-day-of-month)) + (org-super-agenda-groups + `((:deadline (before ,target-date)) + (:discard (:anything t))))) + (org-todo-list))) +#+END_SRC + +** Effort + +#+BEGIN_SRC elisp + (with-org-today-date "2017-07-05 00:00" + (let ((org-agenda-files (list "~/src/org-super-agenda/test/test.org")) + (org-super-agenda-groups + '((:effort< "0:06")))) + (org-agenda-list nil nil 'day))) +#+END_SRC + +** Misc + +*** let-plist + +I don't need this right now, but it might come in handy here or elsewhere. + +#+BEGIN_SRC elisp + (defmacro osa/let-plist (keys plist &rest body) + "`cl-destructuring-bind' without the boilerplate for plists." + ;; See https://emacs.stackexchange.com/q/22542/3871 + + ;; I really don't understand why Emacs doesn't have this already. + ;; So many things come close to it: pcase, pcase-let, map-let, + ;; cl-destructuring-bind, -let...but none of them let you simply + ;; bind all the values of a plist to variables with the same name as + ;; their keys. You always have to type the name of the key twice. + + ;; For example, compare these two forms: + + ;; (-let (((&keys :from from :to to :date date :subject subject) email)) + ;; (list from to date subject)) + + ;; (osa/let-plist (:from :to :date :subject) email + ;; (list from to date subject)) + + ;; Now, sure, sometimes you need to bind values to differently named + ;; variables. But when you don't, I know which one I prefer. + (declare (indent defun)) + (setq keys (cl-loop for key in keys + collect (intern (replace-regexp-in-string (rx bol ":") "" + (symbol-name key))))) + `(cl-destructuring-bind + (&key ,@keys &allow-other-keys) + ,plist + ,@body)) +#+END_SRC + +** Profiling + +#+BEGIN_SRC elisp :results none + (defmacro profile-it (times &rest body) + `(let (output) + (dolist (p '("org-super-agenda-" "map" "org-" "string-" "s-" "buffer-" "append" "delq" "map" "list" "car" "save-" "outline-" "delete-dups" "sort" "line-" "nth" "concat" "char-to-string" "rx-" "goto-" "when" "search-" "re-")) + (elp-instrument-package p)) + (dotimes (x ,times) + ,@body) + (elp-results) + (elp-restore-all) + (point-min) + (forward-line 20) + (delete-region (point) (point-max)) + (setq output (buffer-substring-no-properties (point-min) (point-max))) + (kill-buffer) + (delete-window) + output)) +#+END_SRC + +* Testimonials + +I don't know if I'll ever use these, but maybe they could help some people understand that it's not as hard or intimidating as it may seem; it's intended to be the opposite. + +** [[https://old.reddit.com/r/orgmode/comments/19876xa/stylistic_question_one_large_vs_many_smaller_files/kiku3t5/][u/TiMueller]] + +#+begin_quote +I had ~org-ql~ installed, but I didn't use it, somehow I was afraid it would be difficult or complicated. Now I tried ~org-ql-find~ for a few days and I am enthusiastic. It is so fast, and it seems to "know" the most important findings. I also like the way it shows the contents backwards: first the finding, then the headings going upwards. Will be my main function for searching inside my Org files from now on. Thank you so much! +#+end_quote + +** [[https://matrix.to/#/!rUhEinythPhVTdddsb:matrix.org/$17084833114948KGKOB:matrix.org?via=matrix.org&via=envs.net&via=kde.org][oantolin in #org-mode:matrix.org]] + ++ [2024-02-20 Tue 21:25] + +#+begin_quote markdown +>> oantolin: After using org-ql-find for a while I don't know how I lived without it before. It even replaced a consult command for me and I don't think I've ever before stopped using anything Daniel Mendler wrote! I've settled on always searching the current buffer (if it's an org buffer), all agenda files and all files containing refile targets. So convenient. I also use org-ql-open-link on that same set of the files a lot. + +> alphapapa: Thanks, it's good to hear that someone "gets" org-ql-find. :) May I quote you on that someday? I'm beginning to wonder if a few testimonials might get some people who don't seem to get it to try it. + +oantolin: Sure, you can quote me. Well, maybe not the part about how I don't use consult-outline any more. :) But you can certainly quote me as saying I don't know how I lived without it before. + +I love the rifle predicate, I almost never use any other (maybe I've used the todo and link predicates a couple of times, but that's it). The fact that it searches body text is what got me to switch. Previously my strategy was to search headings with consult-outline and if necessary switch to consult-line to search for lines of text anywhere in the buffer. I had the impression that I could almost always remember a word in the heading but, while I never gathered statistics, I don't actually think that was true. And searching for body text with something like consult-line or isearch, say, is actually not as convenient for me as with org-ql-find because it turns out that I can make sure I have the right match quicker by looking at the headings than at the occurrence of the search terms in the body text. + +One thing I do miss from consult-outline is the preview as I moved the cursor among the candidates, but for org-ql-find I find that embark-dwim is good enough as a preview: I do have to trigger it manually but that's perfectly fine, I don't need them all the time. And it's been great clocking in with org-ql-find and embark-act, without having to actually visit the clocked in heading at all. +#+end_quote + +#+begin_quote +I mean, I think I understood what it did. I just thought I didn't need it, that the advantage was small because I I could usually remember words from the headings. But now I regularly find headings through their body text so either I was wrong about being able to remember heading words or I have adapted to not having to. I guess another thing I didn't realize is that it is often easier for me to be sure I have the right for some body text by looking at the heading (I go, "oh yeah, that one!") than by looking at the surrounding body text. I'm pretty surprised by both of those things. I think not expecting them was keeping me from trying it sooner. +#+end_quote + +** [[https://old.reddit.com/r/emacs/comments/1ccrw5f/orgqlsearch_custom_sort/][u/clausimu9 in r/emacs]] + +#+begin_src markdown + First of all - org-ql is a game changer for me. Absolutely amazing! The combination with super-agenda is perfect. At work I use orgmode and org-roam to manage teams, projects, meetings, and information. + + One thing I can't seem to figure out is how to use custom sorting in org-ql-search. According to the documentation (https://github.com/alphapapa/org-ql) you can use a "a user-defined comparator function that accepts two items as arguments and returns nil or non-nil". + + Assuming meetings are saved using the following structure: + + ``` org + ,** payment updates :meeting: + :PROPERTIES: + :ID: A10CB210-2877-427E-AB8B-AE85E58ECBBB + :DATE: [2024-04-24 Wed] + :ATTENDEES: [[id:6B3BC68C-B75E-4ABE-B4B9-93E8CAF6AD40][Katrin]] [[id:2F6436D1-12C9-45DA-BFA3-FAE70A24597A][Peter]] + :CATEGORY: lead_team + :END: + + ``` + + I can pull all meetings with a specific person using org-ql-search and super-agenda (using a custom predicate I created called "person") with this: + + ``` emacs-lisp + (org-ql-search (current-buffer) + '(and (person "Katrin") + (tags "meeting")) + :title "Meetings with Katrin" + :super-groups '((:auto-property "CATEGORY" + :order 1))) + ``` + + I would like to sort these meetings by the custom property :DATE:. I found this solution here that should provide the sorting: + + ``` emacs-lisp + (defun cmp-date-property (prop) + "Compare two \org-mode' agenda entries, `A' and `B', by some date property.` + If a is before b, return -1. If a is after b, return 1. If they + are equal return nil." + (lexical-let ((prop prop)) + #'(lambda (a b) + (let* ((a-pos (get-text-property 0 'org-marker a)) + (b-pos (get-text-property 0 'org-marker b)) + (a-date (or (org-entry-get a-pos prop) + (format "<%s>" (org-read-date t nil "now")))) + (b-date (or (org-entry-get b-pos prop) + (format "<%s>" (org-read-date t nil "now")))) + (cmp (compare-strings a-date nil nil b-date nil nil)) + ) + (if (eq cmp t) nil (signum cmp)) + )))) + ``` + + This should work for orgmode agendas using the org-agenda-cmp-user-defined setting: + + org-agenda-cmp-user-defined (cmp-date-property ("DATE")) + + But I can't plug this into org-ql-search directly like this: + + ``` emacs-lisp + (org-ql-search (current-buffer) + '(and (person "Emily") + (tags "meeting")) + :title "Meetings with Emily" + :super-groups '((:auto-property "CATEGORY" + :order 1)) + :sort '(cmp-date-property "DATE")) + ``` + + because :sort expects "a comparison function of two arguments". + + Has anyone plugged in a custom sorting function into org-ql-search that depends on a property value? I can't find any examples online that would help me figure out the next steps. + + Any pointers would be greatly appreciated! +#+end_src + ++ He [[https://old.reddit.com/r/emacs/comments/1ccrw5f/orgqlsearch_custom_sort/l1eyzvf/][followed up]]: + + #+begin_src markdown + Ha, "working well" is an understatement! I think about my orgmode data in a completely different way now that I have found your package. Some of my org files have ~ 40k lines, just meetings are ~20k lines. Rookie numbers by some standards, I'm sure. But it is getting to the point where org-ql is changing my daily work. + + While being a solid every-day user of emacs/orgmode for over a decade, I have never really ventured into elisp-land. But I am teased to dive in. Thanks for the pointer, I will definitely look under the hood of your built-in sorting functions. + #+end_src + +* [#C] COMMENT Config :noexport: +:LOGBOOK: +CLOCK: [2021-06-18 Fri 07:38]--[2021-06-18 Fri 21:51] => 14:13 +:END: + +** Org settings + +#+PROPERTY: LOGGING nil +#+TODO: TODO MAYBE NEXT PROJECT UNDERWAY WAITING | DONE(d) CANCELED + +** File-local variables + +# before-save-hook: ((lambda () (when (fboundp 'unpackaged/org-fix-blank-lines) (unpackaged/org-fix-blank-lines t))) (lambda () (when (fboundp 'ap/org-sort-entries-recursive-multi) (let ((narrowed-p (buffer-narrowed-p)) (olp (org-get-outline-path 'with-self)) (relative-pos (- (point) (save-excursion (org-back-to-heading) (point))))) (widen) (goto-char (point-min)) (ap/org-sort-entries-recursive-multi '(?a ?p ?o)) (goto-char (org-find-olp olp 'this-buffer)) (when narrowed-p (org-narrow-to-subtree)) (forward-char relative-pos)))) org-update-all-dblocks org-make-toc) + +# Local Variables: +# before-save-hook: ((lambda () (when (fboundp 'unpackaged/org-fix-blank-lines) (unpackaged/org-fix-blank-lines t))) ap/org-sort-before-save-hook org-update-all-dblocks org-make-toc) +# org-ql-ask-unsafe-queries: nil +# End: + diff --git a/README.org b/README.org deleted file mode 100644 index 457dedd..0000000 --- a/README.org +++ /dev/null @@ -1,1130 +0,0 @@ -#+TITLE: org-ql - -# NOTE: Using =BEGIN_HTML= for this causes TeX/info export to fail, but this HTML block works. -#+HTML: - -# NOTE: To avoid having this in the info manual, we use HTML rather than Org syntax; it still appears with the GitHub renderer. -#+HTML: - -This package provides a query language for Org files. It offers two syntax styles: Lisp-like sexps and search engine-like keywords. - -It includes three libraries: The =org-ql= library is flexible and may be used as a backend for other tools. The libraries =org-ql-search= and =helm-org-ql= (a separate package) provide interactive search commands and saved views. - -* Contents -:PROPERTIES: -:TOC: :include siblings :depth 0 :ignore this :force depth -:END: -:CONTENTS: -- [[#screenshots][Screenshots]] -- [[#installation][Installation]] -- [[#usage][Usage]] -- [[#changelog][Changelog]] -- [[#development][Development]] -:END: - - -* Screenshots - -[[images/org-ql-search.gif]] - -[[images/org-ql-view-dispatch.gif]] - -[[images/helm-org-ql.gif]] - -[[images/org-ql-view-sidebar.gif]] - -* Installation -:PROPERTIES: -:TOC: ignore-children -:END: - -The package =org-ql= may be installed directly from [[https://melpa.org/#/org-ql][MELPA]] or with other tools like [[https://framagit.org/steckerhalter/quelpa][Quelpa]]. - -After installation, you can use the commands without additional configuration. To use the functions and macros in your own Elisp code, use libraries =org-ql= and =org-ql-view=. - -** Quelpa - -Installing with [[https://framagit.org/steckerhalter/quelpa][Quelpa]] is easy: - -1. Install [[https://framagit.org/steckerhalter/quelpa-use-package#installation][quelpa-use-package]] (which can be installed directly from MELPA). -2. Add this form to your init file: - -#+BEGIN_SRC elisp - (use-package org-ql - :quelpa (org-ql :fetcher github :repo "alphapapa/org-ql" - :files (:defaults (:exclude "helm-org-ql.el")))) -#+END_SRC - -** Helm support - -The command =helm-org-ql= is available in the package =helm-org-ql=. It may be installed from MELPA, or with Quelpa, like so: - -#+BEGIN_SRC elisp - (use-package helm-org-ql - :quelpa (helm-org-ql :fetcher github :repo "alphapapa/org-ql" - :files ("helm-org-ql.el"))) -#+END_SRC - -* Usage -:PROPERTIES: -:TOC: :include descendants :depth 1 -:END: -:CONTENTS: -- [[#commands][Commands]] -- [[#queries][Queries]] - - [[#non-sexp-query-syntax][Non-sexp query syntax]] - - [[#general-predicates][General predicates]] - - [[#ancestordescendant-predicates][Ancestor/descendant predicates]] - - [[#datetime-predicates][Date/time predicates]] -- [[#functions--macros][Functions / Macros]] -- [[#dynamic-block][Dynamic block]] -- [[#links][Links]] -- [[#tips][Tips]] -:END: - -# These links work on GitHub's Org renderer but not in Org. - -Feedback on these APIs is welcome. Eventually, after being tested and polished, they will be considered stable. - -# TODO: Find a way to get these examples included in the info manual. -Lisp code examples are in [[examples.org]]. - -** Commands -:PROPERTIES: -:TOC: ignore-children -:END: - -+ *Jumping to an entry:* - - [[#org-ql-find][org-ql-find]] and related commands - - [[#helm-org-ql][helm-org-ql]] -+ *Showing an agenda-like view:* - - [[#org-ql-search][org-ql-search]] - - [[#org-ql-view][org-ql-view]] - - [[#org-ql-view-sidebar][org-ql-view-sidebar]] - - [[#org-ql-view-recent-items][org-ql-view-recent-items]] -+ *Showing a tree in a buffer:* - - [[#org-ql-sparse-tree][org-ql-sparse-tree]] - -*** org-ql-find - -/Note: These commands use [[#non-sexp-query-syntax][non-sexp queries]]./ - -These commands jump to a heading selected using Emacs's built-in completion facilities with an Org QL query: - -- ~org-ql-find~ searches in the current buffer. -- ~org-ql-find-path~ searches outline paths in the current buffer. -- ~org-ql-find-in-agenda~ searches in ~(org-agenda-files)~. -- ~org-ql-find-in-org-directory~ searches in ~org-directory~. - -Note that these commands are compatible with [[https://github.com/oantolin/embark][Embark]]: the ~embark-act~ command can be called on a completion candidate (i.e. a search result) to act on it immediately, without having to visit the entry in its source Org buffer, and ~embark-export~ may be called to show the results in an ~org-ql-view~ buffer. - -[[images/org-ql-find.png]] - -*** org-ql-open-link - -This command finds links in entries matching the input query and offers them for selection; the selected link is then opened with ~org-open-at-point~. - -The input is matched using the default predicate, which means it searches both entry content and outline paths. This is helpful when a collection of links are kept in Org files: rather than having to first visit the entry containing the desired link, then locate it within the entry, and then open it, the user can simply select the link and open it directly. For example, if an entry with the heading =Emacs= contained a link named =mailing list=, one could search for =Emacs list= and open the link to the mailing list directly. - -*** org-ql-refile - -This command refiles the current Org entry to one selected by searching with Org QL completion. It searches files listed in ~org-refile-targets~ as well as the current buffer. - -*** org-ql-search - -/Note: This command supports both sexp queries and [[#non-sexp-query-syntax][non-sexp queries]]./ - -Read ~QUERY~ and search with ~org-ql~. Interactively, prompt for these variables: - -~BUFFERS-FILES~: ~A~ list of buffers and/or files to search. Interactively, may also be: - -+ ~buffer~: search the current buffer -+ ~all~: search all Org buffers -+ ~agenda~: search buffers returned by the function ~org-agenda-files~ -+ A space-separated list of file or buffer names - -~GROUPS~: An ~org-super-agenda~ group set. See variable ~org-super-agenda-groups~. - -~NARROW~: When non-nil, don't widen buffers before searching. Interactively, with prefix, leave narrowed. - -~SORT~: One or a list of ~org-ql~ sorting functions, like ~date~ or ~priority~. - -*Bindings:* Keys bound in results buffer. -+ =r=: Refresh results. With prefix, prompt to adjust search parameters. -+ =v=: Show =transient= view dispatcher (like Magit's popups). -+ =C-x C-s=: Save query to variable ~org-ql-views~ (accessible with command ~org-ql-view~). - -*Note:* The view buffer is currently put in ~org-agenda-mode~, which means that /some/ Org Agenda commands work, such as jumping to entries and changing item priorities (without necessarily updating the view). This feature is experimental and not guaranteed to work correctly with all commands. (It works to the extent it does because the appropriate text properties are placed on each item, imitating an Agenda buffer.) - -*Note:* Also, this buffer is compatible with [[https://github.com/oantolin/embark][Embark]]: the ~embark-act~ command can be called on an entry to act on it immediately, without having to visit the entry in its source Org buffer. - -*** helm-org-ql - -/Note: This command uses [[#non-sexp-query-syntax][non-sexp queries]]. It is available separately in the package =helm-org-ql=./ - -This command displays matches with Helm. - -+ Press =C-x C-s= in the Helm session to save the results to an =org-ql-search= buffer. - -*** org-ql-view - -Choose and display a view stored in ~org-ql-views~. - -*Bindings:* Keys bound in view buffer. -+ =g=, =r=: Refresh results. With prefix, prompt to adjust search parameters. -+ =v=: Show =transient= view dispatcher (like Magit's popups). -+ =C-x C-s=: Save query to variable ~org-ql-views~ (accessible with command ~org-ql-view~). - -*** org-ql-view-sidebar - -Show a sidebar window listing views stored in =org-ql-views= for easy access. In the sidebar, press =RET= or =mouse-1= to show the view at point, and press =c= to customize the view at point. - -*** org-ql-view-recent-items - -Show items in ~FILES~ from last ~DAYS~ days with timestamps of ~TYPE~. ~TYPE~ may be ~ts~, ~ts-active~, ~ts-inactive~, ~clocked~, ~closed~, ~deadline~, ~planning~, or ~scheduled~. =FILES= defaults to those returned by the function =org-agenda-files=. - -*** org-ql-sparse-tree - -Arguments: ~(query &key keep-previous (buffer (current-buffer)))~ - -Show a sparse tree for ~QUERY~ in ~BUFFER~ and return number of results. The tree will show the lines where the query matches, and any other context defined in ~org-show-context-detail~, which see. - -~QUERY~ is an ~org-ql~ query sexp (quoted, since this is a function). ~BUFFER~ defaults to the current buffer. When ~KEEP-PREVIOUS~ is non-nil (interactively, with prefix), the outline is not reset to the overview state before finding matches, which allows stacking calls to this command. Runs ~org-occur-hook~ after making the sparse tree. - -** Queries -:PROPERTIES: -:TOC: :include descendants :depth 1 -:END: -:CONTENTS: -- [[#non-sexp-query-syntax][Non-sexp query syntax]] -- [[#general-predicates][General predicates]] -- [[#ancestordescendant-predicates][Ancestor/descendant predicates]] -- [[#datetime-predicates][Date/time predicates]] -:END: - -An =org-ql= query is a Lisp expression which may contain arbitrary expressions, as well as calling certain built-in predicates. It is byte-compiled into a predicate function which is tested with point on each heading in an Org buffer; when it returns non-nil, the heading matches the query. When possible, certain built-in predicates are optimized away to whole-buffer regular expression searches, which are much faster to search for than testing the predicate on each heading. - -*Notes:* -+ Bare strings like ~"string"~ are automatically converted to ~(regexp "string")~ predicates. -+ Standard numeric comparator function symbols (~<~, ~<=~, ~>~, ~>=~, ~=~ ) need not be quoted when passed as an argument to predicates which accept them. The resemblance to infix notation is coincidental. - -*** Non-sexp query syntax - -The command =org-ql-search= also accepts, and the command =helm-org-ql= only accepts, an alternative, non-sexp query syntax. The syntax is simple, and a few examples of queries in both syntaxes should suffice. By default, when multiple predicates are used, they are combined with boolean =and=. - -| Sexp syntax | Non-sexp syntax | -|-------------------------------------------------+----------------------------------------------| -| ~(todo)~ | ~todo:~ | -| ~(todo "SOMEDAY")~ | ~todo:SOMEDAY~ | -| ~(todo "SOMEDAY" "WAITING")~ | ~todo:SOMEDAY,WAITING~ | -| ~(ts :on today)~ | ~ts:on=today~ | -| ~(ts-active :from "2017-01-01" :to "2018-01-01")~ | ~ts-active:from=2017-01-01,to=2018-01-01~ | -| ~(clocked :on -1)~ | ~clocked:on=-1~ | -| ~(heading "quoted phrase" "word")~ | ~heading:"quoted phrase",word~ | -| ~(and (tags "book" "books") (priority "A"))~ | ~tags:book,books priority:A~ | -| ~(src :lang "elisp" :regexps ("defun"))~ | ~src:defun,lang=elisp~ or ~src:lang=elisp,defun~ | -| ~(and (tags "space") (not (regexp "moon")))~ | ~tags:space !moon~ | -| ~(priority >= B)~ | ~priority:A,B~ | - -Note that the =effort=, =level=, and =priority= predicates do not support comparators in the non-sexp syntax, so multiple arguments should be passed instead, as seen in the last example. - -*** General predicates - -Arguments are listed next to predicate names, where applicable. - -+ =blocked= :: Return non-nil if current heading is blocked. Calls ~org-entry-blocked-p~, which see. -+ =category (&rest categories)= :: Return non-nil if current heading is in one or more of ~CATEGORIES~ (a list of strings). -+ =done= :: Return non-nil if entry's ~TODO~ keyword is in ~org-done-keywords~. -+ =effort (&optional effort-or-comparator effort)= :: Return non-nil if current heading's effort property matches arguments. The following forms are accepted: ~(effort DURATION)~: Matches if effort is ~DURATION~. ~(effort DURATION DURATION)~: Matches if effort is between DURATIONs, inclusive. ~(effort COMPARATOR DURATION)~: Matches if effort compares to ~DURATION~ with ~COMPARATOR~. ~COMPARATOR~ may be ~<~, ~<=~, ~>~, or ~>=~. ~DURATION~ should be an Org effort string, like =5= or =0:05=. -+ =habit= :: Return non-nil if entry is a habit. -+ =heading (&rest strings)= :: Return non-nil if current entry's heading matches all ~STRINGS~. Matching is done case-insensitively. - - Aliases: =h=. -+ ~heading-regexp (&rest regexps)~ :: Return non-nil if current entry's heading matches all ~REGEXPS~ (regexp strings). Matching is done case-insensitively. - - Aliases: ~h*~. -+ =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 ~>=~. -+ =link (&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. -+ =outline-path (&rest strings)= :: Return non-nil if current node's outline path matches all of ~STRINGS~. Each string may appear as a substring in any part of the node's outline path. For example, the path =Food/Fruits/Grapes= would match ~(olp "Fruit" "Grape")~. - - Aliases: ~olp~. -+ =outline-path-segment (&rest strings)= :: Return non-nil if current node's outline path matches ~STRINGS~. Matches ~STRINGS~ as a contiguous segment of the outline path. Each string is compared as a substring. For example the path ~Food/Fruits/Grapes~ would match ~(olps "Fruit" "Grape")~ but not ~(olps "Food" "Grape")~. - - Aliases: ~olps~. -+ =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. -+ =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 letter string. For example: ~(priority "A") (priority "A" "B") (priority '>= "B")~ Note that items without a priority cookie never match this predicate (while Org itself considers items without a cookie to have the default priority, which, by default, is equal to priority ~B~). -+ =property (property &optional value &key inherit)= :: Return non-nil if current entry has ~PROPERTY~ (a string), and optionally ~VALUE~ (a string). If ~INHERIT~ is nil, only match entries with ~PROPERTY~ set on the entry; if t, also match entries with inheritance. If ~INHERIT~ is not specified, use the value of ~org-use-property-inheritance~, which see. -+ =regexp (&rest regexps)= :: Return non-nil if current entry matches all of ~REGEXPS~ (regexp strings). Matches against entire entry, from beginning of its heading to the next heading. - - Aliases: =r=. -+ =rifle (&rest strings)= :: Return non-nil if each string is found in either the entry or its outline path. Works like =org-rifle=. This is probably the most useful, intuitive, general-purpose predicate. - - Aliases: ~smart~. - - *Note:* By default, this is the default predicate used for plain-string query tokens (i.e. given without a specified predicate). This can be customized with the option ~org-ql-default-predicate~. -+ ~src (&key lang regexps)~ :: Return non-nil if current entry contains an Org Babel source block. If ~LANG~ is non-nil, match blocks of that language. If ~REGEXPS~ is non-nil, require that block's contents match all regexps. Matching is done case-insensitively. -+ =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. -+ =tags-inherited (&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. - - Aliases: ~inherited-tags~, ~tags-i~, ~itags~. -+ =tags-local (&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. - - Aliases: ~local-tags~, ~tags-l~, ~ltags~. -+ =tags-all (&rest tags)= :: Return non-nil if current heading includes all of ~TAGS~. Tests both inherited and local tags. - - Aliases: ~tags&~. -+ =tags-regexp (&rest regexps)= :: Return non-nil if current heading has tags matching one or more of ~REGEXPS~. Tests both inherited and local tags. - - Aliases: ~tags*~. -+ =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 called without arguments, only matches non-done tasks (i.e. does not match keywords in ~org-done-keywords~). - -*** Ancestor/descendant predicates - -+ =ancestors (&optional query)= :: Return non-nil if current heading has ancestor headings. If ~QUERY~, return non-nil if an ancestor heading matches it. This selector may be nested. -+ =children (&optional query)= :: Return non-nil if current heading has direct child headings. If ~QUERY~, return non-nil if a child heading matches it. This selector may be nested, e.g. to match grandchild headings. -+ =descendants (&optional query)= :: Return non-nil if current heading has descendant headings. If ~QUERY~, return non-nil if a descendant heading matches it. This selector may be nested (if you can grok the nesting!). -+ =parent (&optional query)= :: Return non-nil if current heading has a direct parent heading. If ~QUERY~, return non-nil if the parent heading matches it. This selector may be nested, e.g. to match grandparent headings. - -*** Date/time predicates - -These predicates take optional keyword arguments: - -+ ~:from~: Match entries whose timestamp is on or after timestamp ~:from~. -+ ~:to~: Match entries whose timestamp is on or before timestamp ~:to~. -+ ~:on~: Match entries whose timestamp is on date ~:on~. -+ ~:with-time~: If unspecified, match timestamps with or without times (i.e. HH:MM). If nil, match timestamps without times. If t, match timestamps with times. - -Timestamp/date arguments should be either a number of days (positive to look forward, or negative to look backward), a string parseable by ~parse-time-string~ (the string may omit the time value), the symbol ~today~, or a ~ts~ struct. - -+ *Predicates* - - =ts= :: Return non-nil if current entry has a timestamp in given period. Without arguments, return non-nil if entry has a timestamp. - - =ts-active=, =ts-a= :: Like =ts=, but only matches active timestamps. - - =ts-inactive=, =ts-i= :: Like =ts=, but only matches inactive timestamps. - -The following predicates, in addition to the keyword arguments, can also take a single argument, a number, which looks backward or forward a number of days. The number can be negative to invert the direction. - -These two predicates interpret a single number argument as if it were passed to the ~:from~ keyword argument, which eases the common case of searching for items clocked or closed in the past few days: - -+ *Backward-looking* - - =clocked= :: Return non-nil if current entry was clocked in given period. Without arguments, return non-nil if entry was ever clocked. Note: Clock entries are expected to be clocked out. Currently clocked entries (i.e. with unclosed timestamp ranges) are ignored. - - =closed= :: Return non-nil if current entry was closed in given period. Without arguments, return non-nil if entry is closed. - -These predicates interpret a single number argument as if it were passed to the ~:to~ keyword argument, which eases the common case of searching for items planned in the next few days: - -+ *Forward-looking* - - =deadline= :: Return non-nil if current entry has deadline in given period. If argument is =auto=, return non-nil if entry has deadline within =org-deadline-warning-days=. Without arguments, return non-nil if entry has any deadline. - - =planning= :: Return non-nil if current entry has planning timestamp (i.e. its deadline, scheduled, or closed timestamp) in given period. Without arguments, return non-nil if entry has any planning timestamp. - - =scheduled= :: Return non-nil if current entry is scheduled in given period. Without arguments, return non-nil if entry is scheduled. - -** Functions / Macros -:PROPERTIES: -: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-deprecated][Macro: org-ql (deprecated)]] -- [[#custom-predicates][Custom predicates]] - - [[#macro-org-ql-defpred][Macro: org-ql-defpred]] -:END: - -*** Agenda-like views - -**** Function: ~org-ql-block~ - -For use as a custom agenda block type in ~org-agenda-custom-commands~. For example, you could define a custom series command like this, which would list all priority A items tagged =Emacs= with to-do keyword =SOMEDAY=, followed by the standard agenda view, in a single buffer: - -#+BEGIN_SRC elisp - (setq org-agenda-custom-commands - '(("ces" "Custom: Agenda and Emacs SOMEDAY [#A] items" - ((org-ql-block '(and (todo "SOMEDAY") - (tags "Emacs") - (priority "A")) - ((org-ql-block-header "SOMEDAY :Emacs: High-priority"))) - (agenda))))) -#+END_SRC - -Which would be equivalent to a ~tags-todo~ search like this: - -#+BEGIN_SRC elisp - (setq org-agenda-custom-commands - '(("ces" "Custom: Agenda and Emacs SOMEDAY [#A] items" - ((tags-todo "PRIORITY=\"A\"+Emacs/!SOMEDAY") - (agenda))))) -#+END_SRC - -However, the ~org-ql-block~ version runs in about 1/5th the time. - -The variable =org-ql-block-header= may be bound to a string to use as the block header, otherwise the header is formed automatically. - -*** Listing / acting-on results - -**** Caching - -Org QL uses a per-buffer cache to speed up subsequent searches. It's keyed on query expressions and match actions, which means that, for the same query and same match action in the same buffer, if the buffer has not been modified since the last time the query was run, the cached match-action result will be returned, and the query will not be evaluated in that buffer again. - -Therefore, since neither query expressions nor match actions are guaranteed to be evaluated when the following functions are called, they should be free of side effects. Or, if a side effect is required, the cache should be invalidated (e.g. by incrementing the buffer's modified tick, or by using a query expression or match action that has yet to be cached). /Note: Future improvements will allow the cache to be more easily disabled or cleared./ - -**** Function: ~org-ql-select~ - -/Arguments:/ ~(buffers-or-files query &key action narrow sort)~ - -Return items matching ~QUERY~ in ~BUFFERS-OR-FILES~. - -~BUFFERS-OR-FILES~ is a one or a list of files and/or buffers. - -~QUERY~ is an ~org-ql~ query sexp (quoted, since this is a function). - -~ACTION~ is a function which is called on each matching entry with point at the beginning of its heading. It may be: - - - ~element~ or nil: Equivalent to ~org-element-headline-parser~. - - - ~element-with-markers~: Equivalent to calling ~org-element-headline-parser~, with markers added using ~org-ql--add-markers~. Suitable for formatting with ~org-ql-agenda--format-element~, allowing insertion into an Org Agenda-like buffer. - - - A sexp, which will be byte-compiled into a lambda function. - - - A function symbol. - -If ~NARROW~ is non-nil, buffers are not widened (the default is to widen and search the entire buffer). - -~SORT~ is either nil, in which case items are not sorted; or one or a list of defined ~org-ql~ sorting methods (~date~, ~deadline~, ~scheduled~, ~closed~, ~todo~, ~priority~, or ~random~); or a user-defined comparator function that accepts two items as arguments and returns nil or non-nil. - -Examples: - -#+BEGIN_SRC elisp - ;; Return list of to-do headings in inbox file with tags and to-do keywords: - (org-ql-select "~/org/inbox.org" - '(todo) - :action #'org-get-heading) - ;; => ("TODO Practice leaping tall buildings in a single bound :personal:" ...) - - ;; Without tags and to-do keywords: - (org-ql-select "~/org/inbox.org" - '(todo) - :action '(org-get-heading t t)) - ;; => ("Practice leaping tall buildings in a single bound" ...) - - ;; Return WAITING heading elements in agenda files: - (org-ql-select (org-agenda-files) - '(todo "WAITING") - :action 'element) - ;; => ((headline (:raw-value "Visit the moon" ...) ...) ...) - - ;; Since `element' is the default for ACTION, it may be omitted: - (org-ql-select (org-agenda-files) - '(todo "WAITING")) - ;; => ((headline (:raw-value "Visit the moon" ...) ...) ...) -#+END_SRC - -**** Function: ~org-ql-query~ - -/Arguments:/ ~(&key (select 'element-with-markers) from where order-by narrow)~ - -Like ~org-ql-select~, but arguments are named more like a ~SQL~ query. - -+ ~SELECT~ corresponds to the ~org-ql-select~ argument ~ACTION~. -+ ~FROM~ corresponds to the ~org-ql-select~ argument ~BUFFERS-OR-FILES~. -+ ~WHERE~ corresponds to the ~org-ql-select~ argument ~QUERY~. -+ ~ORDER-BY~ corresponds to the ~org-ql-select~ argument ~SORT~, which see. -+ ~NARROW~ corresponds to the ~org-ql-select~ argument ~NARROW~. - -Examples: - -#+BEGIN_SRC elisp - ;; Return list of to-do headings in inbox file with tags and to-do keywords: - (org-ql-query - :select #'org-get-heading - :from "~/org/inbox.org" - :where '(todo)) - ;; => ("TODO Practice leaping tall buildings in a single bound :personal:" ...) - - ;; Without tags and to-do keywords: - (org-ql-query - :select '(org-get-heading t t) - :from "~/org/inbox.org" - :where '(todo)) - ;; => ("Practice leaping tall buildings in a single bound" ...) - - ;; Return WAITING heading elements in agenda files: - (org-ql-query - :select 'element - :from (org-agenda-files) - :where '(todo "WAITING")) - ;; => ((headline (:raw-value "Visit the moon" ...) ...) ...) - - ;; Since `element' is the default for SELECT, it may be omitted: - (org-ql-query - :from (org-agenda-files) - :where '(todo "WAITING")) - ;; => ((headline (:raw-value "Visit the moon" ...) ...) ...) -#+END_SRC - -**** Macro: ~org-ql~ (deprecated) - -/Arguments:/ ~(buffers-or-files query &key sort narrow markers action)~ - -Expands into a call to ~org-ql-select~ with the same arguments. For convenience, arguments should be unquoted. - -/Note: This macro is deprecated and will be removed in v0.7./ - -*** 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. NOTE: Normalizers are applied to a query repeatedly until the query is fully normalized, so normalizers should be carefully written to avoid infinite loops. - -~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: - -+ ~:query~: An Org QL query expression in either sexp or non-sexp form. -+ ~:columns~ A list of columns, including ~heading~, ~todo~, ~property~, ~priority~, ~deadline~, ~scheduled~, ~closed~. - - Each column may also be specified as a list with the second element being a header string. For example, to abbreviate the priority column: ~(priority "P")~. - - For certain columns, like =property=, arguments may be passed by specifying the column type itself as a list. For example, to display a column showing the values of a ~property~ named ~milestone~, with the header being abbreviated to ~M~: ~((property "milestone") "M")~. -+ ~:sort~ One or a list of Org QL sorting methods (see ~org-ql-select~). -+ ~:take~ Optionally take a number of results from the front (a positive number) or the end (a negative number) of the results. -+ ~:ts-format~ Optional format string used to format timestamp-based columns. - -The heading column is formatted as a link to the heading (not shown in the following example). - -For example, this dynamic block shows the first seven headings that are to-do items with priority A or B, sorted by deadline then priority, with certain columns (including the value of the =agenda-group= property with a custom header) and timestamp format: - -# NOTE: These results are edited manually because the Org links don't display well in the Info manual. - -#+BEGIN_SRC org - ,#+BEGIN: org-ql :query "todo: priority:A,B" :columns (todo (priority "P") ((property "agenda-group") "Group") deadline heading) :sort (deadline priority) :take 7 :ts-format "%Y-%m-%d %H:%M" - | Todo | P | Group | Deadline | Heading | - |------+---+-------+------------------+---------------------------------------| - | TODO | A | | 2017-07-07 00:00 | Take over the world | - | TODO | B | | 2017-07-10 00:00 | Renew membership in supervillain club | - | TODO | A | plans | 2017-07-15 00:00 | Take over the universe | - | TODO | B | | 2017-07-21 00:00 | Internet | - | TODO | A | bills | 2017-08-01 00:00 | Spaceship lease | - | TODO | A | | | Skype with president of Antarctica | - | TODO | B | | | Take over Mars | - ,#+END: -#+END_SRC - -** Links - -Org QL View searches may be accessed by opening ~org-ql-search:~ links in an Org file. - -In an Org QL View buffer, the command ~org-store-link~ (i.e. ~C-c l~) stores a link to the current search, and it may be inserted into an Org buffer with the command ~org-insert-link~ (~C-c C-l~). The stored link records all of the view settings, like title, sorting, and grouping. - -Simple links may also be written manually in either sexp or non-sexp form, like: - -#+BEGIN_SRC org - [[org-ql-search:todo:NEXT priority:A]] - [[org-ql-search:(and (todo "NEXT") (priority "A"))]] -#+END_SRC - -** Tips - -+ Org QL View buffers can be bookmarked with Emacs bookmark commands, e.g. =C-x r m=. This also integrates with [[https://github.com/alphapapa/org-sidebar][org-sidebar]] and [[https://github.com/alphapapa/burly.el][Burly]]. - -* Changelog -:PROPERTIES: -:TOC: ignore-children -:END: - -/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.9-pre - -*Additions* -+ Face ~org-ql-view-query~, applied to view queries in header line. -+ Face ~org-ql-view-title~, applied to view titles in header line. -+ Option ~org-ql-view-relative-deadline-prefix~. - -*Changes* -+ Command ~org-ql-find~ respects narrowing of the current buffer by default, allowing searching within the narrowed region. (Using one ~C-u~ argument widens the current buffer, and using two ~C-u~ arguments prompts for the buffers to search.) -+ Function ~org-ql-completing-read~ accepts a new ~NARROWP~ argument, which is passed to ~org-ql-select~. - -*Fixes* -+ Customization group for face ~org-ql-view-due-date~. -+ Apply Org syntax font-locking to items in ~org-ql-view~ buffers. - -*** helm-org-ql - -Tagged v0.6.2, fixing a compilation warning. - -** 0.8.10 - -*Fixes* -+ Command ~org-ql-refile~ uses the base buffer when refiling to an indirect buffer. ([[https://github.com/alphapapa/org-ql/issues/466][#466]].) -+ Predicate ~link~ could signal an error when searching text that is mistakenly recognized as an Org link (e.g. Bash double-bracket constructs in a source block). (Thanks to [[https://github.com/jwiegley][John Wiegley]] for reporting.) - -** 0.8.9 - -*Fixes* -+ Predicate ~property~ when called with argument form ~(property "PROPERTY-NAME" :inherit t)~. ([[https://github.com/alphapapa/org-ql/issues/460][#460]]. Thanks to [[https://github.com/Stewmath][Stewmath]] for reporting.) -+ Predicate ~level~'s preamble optimizer allows expressions in place of the numeric argument. (See [[https://github.com/alphapapa/org-ql/issues/460][#460]]. Thanks to [[https://github.com/Stewmath][Stewmath]] for reporting.) -+ Reading of view settings from Org links in upcoming Emacs version. ([[https://github.com/alphapapa/org-ql/issues/461][#461]]. Thanks to [[https://github.com/snogge][Ola Nilsson]] for help debugging, and for maintaining [[https://github.com/jorgenschaefer/emacs-buttercup][Buttercup]].) - -*Compatibility* -+ Fix compilation error on Emacs 30. ([[https://github.com/alphapapa/org-ql/issues/433][#433]]. Thanks to [[https://github.com/akirak][Akira Komamura]] and [[https://github.com/monnier][Stefan Monnier]].) - -** 0.8.8 - -*Fixes* -+ Remove text properties from to-do keywords before displaying them in an ~org-ql-view~ buffer. (Such text properties could cause them to, e.g. display with extra leading spaces, depending on which other modes might be enabled in the source Org buffer.) -+ Binding of ~completion-styles-alist~ in ~org-ql-completing-read~. (This fixes compatibility with Helm's ~helm~ completion style, as well as default Emacs completion in recursive minibuffers. [[https://github.com/alphapapa/org-ql/issues/337][#337]]. Thanks to [[https://github.com/progfolio][Nicholas Vollmer]], [[https://github.com/9viz][viz]], and [[https://github.com/karthink][Karthik Chikmagalur]] for reporting and suggesting fixes.) -+ Use of the context snippet function for ~org-ql-completing-read~. ([[https://github.com/alphapapa/org-ql/issues/419][#419]]. Thanks to [[https://github.com/tpeacock19][tpeacock19]] for reporting.) - -** 0.8.7 - -*Fixes* -+ Timestamps with internal time ranges (e.g. ~<2024-06-26 10:00-11:00>~) are matched for simple queries. (This support is not yet comprehensive, e.g. a query that depends on the specific inner time range may not behave as expected. Previously such timestamps were not matched at all. See [[https://github.com/alphapapa/org-ql/pull/237][#237]] and [[https://github.com/alphapapa/org-ql/issues/371][#371]]. Thanks to [[https://github.com/yantar92][Ihor Radchenko]].) -+ Timestamps with day-of-the-week abbreviations are matched more flexibly (allowing, e.g. a period in French locales). (See [[https://github.com/alphapapa/org-ql/discussions/429][#429]], [[https://github.com/alphapapa/org-ql/issues/432][#432]]. Thanks to [[https://github.com/neurolit][Florian D.]] for reporting.) -+ Command ~org-ql-search~ did not narrow properly when called interactively. - -*Compatibility* -+ Dynamic blocks work with Org 9.7. ([[https://github.com/alphapapa/org-ql/issues/431][#431]]. Thanks to [[https://github.com/jezcope][Jez Cope]] for reporting.) - -** 0.8.6 - -*Fixes* -+ Bookmarking ~org-ql-view~ buffers when the ~buffers-files~ argument is a symbol (like ~org-agenda-files~). - -** 0.8.5 - -*Fixes* -+ Predicate ~heading~ incorrectly matched strings as regular expressions, sometimes returning incorrect results. (See [[https://github.com/alphapapa/org-ql/discussions/410][discussion]]. Thanks to [[https://github.com/al3xandru][Alex Popescu]] for reporting.) -+ Predicates ~ancestor~ and ~parent~ did not normalize their sub-queries, sometimes returning incorrect results. ([[https://github.com/alphapapa/org-ql/issues/365][#365]]. Thanks to [[https://github.com/kofm][Gabriele Mongiano]] for reporting.) - -** 0.8.4 - -*Fixes* - -+ Command ~org-ql-find~ goes to the selected entry in the base buffer (rather than potentially an indirect buffer, whose narrowing could leave the selected entry hidden. The nuances around going to entries in buffers that may be indirect and/or narrowed are surprisingly complicated. Hopefully this is the last fix). - -** 0.8.3 - -*Fixes* - -+ Command ~org-ql-find~ incorrectly moved point. (See [[https://github.com/alphapapa/org-ql/issues/380#issuecomment-1881913025][#380]]. Thanks to [[https://github.com/oantolin][Omar Antolín Camarena]] for reporting.) - -** 0.8.2 - -*Fixes* - -+ Command ~org-ql-find~ incorrectly restored the buffer after jumping when not using indirect buffers. (See [[https://github.com/alphapapa/org-ql/issues/380#issuecomment-1881913025][#380]]. Thanks to [[https://github.com/bram85][Bram Schoenmakers]] for reporting.) - -** 0.8.1 - -*Fixes* - -+ Command ~org-ql-find~ widens the buffer before going to the selected entry. -+ In ~org-ql-view~ buffers, links in headings remain clickable links. (Fixes [[https://github.com/alphapapa/org-ql/issues/282][#282]]. Thanks to [[https://github.com/jakebox][Jacob Boxerman]] for reporting.) - -** 0.8 - -*Additions* - -+ Function ~org-ql-completing-read~, used by command ~org-ql-find~, now specifies the completion category as ~org-heading~, providing compatibility with [[https://github.com/oantolin/embark][Embark]]. (This is a powerful feature, as it means any ~org-ql-find~ result can be acted on from inside the search results with Embark, which provides common actions from Org Agenda and Org speed keys bindings.) ([[https://github.com/alphapapa/org-ql/issues/299][#299]]. Thanks to [[https://github.com/oantolin][Omar Antolín Camarena]], [[https://github.com/minad][Daniel Mendler]], and [[https://github.com/akirak][Akira Komamura]].) - - Command ~org-ql-completing-read-export~, bound to ~C-c C-e~ or ~embark-export~ while in an ~org-ql-completing-read~ session, exits and shows an ~org-ql-view~ buffer for the current search. -+ Command ~org-ql-find~ may be called in an ~org-agenda~ or ~org-ql-view~ buffer to search the buffers which contributed to the agenda/view buffer. -+ Command ~org-ql-find-path~, which searches outline paths in the current buffer. -+ Command ~org-ql-open-link~, which finds links in entries matching the given query, and opens the selected one with ~org-open-at-point~. (This is helpful when a collection of links are kept in Org files: rather than having to first visit the entry containing the desired link, then locate it within the entry, and then open it, the user can simply select the link and open it directly.) -+ Items in ~org-ql-view~ buffers now include the ~org-category~ text property, like Org Agenda buffers, which allows grouping with ~org-super-agenda~'s category-related selectors. ([[https://github.com/alphapapa/org-ql/issues/363][#363]]. Thanks to [[https://github.com/kofm][Gabriele Mongiano]] for reporting.) - -*Fixes* - -+ Predicate ~property~ correctly uses the value of ~org-use-property-inheritance~ when not specified. ([[https://github.com/alphapapa/org-ql/pull/346][#346]], [[https://github.com/alphapapa/org-ql/issues/356][#356]]. Thanks to [[https://github.com/bram85][Bram Schoenmakers]].) - -*Compatibility* - -+ Emacs 27.1 or later is now required. -+ Org v9.7's ~org-element~ API changes required some adjustments. ([[https://github.com/alphapapa/org-ql/issues/364][#364]]. Thanks to several users for reporting, and to [[https://github.com/yantar92][Ihor Radchenko]] for his feedback.) - -** 0.7.4 - -*Fixes* -+ Ignore empty quoted strings in plain-string queries ([[https://github.com/alphapapa/org-ql/issues/383][#383]]). - -** 0.7.3 - -*Fixes* -+ Disable ~case-fold-search~ when collecting headings in outline paths. (Headings that started with a word that is also a to-do keyword but with different capitalization would be matched incorrectly.) -+ Saving of ~org-ql-view~ views. ([[https://github.com/alphapapa/org-ql/issues/378][#378]]. Thanks to [[https://github.com/Pentaquark1][Pentaquark1]] for reporting.) -+ Command ~org-ql-find~ didn't move point to the selected entry. ([[https://github.com/alphapapa/org-ql/issues/380][#380]]. Thanks to [[https://github.com/oantolin][Omar Antolín Camarena]] for reporting.) - -** 0.7.2 - -*Fixes* -+ Timestamp predicates are more tolerant of partial input (e.g. preventing errors while the user is typing a query into ~org-ql-find~). -+ Query parser ignores leading whitespace (e.g. preventing errors while the user is typing a query into ~org-ql-find~). -+ Use of ~org-ql-find~ with ~:query-prefix~ argument prevented selection of results. ([[https://github.com/alphapapa/org-ql/issues/351][#351]]. Thanks to [[https://github.com/danielfleischer][Daniel Fleischer]] for reporting.) -+ Handle narrowed buffers correctly in ~org-ql-find~. -+ Warn about empty headings in ~org-ql-completing-read~ (the Org format allows a heading line to have no text, but it's useless for this purpose, and usually indicates unnoticed corruption). - -** 0.7.1 - -*Fixes* -+ Function ~org-ql-completing-read~ is more compatible with default Emacs completion. (See [[https://github.com/alphapapa/org-ql/issues/338][#338]]. Thanks to [[https://github.com/arozbiz][arozbiz]] for reporting.) -+ Function ~org-ql-completing-read~ would sometimes stop updating with changes in input. (See [[https://github.com/alphapapa/org-ql/issues/350][#350]]. Thanks to [[https://github.com/anpandey][Ankit Raj Pandey]] for reporting and fixing, and to [[https://github.com/minad][Daniel Mendler]] for advising.) -+ In ~org-ql-completing-read~, format links for display, and use ~org-entry-get~ internally rather than ~org-get-heading~. - -** 0.7 - -*Added* -+ Command ~org-ql-find~, which jumps to entries selected using Emacs's built-in completion facilities and Org QL queries (like ~helm-org-ql~, but doesn't require Helm.). -+ Command ~org-ql-refile~, which refiles the entry at point to one selected using Org QL completion. -+ Predicate ~rifle~, which matches an entry if each of the given arguments is found in either the entry's contents or its outline path. This provides very intuitive results, mimicing the behavior of [[https://github.com/alphapapa/org-rifle][=org-rifle=]]. In fact, the results are so useful that it's now the default predicate for plain-string query tokens. (It is also aliased to ~smart~, since it's so "smart," and not all users have used =org-rifle=.) -+ Option ~org-ql-default-predicate~, applied to plain-string query tokens (before, the ~regexp~ predicate was always used, but now it may be customized). -+ Alias ~c~ for predicate ~category~. -+ Predicate ~property~ now accepts the argument ~:inherit~ to match entries with property inheritance, and when unspecified, the option ~org-use-property-inheritance~ controls whether inheritance is used. -+ Predicate ~blocked~. (Thanks to [[https://github.com/akirak][Akira Komamura]].) - -*Changed* -+ Give more useful error message for invalid queries. -+ Predicate ~src~ now matches case-insensitively. -+ Command ~org-ql-sparse-tree~ accepts both string and sexp queries. (Thanks to [[https://github.com/akirak][Akira Komamura]].) - -*Fixed* -+ Predicate ~link~ matches links whose descriptions contain escaped brackets (changed in Org 9.3). (Thanks to [[https://github.com/exot][Daniel Borchmann]] for reporting.) -+ Predicate ~src~'s matching of begin/end block lines, normalization of arguments, and handling in non-sexp queries. (Thanks to [[https://github.com/akirak][Akira Komamura]] for reporting.) -+ Predicate ~src~'s behavior with various arguments. -+ Various compilation warnings. - -*Internal* -+ Certain query predicates, when called multiple times in an ~and~ sub-expression, are optimized to a single call. -+ Use ~buffer-chars-modified-tick~ instead of ~buffer-modified-tick~. (Thanks to [[https://github.com/yantar92][Ihor Radchenko]].) -+ Implemented tests for ~src~ predicate. - -*Credits* -+ Thanks to [[https://github.com/chasecaleb][Caleb Chase]] for help with [[https://github.com/alphapapa/org-ql/pull/285][#285]], fixed in [[https://github.com/alphapapa/org-ql/commit/91908186fcca4b5fd2e9d26da5bc0375c2b41acf][9190818]]. - -** 0.6.3 - -*Fixed* -+ Non-sexp query parsing with updated version 1.0.1 of the ~peg~ package. (Fixes [[https://github.com/alphapapa/org-ql/issues/314][#314]], [[https://github.com/alphapapa/org-ql/issues/316][#316]]. Thanks to [[https://github.com/akirak][Akira Komamura]] and [[https://github.com/joonro][Joon Ro]] for reporting.) -+ Require library ~org-duration~ (apparently necessary in newer Org versions). - -** 0.6.2 - -*Fixed* -+ ~link~ predicate when used in an ~or~'ed query. ([[https://github.com/alphapapa/org-ql/issues/279][#279]]. Thanks to [[https://github.com/telenieko][Marc Fargas]] for reporting.) - -** 0.6.1 - -*Fixed* -+ In dynamic blocks, links to headings with statistics cookies were broken. (Fixes [[https://github.com/alphapapa/org-ql/issues/248][#248]]. Thanks to [[https://github.com/maikol-solis][Maikol Solis]] and [[https://github.com/yantar92][Ihor Radchenko]].) - -*Updated* -+ Compatibility with new macro names in Transient. ([[https://github.com/alphapapa/org-ql/pull/269][#269]]. Thanks to [[https://github.com/tarsius][Jonas Bernoulli]].) - -** 0.6 - -*Added* -+ Macro =org-ql-defpred=, used to define search predicates. (See [[file:examples/defpred.org][tutorial]].) -+ Predicate ~effort~. -+ Predicate ~heading-regexp~, which matches regular expressions against heading text (alias: ~h*~). -+ Timestamp-related predicates now accept an optional ~:with-time~ argument, which allows matching timestamps with or without times (i.e. HH:MM). -+ Sorting methods: - - ~reverse~ - - ~closed~ (Thanks to [[https://github.com/yejianye][Ryan Ye]].) -+ Dynamic block column ~closed~. (Thanks to [[https://github.com/yejianye][Ryan Ye]].) -+ Abbreviate filenames in bookmarks. (Thanks to [[https://github.com/akirak][Akira Komamura]].) - -*Changed* -+ The order in which sorting functions is applied has been reversed. For example, ~:sort '(todo priority date)~ now does what ~:sort '(date priority todo)~ did in earlier versions. (This change is made to enable the new ~reverse~ sorting method.) Users who have customized =org-ql-views= will need to update the stored views' sorting methods to preserve the desired sort order. -+ Helm support (including the command =helm-org-ql=) has been moved to a separate package, =helm-org-ql=. -+ Predicate ~heading~ now matches plain strings instead of regular expressions. -+ Update =dash= dependency, and remove dependency on obsolete =dash-functional=. (Fixes [[https://github.com/alphapapa/org-ql/issues/179][#179]], [[https://github.com/alphapapa/org-ql/issues/209][#209]]. Thanks to [[https://github.com/landakram][Mark Hudnall]], [[https://github.com/akirak][Akira Komamura]], [[https://github.com/natask][Nathanael kinfe]], [[https://github.com/benthamite][Pablo Stafforini]], [[https://github.com/jmay][Jason May]], and [[https://github.com/basil-conto][Basil L. Contovounesios]].) - -*Removed* -+ Obsolete macro ~org-ql~ (obsolete since 0.5, replaced by functions ~org-ql-select~ and ~org-ql-query~). - -*Fixed* -+ Timestamp-related predicates called with relative-date arguments did not properly invalidate the query cache. (Fixes [[https://github.com/alphapapa/org-ql/issues/223][#223]]. Thanks to [[https://github.com/yantar92][Ihor Radchenko]] for reporting.) - -*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. -+ Version 1.0 of library ~peg~ is now required. -+ Improvements to how arguments to timestamp-related predicates are processed. -+ Predicate normalizers are now applied repeatedly until a query is fully normalized. (Normalizers should be written with this in mind to avoid infinite loops.) - -** 0.5.2 - -*Fixed* -+ Predicate ~link~'s ~:target~ and ~:regexp-p~ arguments. ([[https://github.com/alphapapa/org-ql/pull/220][#220]]. Thanks to [[https://github.com/akirak][Akira Komamura]].) - -** 0.5.1 - -*Fixed* -+ Custom sorting functions could corrupt the cache, causing items to disappear after refreshing an =org-ql-search= buffer. ([[https://github.com/alphapapa/org-ql/issues/186][#186]], [[https://github.com/alphapapa/org-ql/issues/187][#187]]. Thanks to [[https://github.com/natask][Nathanael kinfe]].) - -** 0.5 - -*Added* -+ View dispatcher using =transient.el= (like Magit), bound to =v= in search/view buffers. -+ Predicate =link=, which matches descriptions and targets in Org links. -+ Predicate ~tags-regexp~ (alias: ~tags*~), which matches regexps against entry tags (e.g, helpful when a tag might end in "s"). -+ Emacs bookmark support: Org QL View buffers can be bookmarked with, e.g. =C-x r m= and shown with, e.g. =C-x r b=. (This also enables view restoration with [[https://github.com/alphapapa/burly.el][Burly]].) -+ Dynamic block support. -+ Org link support (storing and opening links to Org QL View searches). -+ Mascot. - -*Changed* -+ Binding to refresh search/view buffers changed to =r=. - -*Internal* -+ When formatting entries for Org QL View buffers, use internal function for retrieving heading tags. This improves speed by using our cache, and it removes the need for a compatibility alias for Org versions before 9.3. - -*Deprecated* -+ Macro =org-ql= is marked obsolete. It will be removed in v0.7. Functions =org-ql-select= and =org-ql-query= should be used instead. (The macro serves only to confuse with regard to quoting arguments.) - -*Acknowledgments* -+ [[https://github.com/tpeacock19][tpeacock19]] for extensive help testing new features in this version. - -** 0.4.9 - -*Fixed* -+ Agenda restriction in =org-ql-block=. (Fixes [[https://github.com/alphapapa/org-ql/issues/84][#84]]. Thanks to [[https://github.com/yantar92][Ihor Radchenko]].) - -** 0.4.8 - -*Fixed* -+ Multiple sorters not preserved when refreshing views. (Fixes [[https://github.com/alphapapa/org-ql/issues/136][#136]], [[https://github.com/alphapapa/org-ql/pull/137][#137]]. Thanks to [[https://github.com/natrys][Imran Khan]].) - -** 0.4.7 - -*Fixed* -+ Give a useful error if =org-ql-search-directories-files= is called without a directories argument and =org-directory= doesn't exist. (Fixes [[https://github.com/alphapapa/org-ql/issues/139][#139]]. Thanks to [[https://github.com/matthuszagh][Matt Huszagh]] for reporting.) - -** 0.4.6 - -*Fixed* -+ Compatibility with newer versions of the =peg= library, which removed a macro used by this package. (Fixes [[https://github.com/alphapapa/org-ql/issues/75][#75]]. Thanks to [[https://github.com/novoid][Karl Voit]] and [[https://github.com/karlicoss][@karlicoss]] for reporting.) - -** 0.4.5 - -*Fixed* -+ Non-case-folding predicates like ~(todo)~ unnecessarily disabled case-folding for other predicates. ([[https://github.com/alphapapa/org-ql/issues/114][Issue #114]]. Thanks to [[https://github.com/bitclick][@bitclick]] for reporting.) - -** 0.4.4 - -*Fixed* -+ Compatibility with Org Agenda remote editing commands (some of which were broken by 0.4.3). (Fixes [[https://github.com/alphapapa/org-ql/issues/102][#102]]. Thanks to [[https://github.com/AloisJanicek][Alois Janíček]] for reporting.) - -** 0.4.3 - -*Fixed* -+ When =org-ql-view-refresh= is called, ensure the buffer is an Org QL View buffer. - -** 0.4.2 - -*Fixed* -+ Items' to-do keywords were not shown in views. - -** 0.4.1 - -*Fixed* -+ =level= predicate used with arguments in plain queries. (Thanks to [[https://github.com/akirak][Akira Komamura]] for reporting.) - -** 0.4 - -/Note:/ The next release, 0.5, may include changes which will require minor updates to written queries (e.g. a few predicates may be renamed). Users who wish to avoid those changes happening unexpectedly in their configs should avoid upgrading =org-ql= beyond 0.4 automatically, as they will be pushed to the =master= branch when ready. - -*Added* -+ *Commands* - - ~helm-org-ql-views~, which shows one of ~org-ql-views~ selected with Helm. - - ~org-ql-search~ can search files in ~org-directory~; customization options are available in the ~org-ql-search~ group. - - ~org-ql-view-refresh~ can be called with a prefix argument to adjust search parameters. -+ *Queries* - - Negation of terms in plain queries using ~!~. For example, ~tags:space !moon~ to exclude entries which contain ~moon~. - - Predicates =outline-path= (alias =olp=) and =outline-path-segment= (alias =olps=). - - Predicate ~src~, which matches Org Babel source blocks. - - Predicates =parent= and =ancestors=. (Thanks to [[https://github.com/mm--][Josh Moller-Mara]].) - - Alias =h= for =heading= predicate. - - Alias =r= for =regexp= predicate. (Thanks to [[https://github.com/tumashu][Feng Shu]].) -+ Info manual. -+ Function ~helm-org-ql-source~, which returns a Helm source that searches given buffers/files with ~helm-org-ql~. It can be used for custom Helm commands that search certain files. -+ Display a message when views are refreshed. (Thanks to [[https://github.com/xeijin][xeijin]].) -+ Respect Org Agenda restriction in =org-ql-block=. (Thanks to [[https://github.com/yantar92][Ihor Radchenko]] for reporting.) -+ Option =org-ql-view-sidebar-sort-views=. -+ Mouseover =help-echo= text for =org-ql-views= default view names. -+ "Dangling tasks" default view in =org-ql-views=. (Users who have modified =org-ql-views= from the default will not see the new view unless they copy it into their config.) - -*Changed* -+ Some default =org-ql-view= views (users who have modified =org-ql-views= from the default will not see the new views unless they copy them into their config): - - Rename some views. - - "Stuck projects" view (now uses =descendants= instead of =children=, which is more useful. - -*Fixed* -+ Inherit file tags when =org-tag-inheritance= is enabled. (Fixes [[https://github.com/alphapapa/org-ql/issues/55][#55]]. Thanks to [[https://github.com/mskorzhinskiy][Mikhail Skorzhinskiy]].) -+ Call =helm-make-source= directly instead of using =helm-build-sync-source= macro. (Fixes [[https://github.com/alphapapa/org-ql/issues/60][#60]]. Thanks to [[https://github.com/matthuszagh][Matt Huszagh]] for reporting.) -+ Search/view buffers now always end with a newline, which prevents side-scrolling of the window when calling =end-of-buffer=. -+ Face for done to-do keywords in =org-ql-view= buffers. (Thanks to [[https://github.com/dsdshcym][Yiming Chen]].) -+ Make view buffers read-only. (Fixes [[https://github.com/alphapapa/org-ql/issues/72][#72]]. Thanks to [[https://github.com/xeijin][xeijin]].) -+ Sorting with single sorter specified as an atom. (Thanks to [[https://github.com/legalnonsense][Jeff Filipovits]].) -+ Autoload for =org-ql-block= agenda block. (Fixes [[https://github.com/alphapapa/org-ql/issues/53][#53]]. Thanks to reports from [[https://github.com/gcantieni][Gus Cantieni]], [[https://github.com/novoid][Karl Voit]], [[https://github.com/rieje][rieje]], and [[https://github.com/jakejx][Jake | Junxuan]].) - -*Internal* -+ Added generic node data cache to speed up recursive, tree-based queries. - -** 0.3.2 - -*Fixed* -+ In =org-ql-search=, accept symbol as ~:super-groups~ argument. -+ In the =This week= and =Next week= default =org-ql-views= views, set timestamps for beginning-of-week to 00:00:00 and end-of-week to 23:59:59. -+ Plain quoted-phrases in non-sexp queries. - -** 0.3.1 - -*Fixed* -+ Compatibility with Org 9.2. Thanks to [[https://github.com/leungbk][Brian Leung]]. - -** 0.3 - -*Added* -+ Alternative, non-sexp query syntax for commands =org-ql-search= and =helm-org-ql=. See [[#non-sexp-query-syntax][documentation]]. -+ Command =helm-org-ql=. -+ Command =org-ql-sparse-tree=, like =org-sparse-tree= for =org-ql= queries. (Thanks to [[https://github.com/akirak][Akira Komamura]].) -+ Command =org-ql-view-sidebar=. -+ Per-buffer, per-heading tag caching, which increases the speed of tags-related queries by 6-7x. -+ More tags-related predicates and aliases: - - For inherited tags: =tags-inherited=, =inherited-tags=, =tags-i=, =itags=. - - For heading-local tags: =tags-local=, =local-tags=, =tags-l=, =ltags=. - - =tags-all=, =tags&=: Matches all given tags using boolean =AND= (rather than boolean =OR=, which the =tags= predicate uses). -+ Variable =org-ql-block-header=, which overrides the default header in =org-ql-block= agenda blocks. -+ Predicate =(path)=. -+ Option =org-ql-views= may now be customized in a guided, structured way with the customization UI (e.g. =M-x customize-option RET org-ql-views RET=, or press =c= in the =org-ql-view-sidebar= buffer). -+ Enable more Org Agenda commands in =org-ql-view= buffers (e.g. setting deadlines and scheduling). (Fixes [[https://github.com/alphapapa/org-ql/issues/35][#35]]. Thanks to [[https://github.com/mz-pdm][Milan Zamazal]] and [[https://github.com/mskorzhinskiy][Mikhail Skorzhinskii]].) -+ Function =org-ql-select='s =buffers-files= argument can be a function which returns a list of buffers and/or files. - -*Changed* -+ Predicate =heading= now accepts multiple regexps, which are matched with boolean =AND=. -+ Predicate =regexp= now matches its regexp arguments with boolean =AND=. -+ Package =org-super-agenda= is now a dependency. This removes the need for awkward code to handle the case where it's not installed, and makes grouping features always available. Of course, the global minor mode =org-super-agenda-mode= is not activated by =org-ql=, so no behavior is changed in Org Agenda or =org-ql=; it only means that commands like =org-ql-search= will always provide grouping when called with the appropriate arguments. - -*Removed* -+ Macro =org-ql-agenda=. Instead, use function =org-ql-search=. See also command =org-ql-view=, etc. - -*Fixed* -+ Predicate =heading= now matches only against heading text, i.e. not including tags at the end of the line, to-do keyword, etc. -+ Predicate =todo= now matches case-sensitively, avoiding non-todo-keyword matches (e.g. a heading which begins =Waiting on= will no longer match for a todo keyword =WAITING=). -+ Interactive completion in =org-ql-search=. - -*Internal* -+ Refactored code from file =org-ql-agenda.el= into files =org-ql-search.el= and =org-ql-view.el=. Function and variable names have been changed accordingly. - -** 0.2.3 - -*Fixed* -+ Priority queries could fail to match headings whose to-do keywords had non-alphabetic characters, like =TO-READ=. - -** 0.2.2 - -*Fixed* -+ =(deadline auto)= selector matched entries whose deadlines had a warning period that had not yet been entered (=org-deadline-warning-days= too soon). - -** 0.2.1 - -*Fixed* -+ =(descendants)= selector matched against parent heading instead of only descendants. - -** 0.2 -:PROPERTIES: -:ID: 67be09f9-e959-4333-9be2-93ad8f458fbe -:END: - -*Added* -+ Function ~org-ql-query~, like ~org-ql-select~ but with arguments named more like a SQL query. -+ Bare strings like ~"string"~ can be used in queries, which are converted to ~(regexp "string")~ automatically. -+ Selector ~(regexp)~ accepts multiple regexps to test. -+ Macro ~org-ql~ and functions ~org-ql-query~ and ~org-ql-select~ now also accept a comparator function in their ~:sort~ argument. -+ Function ~org-ql-block~, which works as an Org Agenda series/composite/block command, usable in custom agenda commands defined in variable ~org-agenda-custom-commands~. (Inspired by [[https://github.com/pestctrl/emacs-config/blob/84c557982a860e86d6f67976a82ea776a7bd2c7a/config-org-new.org#my-own-agenda-renderer][Benson Chu's config]].) -+ Function ~org-ql-agenda--agenda~ optionally takes a list of entries as an argument. -+ Selectors ~ts-a~ and ~ts-i~, aliases for ~ts-active~ and ~ts-inactive~. -+ Selector ~ts~ now accepts a ~:type~ argument. -+ Face =org-ql-agenda-due-date=. -+ Selectors ~(children)~ and ~(descendants)~. -+ Function ~org-ql-search~ and macro ~org-ql-agenda~ accept a ~:title~ argument, which is displayed in the header. -+ Command ~org-ql-search~ offers global ~org-super-agenda-groups~ in completion. -+ Customization group ~org-ql~. -+ Command ~org-ql-view~, which displays views saved to variable ~org-ql-views~, which can be saved from ~org-ql-search~ buffers with command ~org-ql-search-save~, which is bound to =C-x C-s= in view buffers. -+ Variable ~org-ql-view-map~, active in view buffers displayed by ~org-ql-search~, ~org-ql-agenda~, and ~org-ql-view~. -+ =random= sort method. -+ Save position when refreshing search buffers. - -*Changed* -+ Function ~org-ql-query~ renamed to ~org-ql-select~. ~org-ql-query~ now refers to a new function. -+ Macro ~org-ql~ no longer accepts a ~:markers~ argument. Instead, use argument ~:action element-with-markers~. See function ~org-ql-select~, which ~org-ql~ calls. -+ Selector ~(todo)~ no longer matches "done" keywords when used without arguments (i.e. the ones in variable ~org-done-keywords~). -+ Overhauled date/time-based predicates. See documentation for new argument signatures. - -*Removed* -+ Selector ~(date)~, replaced by ~(ts)~. - -*Fixed* -+ Handle date ranges in date-based selectors. (Thanks to [[https://github.com/codygman][Cody Goodman]], [[https://github.com/swflint][Samuel W. Flint]], and [[https://github.com/vikasrawal][Vikas Rawal]].) -+ Don't overwrite bindings in =org-agenda-mode-map=. -+ Don't search buffers without headings, and show a message if the user attempts it. -+ Don't search hidden/special buffers. -+ Properly accept arbitrary sort functions in =org-ql-select=, etc. (Fixes [[https://github.com/alphapapa/org-ql/issues/37][#37]]. Thanks to [[https://github.com/mz-pdm][Milan Zamazal]].) -+ Planning-line-related predicates searched too far into entries. -+ Add autoloads. (Fixes [[https://github.com/alphapapa/org-ql/pull/36/files#][#36]]. Thanks to [[https://github.com/akirak][Akira Komamura]].) - -*Compatibility* -+ Fixes for compatibility with Org 9.2. (Thanks to [[https://github.com/ataias][Ataias Pereira Reis]] and [[https://github.com/dakra][Daniel Kraus]].) - -*Internal* -+ Optimizations for some query selectors, e.g. =regexp= and =todo=. These can provide a significant improvement for some queries. See benchmarks in [[file:notes.org][notes.org]]. -+ Library [[https://github.com/alphapapa/ts.el][ts]] is now used for parsing and comparing timestamps. - -** 0.1 - -First tagged release. - -* Development - -Bug reports, feature requests, and suggestions are welcome. For patches, see below. - -** Copyright assignment - -While Org QL is currently distributed in MELPA, it's [[https://github.com/alphapapa/org-ql/issues/409][intended]] to merge Org QL into Org mode. When that happens, it will become a part of Emacs and Org, and therefore cumulative contributions of more than 15 lines of code will require that the author assign copyright of such contributions to the FSF. Authors who are interested in doing so may contact [[mailto:assign@gnu.org][assign@gnu.org]] to request the appropriate form. - -* Notes -:PROPERTIES: -:TOC: :ignore this -:END: - -** Comparison with Org Agenda searches - -Of course, queries like these can already be written with Org Agenda searches, but the syntax can be complex. For example, this query would be difficult to write in a standard Org Agenda search, because it matches against a to-do keyword /and/ a plain-text search. As described in the [[https://orgmode.org/worg/org-tutorials/advanced-searching.html#combining-metadata-and-full-text-queries][advanced searching tutorial]], it would require using ~org-search-view~ with a query with specific regular expression syntax, like this: - -#+BEGIN_EXAMPLE - +lisp +{^\*+\s-+TO-READ\s-} -#+END_EXAMPLE - -But with =org-ql-search=, you would write a query like =lisp todo:TO-READ=, or in Lisp syntax, ~(and "lisp" (todo "TO-READ"))~. - -** org-sidebar - -This package is used by [[https://github.com/alphapapa/org-sidebar][org-sidebar]], which presents a customizable agenda-like view in a sidebar window. - -* License -:PROPERTIES: -:TOC: :ignore this -:END: - -GPLv3 - -* COMMENT Code :noexport: -:PROPERTIES: -:TOC: :ignore this -:END: - -# The COMMENT keyword prevents GitHub's renderer from showing this entry. - -Code used to update this document. - -** Predicates - -Generates the predicate subtree. - -#+BEGIN_SRC elisp :results silent :exports code - (defun org-ql--readme-update-predicates () - "Update predicate subtree in current document." - (interactive) - (org-ql--readme-replace-node '("Usage" "Queries" "Predicates") (org-ql--readme-predicate-list))) - - (defun org-ql--readme-replace-node (outline-path string) - "Replace contents of node at OUTLINE-PATH with STRING." - (org-with-wide-buffer - (-let* ((subtree-marker (org-find-olp outline-path t)) - ((_headline element) (progn - (goto-char subtree-marker) - (org-element-headline-parser (point-max)))) - ((&plist :contents-begin beg :contents-end end) element)) - (goto-char beg) - (delete-region (point) (1- end)) - (insert string "\n")))) - - (defun org-ql--readme-predicate-list () - "Return an Org list string documenting predicates." - (concat (unpackaged/docstring-to-org - "Arguments are listed next to predicate names, where applicable. - - Note that, for convenience, standard numeric comparator function symbols (`<', `=', etc.) do not need to be quoted when passed as an argument to these predicates. The resemblance to infix notation is coincidental. See examples in documentation.\n\n") - (s-join "\n" (->> org-ql-predicates - (--sort (string< (symbol-name (plist-get it :name)) - (symbol-name (plist-get other :name)))) - (--map (-let* (((&plist :name name :docstring docstring :fn fn :args args) it) - (args (->> args - (--replace-where (listp it) (car it)) - (--replace-where (eq '&rest it) '&optional)))) - (if docstring - (progn - (setq docstring (s-replace "\n" " " docstring)) - (format "+ ~%s%s~ :: %s" name - (if args - (format " %s" args) - "") - (unpackaged/docstring-to-org docstring))) - (when (s-prefix? "org-ql-" (symbol-name name)) - (warn "No docstring for: %s" name)) - nil))) - -non-nil)))) -#+END_SRC - -*** TODO Use async - -If ~org-ql~ is loaded byte-compiled, the argument lists are not named properly (not sure why, as ~help-function-arglist~ is supposed to handle that). We could run the function in another Emacs process with ~async~ to avoid this. - -* COMMENT Export setup :noexport: -:PROPERTIES: -:TOC: :ignore this -:END: - -# Copied from org-super-agenda's readme, in which much was borrowed from Org's =org-manual.org=. - -#+OPTIONS: broken-links:t *:t - -** Info export options - -#+TEXINFO_DIR_CATEGORY: Emacs -#+TEXINFO_DIR_TITLE: Org QL: (org-ql) -#+TEXINFO_DIR_DESC: Query language, search commands, and saved views for Org files - -# NOTE: We could use these, but that causes a pointless error, "org-compile-file: File "..README.info" wasn't produced...", so we just rename the files in the after-save-hook instead. -# #+TEXINFO_FILENAME: org-ql.info -# #+EXPORT_FILE_NAME: org-ql.texi - -** File-local variables - -# NOTE: Setting org-comment-string buffer-locally is a nasty hack to work around GitHub's org-ruby's HTML rendering, which does not respect noexport tags. The only way to hide this tree from its output is to use the COMMENT keyword, but that prevents Org from processing the export options declared in it. So since these file-local variables don't affect org-ruby, wet set org-comment-string to an unused keyword, which prevents Org from deleting this tree from the export buffer, which allows it to find the export options in it. And since org-export does respect the noexport tag, the tree is excluded from the info page. - -# Local Variables: -# before-save-hook: org-make-toc -# after-save-hook: (lambda nil (when (and (require 'ox-texinfo nil t) (org-texinfo-export-to-info)) (delete-file "README.texi") (rename-file "README.info" "org-ql.info" t))) -# org-export-initial-scope: buffer -# org-comment-string: "NOTCOMMENT" -# End: diff --git a/examples.org b/examples.org deleted file mode 100644 index 1c5ef9d..0000000 --- a/examples.org +++ /dev/null @@ -1,257 +0,0 @@ -#+TITLE: org-ql examples - -* Contents -:PROPERTIES: -: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]] -- [[#listing-bills-coming-due][Listing bills coming due]] -- [[#music-database][Music database]] -- [[#return-org-elements][Return Org elements]] -- [[#set-tags-on-certain-entries][Set tags on certain entries]] -- [[#show-entries-with-recent-timestamps][Show entries with recent timestamps]] -- [[#stuck-projects-block-agenda][Stuck projects block agenda]] -- [[#subproject-and-subtask-queries][Subproject and subtask queries]] -- [[#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. - -#+BEGIN_SRC elisp - (org-ql-search (org-agenda-files) - '(or (and (not (done)) - (or (habit) - (deadline auto) - (scheduled :to today) - (ts-active :on today))) - (closed :on today)) - :sort '(todo priority date)) -#+END_SRC - -Another example, showing grouping with [[https://github.com/alphapapa/org-super-agenda][org-super-agenda]]: - -#+BEGIN_SRC elisp - (org-ql-search "~/src/emacs/org-super-agenda/test/test.org" - '(and (or (ts-active :on today) - (deadline auto) - (scheduled :to today)) - (not (done))) - :title "My Agenda View" - ;; The `org-super-agenda-groups' setting is used automatically when set, or it - ;; may be overriden by specifying it here: - :super-groups '((:name "Bills" - :tag "bills") - (:todo ("SOMEDAY" "TO-READ" "CHECK" "TO-WATCH" "WATCHING") - :order 7) - (:name "Personal" - :habit t - :tag "personal" - :order 3) - (:todo "WAITING" - :order 6) - (:priority "A" :order 1) - (:priority "B" :order 2) - (:priority "C" :order 2))) -#+END_SRC - -Which displays this buffer: - -[[images/screenshot.png]] - -* Entries from the past week - -Show entries that have any timestamp within the past week. Group by date using =org-super-agenda= with the =:auto-ts= group. - -#+BEGIN_SRC elisp - (org-ql-search (org-agenda-files) - '(ts :from -7 :to today) - :title "Recent Items" - :sort '(todo priority date) - :super-groups '((:auto-ts t))) -#+END_SRC - -* Find entries matching a certain =CUSTOM_ID= - -Since queries can contain both built-in =org-ql= predicate expressions and arbitrary expressions, they can be combined in useful ways. This example uses the built-in =property= predicate to quickly locate entries that have the =CUSTOM_ID= property set, and then compares the value of that property to the string =issue=. - -#+BEGIN_SRC elisp - (org-ql-query - :select #'org-get-heading - :from "~/org/tickets.org" - :where '(and (property "CUSTOM_ID") - (string-match "issue" (org-entry-get (point) "CUSTOM_ID")))) -#+END_SRC - -Using the =property= predicate as the first clause of the two clauses joined with =and= allows =org-ql= to optimize the query by searching through the buffer directly to entries that set the =CUSTOM_ID= property, which is much faster than testing every entry in a buffer. Also, If the query were only the =string-match= call, it would signal an error on entries that didn't have the property set, because =org-entry-get= would return nil. - -* Listing bills coming due - -This uses the example in the readme file, but maps across the elements returned by ~org-ql~ to present a simple list of titles and deadlines. - -#+BEGIN_SRC elisp - (org-ql-query - :select '(list (substring-no-properties (org-get-heading t t)) - (org-entry-get (point) "DEADLINE")) - :from (org-agenda-files) - :where '(and (not (done)) - (tags "bills") - (deadline auto)) - :order-by 'deadline) - ;;=> (("Electric bill" "<2018-08-23 Thu +1m>") - ;; ("Rent" "<2018-09-01 Sat +1m>")) -#+END_SRC - -This could also be put in a script, which could use desktop notifications to remind of bills coming due: [[examples/org-bills-due.el][org-bills-due.el]]. - -* Music database - - If you kept a database of music in an Org file, you could run a query like this to find tracks composed by Chopin that do not have their key recorded in the database. - -#+BEGIN_SRC elisp - (org-ql-search "~/org/music.org" - '(and (property "genre" "classical") - (property "composer" "Chopin") - (not (property "key")))) -#+END_SRC - -* Return Org elements - -Return a list of Org entry elements in the file =~/org/main.org= which have the =SOMEDAY= to-do keyword, are tagged =Emacs=, and have priority B or higher. =org-ql= is the macro version of =org-ql-select=; it does not require quoting the query sexp. - -#+BEGIN_SRC elisp - (org-ql "~/org/main.org" - (and (todo "SOMEDAY") - (tags "Emacs") - (priority >= "B"))) - ;;=> ((headline (:raw-value "org-board" :begin 1220270 :end 1220403 ...)) ...) -#+END_SRC - -* Set tags on certain entries - -Set the tag =Emacs= on every entry in the inbox file that mentions =Emacs=. The bare-string query ="Emacs"= is equivalent to ~(regexp "Emacs")~. - -#+BEGIN_SRC elisp - (org-ql-select "~/org/inbox.org" - "Emacs" - :action '(org-toggle-tag "Emacs" 'on)) -#+END_SRC - -* Show entries with recent timestamps - -You can also access these views with the command ~org-ql-view~. - -#+BEGIN_SRC elisp - ;; Show entries with any timestamp from last 7 days: - (org-ql-view-recent-items 7) - - ;; Show entries clocked in last 30 days: - (org-ql-view-recent-items 30 'clocked) - - ;; Show entries closed in last 30 days: - (org-ql-view-recent-items 30 'closed) -#+END_SRC - -* Stuck projects block agenda - -Reddit user =emptymatrix= [[https://www.reddit.com/r/emacs/comments/cnrt2d/orgqlblock_integrates_orgql_into_org_agenda/ewtqez8/][shared]] this example of replacing a traditional =org-stuck-projects= view like: - -#+BEGIN_SRC elisp - (setq org-stuck-projects - '("+@project/-DONE" ("NEXT") nil "SCHEDULED:")) -#+END_SRC - -With this =org-ql-block= agenda view, like: - -#+BEGIN_SRC elisp - (setq org-agenda-custom-commands - '(("s" "Stuck Projects" - ((org-ql-block '(and (tags "@project") - (not (done)) - (not (descendants (todo "NEXT"))) - (not (descendants (scheduled)))) - ((org-ql-block-header "Stuck Projects"))))))) -#+END_SRC - -* Subproject and subtask queries - -#+BEGIN_SRC elisp - ;; Search for subprojects. - (org-ql-search (org-agenda-files) - '(and (todo "PROJECT") - (ancestors (todo "PROJECT")))) - - ;; Search for all subtasks of projects, grouped by parent heading. - (org-ql-search (org-agenda-files) - '(and (todo) - (ancestors (todo "PROJECT"))) - :super-groups '((:auto-parent t))) - - ;; Search for direct top-level tasks of projects. - (org-ql-search (org-agenda-files) - '(and (todo) - (parent (todo "PROJECT"))) - :super-groups '((:auto-parent t))) -#+END_SRC - -Of course, all of those presume using a =PROJECT= keyword to define projects. If one defines a project as any task which has an ancestor task, one could use queries like: - -#+BEGIN_SRC elisp - ;; Search for all subtasks of top-level projects, grouped by parent heading. - (org-ql-search (org-agenda-files) - '(and (todo) - (ancestors - (and (todo) - (not (parent))))) - :super-groups '((:auto-parent t))) - - ;; Search for all subtasks of all projects, including subprojects, grouped by project. - (org-ql-search (org-agenda-files) - '(and (todo) - (ancestors (todo))) - :super-groups '((:auto-parent t))) -#+END_SRC - -Other interesting queries: - -#+BEGIN_SRC elisp - ;; Subtasks of upcoming deadline items. - (org-ql-search (org-agenda-files) - '(and (todo) - (ancestors - (and (not (done)) - (deadline auto)))) - :super-groups '((:auto-parent t))) - - ;; TODO items whose ancestor is already DONE, and should therefore be - ;; either marked DONE or CANCELLED. - (org-ql-search (org-agenda-files) - '(and (todo) - (ancestors (done))) - :super-groups '((:auto-parent t))) -#+END_SRC - -* Task list for files in subdirectories - -[[images/org-ql-search-snippet.png]] - -* COMMENT Code :noexport: -:PROPERTIES: -:TOC: :ignore (this descendants) -:END: - -** File-local variables - -# Local Variables: -# eval: (require 'org-make-toc) -# before-save-hook: org-make-toc -# End: diff --git a/examples/defpred.html b/examples/defpred.html deleted file mode 100644 index 9c9b57e..0000000 --- a/examples/defpred.html +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - -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 deleted file mode 100644 index ed26973..0000000 --- a/examples/defpred.org +++ /dev/null @@ -1,390 +0,0 @@ -#+TITLE: Org QL Custom Predicates Tutorial -#+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. - -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 :noexport: -: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]] -- [[#be-formless][Be formless]] -- [[#conclusion][Conclusion]] -- [[#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 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 - -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) - :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 name)))) -#+END_SRC - -/NOTE: Normalizers are applied to a query repeatedly until the query is fully normalized, so normalizers should be carefully written to avoid infinite loops. In this example, there is no risk of an infinite loop, because the normalized query no longer contains the ~person~ predicate, so the normalizer only applies to the query once./ - -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 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 - -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]] -#+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 - -Which shows: - -#+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 - -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: - [#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.) - -* 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: - -#+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))))) -#+END_SRC - -Let's make sure it normalizes 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 - -It does, so the query also produces the correct results: - -#+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 - -Lisp is fun! - -* 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/examples/export/setup/theme-darksun-local.setup b/examples/export/setup/theme-darksun-local.setup deleted file mode 100644 index 1ae015b..0000000 --- a/examples/export/setup/theme-darksun-local.setup +++ /dev/null @@ -1,17 +0,0 @@ -# -*- 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 deleted file mode 100644 index a5abe0e..0000000 --- a/examples/export/setup/theme-darksun.setup +++ /dev/null @@ -1,15 +0,0 @@ -# -*- 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 deleted file mode 100644 index 212fb9a..0000000 --- a/examples/export/styles/darksun/css/darksun.css +++ /dev/null @@ -1,264 +0,0 @@ -* { - 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 deleted file mode 100644 index 4ded9b5..0000000 --- a/examples/export/styles/darksun/css/hideshow.css +++ /dev/null @@ -1,110 +0,0 @@ -/* 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 deleted file mode 100644 index f08e781..0000000 --- a/examples/export/styles/darksun/css/htmlize.css +++ /dev/null @@ -1,145 +0,0 @@ -.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 deleted file mode 100644 index ebdc03b..0000000 Binary files a/examples/export/styles/darksun/images/checked.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/fixme.png b/examples/export/styles/darksun/images/fixme.png deleted file mode 100644 index 5b861d8..0000000 Binary files a/examples/export/styles/darksun/images/fixme.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/halfchecked.png b/examples/export/styles/darksun/images/halfchecked.png deleted file mode 100644 index afd695e..0000000 Binary files a/examples/export/styles/darksun/images/halfchecked.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/info.png b/examples/export/styles/darksun/images/info.png deleted file mode 100644 index 83de654..0000000 Binary files a/examples/export/styles/darksun/images/info.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/note.png b/examples/export/styles/darksun/images/note.png deleted file mode 100644 index 1c6b8eb..0000000 Binary files a/examples/export/styles/darksun/images/note.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/tip.png b/examples/export/styles/darksun/images/tip.png deleted file mode 100644 index 743ef89..0000000 Binary files a/examples/export/styles/darksun/images/tip.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/unchecked.png b/examples/export/styles/darksun/images/unchecked.png deleted file mode 100644 index b75b7a9..0000000 Binary files a/examples/export/styles/darksun/images/unchecked.png and /dev/null differ diff --git a/examples/export/styles/darksun/images/warning.png b/examples/export/styles/darksun/images/warning.png deleted file mode 100644 index 296415e..0000000 Binary files a/examples/export/styles/darksun/images/warning.png and /dev/null differ diff --git a/examples/export/styles/darksun/js/darksun.js b/examples/export/styles/darksun/js/darksun.js deleted file mode 100644 index 99ce23f..0000000 --- a/examples/export/styles/darksun/js/darksun.js +++ /dev/null @@ -1,524 +0,0 @@ -// 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 deleted file mode 100644 index e528759..0000000 --- a/examples/export/styles/darksun/js/hideshow.js +++ /dev/null @@ -1,335 +0,0 @@ -// 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 deleted file mode 100644 index 73f33fb..0000000 --- a/examples/export/styles/darksun/js/jquery-1.11.0.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! 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("