pax_global_header00006660000000000000000000000064122604110750014510gustar00rootroot0000000000000052 comment=0c097d9f6ed4cfaa43b0a7d4a55e7d0b8a795ac9 notmuch-0.17/000077500000000000000000000000001226041107500131145ustar00rootroot00000000000000notmuch-0.17/.dir-locals.el000066400000000000000000000010431226041107500155430ustar00rootroot00000000000000;; emacs local configuration settings for notmuch source ;; surmised by dkg on 2010-11-23 13:43:18-0500 ;; amended by amdragon on 2011-06-06 ((c-mode (indent-tabs-mode . t) (tab-width . 8) (c-basic-offset . 4) (c-file-style . "linux")) (c++-mode (indent-tabs-mode . t) (tab-width . 8) (c-basic-offset . 4) (c-file-style . "linux")) (emacs-lisp-mode (indent-tabs-mode . t) (tab-width . 8)) (shell-mode (indent-tabs-mode . t) (tab-width . 8) (sh-basic-offset . 4) (sh-indentation . 4)) (nil (fill-column . 70)) ) notmuch-0.17/.gitignore000066400000000000000000000002351226041107500151040ustar00rootroot00000000000000.first-build-message Makefile.config TAGS tags *cscope* .deps /notmuch notmuch.sym notmuch-shared libnotmuch.so* libnotmuch*.dylib *.[ao] *~ .*.swp releases notmuch-0.17/AUTHORS000066400000000000000000000023331226041107500141650ustar00rootroot00000000000000Carl Worth is the primary author of Notmuch. But there's really not much that he's done. There's been a lot of standing on shoulders here: William Morgan deserves credit for providing the primary inspiration for Notmuch with his program Sup (http://sup.rubyforge.org/). Some people have contributed code that has made it into Notmuch without their specific knowledge (but with their full permission thanks to the GNU General Public License). This includes: Brian Gladman (with Mikhail Gusarov ) Implementation of SHA-1 (nice and small) (libsha1.c) Please see the various files in the Notmuch distribution for individual copyright statements. And of course, though their code isn't distributed here, Notmuch would be not much of anything without the contributors to Xapian, the search engine that does the really heavy lifting, as well as the various system libraries, compilers, and the kernel that make it all work (thanks GNU, thanks Linux). Thanks to everyone who has played a part! Here is an incomplete list of other people that have made contributions to Notmuch (whether by code, bug reporting/fixes, ideas, inspiration, testing or feedback): Martin Krafft Keith Packard Jamey Sharp notmuch-0.17/COPYING000066400000000000000000000012141226041107500141450ustar00rootroot00000000000000Notmuch 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, (in the COPYING-GPL-3 file in this directory). If not, see http://www.gnu.org/licenses/ notmuch-0.17/COPYING-GPL-3000066400000000000000000001043741226041107500147400ustar00rootroot00000000000000 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 . notmuch-0.17/INSTALL000066400000000000000000000044561226041107500141560ustar00rootroot00000000000000Build and install instructions for Notmuch. Compilation commands -------------------- The process for compiling and installing Notmuch is the very standard sequence of: ./configure make sudo make install In fact, if you don't plan to pass any arguments to the configure script, then you can skip that step and just start with "make", (which will call configure for you). See this command: ./configure --help for detailed documentation of the things you can control at the configure stage. Dependencies ------------ Notmuch depends on three libraries: Xapian, GMime 2.4 or 2.6, and Talloc which are each described below: Xapian ------ Xapian is the search-engine library underlying Notmuch. It provides all the real machinery of indexing and searching, (including the very nice parsing of the query string). Xapian is available from http://xapian.org Note: Notmuch will work best with Xapian 1.0.18 (or later) or Xapian 1.1.4 (or later). Previous versions of Xapian (whether 1.0 or 1.1) had a performance bug that made notmuch very slow when modifying tags. This would cause distracting pauses when reading mail while notmuch would wait for Xapian when removing the "inbox" and "unread" tags from messages in a thread. GMime 2.4 or 2.6 ---------------- GMime provides decoding of MIME email messages for Notmuch. Without GMime, Notmuch would not be able to extract and index the actual text from email message encoded as BASE64, etc. GMime is available from http://spruce.sourceforge.net/gmime/ Talloc ------ Talloc is a memory-pool allocator used by Notmuch. Talloc is an extremely lightweight and easy-to-use tool for allocating memory in a hierarchical fashion and then freeing it with a single call of the top-level handle. Using it has made development of Notmuch much easier and much less prone to memory leaks. Talloc is available from http://talloc.samba.org/ On a modern, package-based operating system you can install all of the dependencies with a simple simple command line. For example: For Debian and similar: sudo apt-get install libxapian-dev libgmime-2.6-dev libtalloc-dev For Fedora and similar: sudo yum install xapian-core-devel gmime-devel libtalloc-devel On other systems, a similar command can be used, but the details of the package names may be different. notmuch-0.17/Makefile000066400000000000000000000031441226041107500145560ustar00rootroot00000000000000# We want the all target to be the implicit target (if no target is # given explicitly on the command line) so mention it first. all: # List all subdirectories here. Each contains its own Makefile.local. # Use of '=', without '+=', seems to be required for out-of-tree # builds to work. subdirs = compat completion emacs lib man parse-time-string performance-test util test # We make all targets depend on the Makefiles themselves. global_deps = Makefile Makefile.config Makefile.local \ $(subdirs:%=%/Makefile) $(subdirs:%=%/Makefile.local) # Sub-directory Makefile.local fragments can append to these variables # to have directory-specific cflags as necessary. extra_cflags := extra_cxxflags := # Get settings from the output of configure by running it to generate # Makefile.config if it doesn't exist yet. # If Makefile.config doesn't exist, then srcdir won't be # set. Conditionally set it (assuming a plain srcdir build) so that # the rule to generate Makefile.config can actually work. srcdir ?= . include Makefile.config Makefile.config: $(srcdir)/configure ifeq ($(configure_options),) @echo "" @echo "Note: Calling ./configure with no command-line arguments. This is often fine," @echo " but if you want to specify any arguments (such as an alternate prefix" @echo " into which to install), call ./configure explicitly and then make again." @echo " See \"./configure --help\" for more details." @echo "" endif $(srcdir)/configure $(configure_options) # Finally, include all of the Makefile.local fragments where all the # real work is done. include $(subdirs:%=%/Makefile.local) Makefile.local notmuch-0.17/Makefile.local000066400000000000000000000256421226041107500156560ustar00rootroot00000000000000# -*- makefile -*- # Here's the (hopefully simple) versioning scheme. # # Releases of notmuch have a two-digit version (0.1, 0.2, etc.). We # increment the second digit for each release and increment the first # digit when we reach particularly major milestones of usability. # # Between releases, (such as when compiling notmuch from the git # repository), we let git append identification of the actual commit. PACKAGE=notmuch IS_GIT=$(shell if [ -d .git ] ; then echo yes ; else echo no; fi) ifeq ($(IS_GIT),yes) DATE:=$(shell git log --date=short -1 --pretty=format:%cd) else DATE:=$(shell date +%F) endif VERSION:=$(shell cat ${srcdir}/version) ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),) ifeq ($(IS_GIT),yes) VERSION:=$(shell git describe --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/) endif endif UPSTREAM_TAG=$(subst ~,_,$(VERSION)) DEB_TAG=debian/$(UPSTREAM_TAG)-1 RELEASE_HOST=notmuchmail.org RELEASE_DIR=/srv/notmuchmail.org/www/releases RELEASE_URL=http://notmuchmail.org/releases TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz SHA1_FILE=$(TAR_FILE).sha1 GPG_FILE=$(SHA1_FILE).asc PV_FILE=bindings/python/notmuch/version.py # Smash together user's values with our extra values FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS) FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS) FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lutil -Llib -lnotmuch $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS) FINAL_NOTMUCH_LINKER = CC ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1) FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS) FINAL_NOTMUCH_LINKER = CXX endif ifeq ($(LIBDIR_IN_LDCONFIG),0) FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS) endif FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS) .PHONY: all all: notmuch notmuch-shared ifeq ($(MAKECMDGOALS),) ifeq ($(shell cat .first-build-message 2>/dev/null),) @NOTMUCH_FIRST_BUILD=1 $(MAKE) --no-print-directory all @echo "" @echo "Compilation of notmuch is now complete. You can install notmuch with:" @echo "" @echo " make install" @echo "" @echo "Note that depending on the prefix to which you are installing" @echo "you may need root permission (such as \"sudo make install\")." @echo "See \"./configure --help\" for help on setting an alternate prefix." @echo Printed > .first-build-message endif endif $(TAR_FILE): if git tag -v $(VERSION) >/dev/null 2>&1; then \ ref=$(VERSION); \ else \ ref="HEAD" ; \ echo "Warning: No signed tag for $(VERSION)"; \ fi ; \ git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ $$ref > $(TAR_FILE).tmp echo $(VERSION) > version.tmp tar --append -f $(TAR_FILE).tmp --transform s_^_$(PACKAGE)-$(VERSION)/_ --transform 's_.tmp$$__' version.tmp rm version.tmp gzip < $(TAR_FILE).tmp > $(TAR_FILE) @echo "Source is ready for release in $(TAR_FILE)" $(SHA1_FILE): $(TAR_FILE) sha1sum $^ > $@ $(GPG_FILE): $(SHA1_FILE) @echo "Please enter your GPG password to sign the checksum." gpg --armor --sign $^ .PHONY: dist dist: $(TAR_FILE) .PHONY: update-versions update-versions: update-man-versions sed -i "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" $(PV_FILE) # We invoke make recursively only to force ordering of our phony # targets in the case of parallel invocation of make (-j). # # We carefully ensure that our VERSION variable is passed down to any # sub-ordinate make invocations (which won't otherwise know that they # are part of the release and need to take the version from the # version file). .PHONY: release release: verify-source-tree-and-version $(MAKE) VERSION=$(VERSION) verify-newer $(MAKE) VERSION=$(VERSION) clean $(MAKE) VERSION=$(VERSION) test git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG) $(MAKE) VERSION=$(VERSION) $(GPG_FILE) ln -sf $(TAR_FILE) $(DEB_TAR_FILE) pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG) git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG) mkdir -p releases mv $(TAR_FILE) $(SHA1_FILE) $(GPG_FILE) releases $(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce ifeq ($(REALLY_UPLOAD),yes) git push origin $(VERSION) cd releases && scp $(TAR_FILE) $(SHA1_FILE) $(GPG_FILE) $(RELEASE_HOST):$(RELEASE_DIR) ssh $(RELEASE_HOST) "rm -f $(RELEASE_DIR)/LATEST-$(PACKAGE)-* ; ln -s $(TAR_FILE) $(RELEASE_DIR)/LATEST-$(TAR_FILE)" endif @echo "Please send a release announcement using $(PACKAGE)-$(VERSION).announce as a template." .PHONY: pre-release pre-release: $(MAKE) VERSION=$(VERSION) clean $(MAKE) VERSION=$(VERSION) test git tag -s -m "$(PACKAGE) $(VERSION) release" $(UPSTREAM_TAG) git tag -s -m "$(PACKAGE) Debian $(VERSION)-1 upload (same as $(VERSION))" $(DEB_TAG) $(MAKE) VERSION=$(VERSION) $(TAR_FILE) ln -sf $(TAR_FILE) $(DEB_TAR_FILE) pristine-tar commit $(DEB_TAR_FILE) $(UPSTREAM_TAG) mkdir -p releases mv $(TAR_FILE) $(DEB_TAR_FILE) releases .PHONY: debian-snapshot debian-snapshot: make VERSION=$(VERSION) clean TMPFILE=$$(mktemp /tmp/notmuch.XXXXXX); \ cp debian/changelog $${TMPFILE}; \ EDITOR=/bin/true dch -b -v $(VERSION)+1 \ -D UNRELEASED 'test build, not for upload'; \ echo '3.0 (native)' > debian/source/format; \ debuild -us -uc; \ mv -f $${TMPFILE} debian/changelog; \ echo '3.0 (quilt)' > debian/source/format .PHONY: release-message release-message: @echo "To: notmuch@notmuchmail.org" @echo "Subject: $(PACKAGE) release $(VERSION) now available" @echo "" @echo "Where to obtain notmuch $(VERSION)" @echo "===========================" @echo " $(RELEASE_URL)/$(TAR_FILE)" @echo "" @echo "Which can be verified with:" @echo "" @echo " $(RELEASE_URL)/$(SHA1_FILE)" @echo -n " " @cat releases/$(SHA1_FILE) @echo "" @echo " $(RELEASE_URL)/$(GPG_FILE)" @echo " (signed by `getent passwd "$$USER" | cut -d: -f 5 | cut -d, -f 1`)" @echo "" @echo "What's new in notmuch $(VERSION)" @echo "=========================" @sed -ne '/^[Nn]otmuch $(VERSION)/{n;n;b NEWS}; d; :NEWS /^===/q; {p;n;b NEWS}' < NEWS | head -n -2 @echo "" @echo "What is notmuch" @echo "===============" @echo "Notmuch is a system for indexing, searching, reading, and tagging" @echo "large collections of email messages in maildir or mh format. It uses" @echo "the Xapian library to provide fast, full-text search with a convenient" @echo "search syntax." @echo "" @echo "For more about notmuch, see http://notmuchmail.org" # This is a chain of dependencies rather than a simple list simply to # avoid the messages getting interleaved in the case of a parallel # make invocation. .PHONY: verify-source-tree-and-version verify-source-tree-and-version: verify-no-dirty-code .PHONY: verify-no-dirty-code verify-no-dirty-code: release-checks ifeq ($(IS_GIT),yes) @printf "Checking that source tree is clean..." ifneq ($(shell git ls-files -m),) @echo "No" @echo "The following files have been modified since the most recent git commit:" @echo "" @git ls-files -m @echo "" @echo "The release will be made from the committed state, but perhaps you meant" @echo "to commit this code first? Please clean this up to make it more clear." @false else @echo "Good" endif endif .PHONY: release-checks release-checks: devel/release-checks.sh .PHONY: verify-newer verify-newer: @echo -n "Checking that no $(VERSION) release already exists..." @wget -q -O /dev/null $(RELEASE_URL)/$(TAR_FILE) ; \ case $$? in \ 8) echo "Good." ;; \ 0) echo "Ouch."; \ echo "Found: $(RELEASE_URL)/$(TAR_FILE)"; \ echo "Refusing to replace an existing release."; \ echo "Don't forget to update \"version\" as described in RELEASING before release." ; \ false ;; \ *) echo "An unexpected error occured"; \ false;; esac # The user has not set any verbosity, default to quiet mode and inform the # user how to enable verbose compiles. ifeq ($(V),) quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n" quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//')) endif # The user has explicitly enabled quiet compilation. ifeq ($(V),0) quiet = @printf "$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//')) endif # Otherwise, print the full command line. quiet ?= $($(shell echo $1 | sed -e s'/ .*//')) %.o: %.cc $(global_deps) @mkdir -p .deps/$(@D) $(call quiet,CXX $(CPPFLAGS) $(CXXFLAGS)) -c $(FINAL_CXXFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d %.o: %.c $(global_deps) @mkdir -p .deps/$(@D) $(call quiet,CC $(CPPFLAGS) $(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d .PHONY : clean clean: rm -rf $(CLEAN); rm -rf .deps .PHONY: distclean distclean: clean rm -rf $(DISTCLEAN) notmuch_client_srcs = \ command-line-arguments.c\ debugger.c \ gmime-filter-reply.c \ hooks.c \ notmuch.c \ notmuch-compact.c \ notmuch-config.c \ notmuch-count.c \ notmuch-dump.c \ notmuch-insert.c \ notmuch-new.c \ notmuch-reply.c \ notmuch-restore.c \ notmuch-search.c \ notmuch-setup.c \ notmuch-show.c \ notmuch-tag.c \ notmuch-time.c \ sprinter-json.c \ sprinter-sexp.c \ sprinter-text.c \ query-string.c \ mime-node.c \ crypto.c \ tag-util.c notmuch_client_modules = $(notmuch_client_srcs:.c=.o) notmuch: $(notmuch_client_modules) lib/libnotmuch.a util/libutil.a parse-time-string/libparse-time-string.a $(call quiet,CXX $(CFLAGS)) $^ $(FINAL_LIBNOTMUCH_LDFLAGS) -o $@ notmuch-shared: $(notmuch_client_modules) lib/$(LINKER_NAME) $(call quiet,$(FINAL_NOTMUCH_LINKER) $(CFLAGS)) $(notmuch_client_modules) $(FINAL_NOTMUCH_LDFLAGS) -o $@ .PHONY: install install: all install-man mkdir -p "$(DESTDIR)$(prefix)/bin/" install notmuch-shared "$(DESTDIR)$(prefix)/bin/notmuch" ifeq ($(MAKECMDGOALS), install) @echo "" @echo "Notmuch is now installed to $(DESTDIR)$(prefix)" @echo "" @echo "New users should simply run \"notmuch\" to be guided" @echo "through the process of configuring notmuch and creating" @echo "a database of existing email messages. The \"notmuch\"" @echo "command will also offer some sample search commands." ifeq ($(WITH_EMACS), 1) @echo "" @echo "Beyond the command-line interface, notmuch also offers" @echo "a full-featured interface for reading and writing mail" @echo "within emacs. To use this, each user should add the" @echo "following line to the ~/.emacs file:" @echo "" @echo " (require 'notmuch)" @echo "" @echo "And then run emacs as \"emacs -f notmuch\" or invoke" @echo "the command \"M-x notmuch\" from within emacs." endif endif .PHONY: install-desktop install-desktop: mkdir -p "$(DESTDIR)$(desktop_dir)" desktop-file-install --mode 0644 --dir "$(DESTDIR)$(desktop_dir)" notmuch.desktop SRCS := $(SRCS) $(notmuch_client_srcs) CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules) notmuch.elc DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config DEPS := $(SRCS:%.c=.deps/%.d) DEPS := $(DEPS:%.cc=.deps/%.d) -include $(DEPS) notmuch-0.17/NEWS000066400000000000000000002541561226041107500136300ustar00rootroot00000000000000Notmuch 0.17 (2013-12-30) ========================= Incompatible change in SHA1 computation --------------------------------------- Previously on big endian architectures like sparc and powerpc the computation of SHA1 hashes was incorrect. This meant that messages with overlong or missing message-ids were given different computed message-ids than on more common little endian architectures like i386 and amd64. If you use notmuch on a big endian architecture, you are strongly advised to make a backup of your tags using `notmuch dump` before this upgrade. You can locate the affected files using something like: notmuch dump | \ awk '/^notmuch-sha1-[0-9a-f]{40} / \ {system("notmuch search --exclude=false --output=files id:" $1)}' Command-Line Interface ---------------------- New options to better support handling duplicate messages If more than one message file is associated with a message-id, `notmuch search --output=files` will print all of them. A new `--duplicate=N` option can be used to specify which duplicate to print for each message. `notmuch count` now supports `--output=files` option to output the number of files associated with matching messages. This may be bigger than the number of matching messages due to duplicates (i.e. multiple files having the same message-id). Improved `notmuch new` performance for unchanged folders `notmuch new` now skips over unchanged folders more efficiently, which can substantially improve the performance of checking for new mail in some situations (like NFS-mounted Maildirs). `notmuch reply --format=text` RFC 2047-encodes headers Previously, this used a mix of standard MIME encoding for the reply body and UTF-8 for the headers. Now, the text format reply template RFC 2047-encodes the headers, making the output a valid RFC 2822 message. The JSON/sexp format is unchanged. `notmuch compact` command The new `compact` command exposes Xapian's compaction functionality through a more convenient interface than `xapian-compact`. `notmuch compact` will compact the database to a temporary location, optionally backup the original database, and move the compacted database into place. Emacs Interface --------------- `notmuch-tree` (formerly `notmuch-pick`) has been added to mainline `notmuch-tree` is a threaded message view for the emacs interface. Each message is one line in the results and the thread structure is shown using UTF-8 box drawing characters (similar to Mutt's threaded view). It comes between search and show in terms of amount of output and can be useful for viewing both single threads and multiple threads. Using `notmuch-tree` The main key entries to notmuch tree are 'z' enter a query to view using notmuch tree (works in hello, search, show and tree mode itself) 'Z' view the current query in tree notmuch tree (works from search and show) Once in tree mode, keybindings are mostly in line with the rest of notmuch and are all viewable with '?' as usual. Customising `notmuch-tree` `notmuch-tree` has several customisation variables. The most significant is the first notmuch-tree-show-out which determines the behaviour when selecting a message (with RET) in tree view. By default tree view uses a split window showing the single message in the bottom pane. However, if this option is set then it views the whole thread in the complete window jumping to the selected message in the thread. In either case command-prefix selects the other option. Tagging threads in search is now race-free Previously, adding or removing a tag from a thread in a search buffer would affect messages that had arrived after the search was performed, resulting in, for example, archiving messages that were never seen. Tagging now affects only the messages that were in the thread when the search was performed. `notmuch-hello` refreshes when switching to the buffer The hello buffer now refreshes whenever you switch to the buffer, regardless of how you get there. You can disable automatic refreshing by customizing `notmuch-hello-auto-refresh`. Specific mini-buffer prompts for tagging operations When entering tags to add or remove, the mini-buffer prompt now indicates what operation will be performed (e.g., "Tag thread", "Tag message", etc). Built-in help improvements Documentation for many commands has been improved, as displayed by `notmuch-help` (usually bound to "?"). The bindings listed by `notmuch-help` also now include descriptions of prefixed commands. Quote replies as they are displayed in show view We now render the parts for reply quoting the same way they are rendered for show. At this time, the notable change is that replies to text/calendar are now pretty instead of raw vcalendar. Fixed inconsistent use of configured search order All ways of interactively invoking search now honor the value of `notmuch-search-oldest-first`. Common keymap for notmuch-wide bindings Several key bindings have been moved from mode-specific keymaps to the single `notmuch-common-keymap`, which is inherited by each notmuch mode. If you've customized your key bindings, you may want to move some of them to the common keymap. The `notmuch-tag` function now requires a list of tag changes For users who have scripted the Emacs interface: the `notmuch-tag` API has changed. Previously, it accepted either a list of tag changes or a space-separated string of tag changes. The latter is no longer supported and the function now returns nothing. Fixed `notmuch-reply` putting reply in primary selection On emacs 24 notmuch-reply used to put the cited text into the primary selection (which could lead to inadvertently pasting this cited text elsewhere). Now the primary-selection is not changed. Fixed `notmuch-show` invisible part handling In some obscure cases part buttons and invisibility had strange interactions: in particular, the default action for some parts gave the wrong action. This has been fixed. Fixed `notmuch-show` attachment viewers and stderr In emacs 24.3+ viewing an attachment could cause spurious text to appear in the show buffer (any stderr or stdout the viewer produced). By default this output is now discarded. For debugging, setting `notmuch-show-attachment-debug` causes notmuch to keep the viewer's stderr and stdout in a separate buffer. Fixed `notmuch-mua-reply` point placement when signature involved By restricting cursor movement to body section for cursor placement after signature is inserted, the cursor cannot "leak" to header section anymore. Now inserted citation content will definitely go to the body part of the message. Vim Interface ------------- It is now possible to compose new messages in the Vim interface, as opposed reply to existing messages. There is also support for going straight to a search (bypassing the folders view). Notmuch 0.16 (2013-08-03) ========================= Command-Line Interface ---------------------- Support for delivering messages to Maildir There is a new command `insert` that adds a message to a Maildir folder and notmuch index. `notmuch count --batch` option `notmuch count` now supports batch operations similar to `notmuch tag`. This is mostly an optimization for remote notmuch usage. `notmuch tag` option to remove all tags from matching messages `notmuch tag --remove-all` option has been added to remove all tags from matching messages. This can be combined with adding new tags, resulting in setting (rather than modifying) the tags of the messages. Decrypting commands explicitly expect a gpg-agent Decryption in `notmuch show` and `notmuch reply` has only ever worked with a functioning gpg-agent. This is now made explicit in code and documentation. The functional change is that it's now possible to have gpg-agent running, but gpg "use-agent" configuration option disabled, not forcing the user to use the agent everywhere. Configuration file saves follow symbolic links The notmuch commands that save the configuration file now follow symbolic links instead of overwrite them. Top level option to specify configuration file It's now possible to specify the configuration file to use on the command line using the `notmuch --config=FILE` option. Bash command-line completion The notmuch command-line completion support for the bash shell has been rewritten. Supported completions include all the notmuch commands, command-line arguments, values for keyword arguments, search prefixes (such as "subject:" or "from:") in all commands that use search terms, tags after + and - in `notmuch tag`, tags after "tag:" prefix, user's email addresses after "from:" and "to:" prefixes, and config options (and some config option values) in `notmuch config`. The new completion support depends on the bash-completion package. Deprecated commands "part" and "search-tags" are removed. Emacs Interface --------------- New keymap to view/save parts; removed s/v/o/| part button bindings The commands to view, save, and open MIME parts are now prefixed with "." (". s" to save, ". v" to view, etc) and can be invoked with point anywhere in a part, unlike the old commands, which were restricted to part buttons. The old "s"/"v"/"o"/"|" commands on part buttons have been removed since they clashed with other bindings (notably "s" for search!) and could not be invoked when there was no part button. The new, prefixed bindings appear in show's help, so you no longer have to memorize them. Default part save directory is now `mm-default-directory` Previously, notmuch offered to save parts and attachments to a mix of `mm-default-directory`, `mailcap-download-directory`, and `~/`. This has been standardized on `mm-default-directory`. Key bindings for next/previous thread Show view has new key bindings M-n and M-p to move to the next and previous thread in the search results. Better handling of errors in search buffers Instead of interleaving errors in search result buffers, search mode now reports errors in the minibuffer. Faster search and show Communication between Emacs and the notmuch CLI is now more efficient because it uses the CLI's S-expression support. As a result, search mode should now fill search buffers faster and threads should show faster. No Emacs 22 support The Emacs 22 support added late 2010 was sufficient only for a short period of time. After being incomplete for roughly 2 years the code in question was now removed from this release. Vim Front-End ------------- The vim based front-end has been replaced with a new one that uses the Ruby bindings. The old font-end is available in the contrib subfolder. Python Bindings --------------- Fix loading of libnotmuch shared library on OS X (Darwin) systems. Notmuch 0.15.2 (2013-02-17) =========================== Build fixes ----------- Update dependencies to avoid problems when building in parallel. Internal test framework changes ------------------------------- Adjust Emacs test watchdog mechanism to cope with `process-attributes` being unimplemented. Notmuch 0.15.1 (2013-01-24) =========================== Internal test framework changes ------------------------------- Set a default value for TERM when running tests. This fixes certain build failures in non-interactive environments. Notmuch 0.15 (2013-01-18) ========================= General ------- Date range search support The `date:` prefix can now be used in queries to restrict the results to only messages within a particular time range (based on the Date: header) with a range syntax of `date:..`. Notmuch supports a wide variety of expressions in `` and ``. Please refer to the `notmuch-search-terms(7)` manual page for details. Empty tag names and tags beginning with "-" are deprecated Such tags have been a frequent source of confusion and cause (sometimes unresolvable) conflicts with other syntax. notmuch tag no longer allows such tags to be added to messages. Removing such tags continues to be supported to allow cleanup of existing tags, but may be removed in a future release. Command-Line Interface ---------------------- `notmuch new` no longer chokes on mboxes `notmuch new` now rejects mbox files containing more than one message, rather than treating the file as one giant message. Support for single message mboxes is deprecated For historical reasons, `notmuch new` will index mbox files containing a single message; however, this behavior is now officially deprecated. Fixed `notmuch new` to skip ignored broken symlinks `notmuch new` now correctly skips symlinks if they are in the ignored files list. Previously, it would abort when encountering broken symlink, even if it was ignored. New dump/restore format and tagging interface There is a new `batch-tag` format for dump and restore that is more robust, particularly with respect to tags and message-ids containing whitespace. `notmuch tag` now supports the ability to read tag operations and queries from an input stream, in a format compatible with the new dump/restore format. Bcc and Reply-To headers are now available in notmuch show json output The `notmuch show --format=json` now includes "Bcc" and "Reply-To" headers. For example notmuch Emacs client can now have these headers visible when the headers are added to the `notmuch-message-headers` variable. CLI callers can now request a specific output format version `notmuch` subcommands that support structured output now support a `--format-version` argument for requesting a specific version of the structured output, enabling better compatibility and error handling. `notmuch search` has gained a null character separated text output format The new --format=text0 output format for `notmuch search` prints output separated by null characters rather than newline characters. This is similar to the find(1) -print0 option, and works together with the xargs(1) -0 option. Emacs Interface --------------- Removal of the deprecated `notmuch-folders` variable `notmuch-folders` has been deprecated since the introduction of saved searches and the notmuch hello view in notmuch 0.3. `notmuch-folders` has now been removed. Any remaining users should migrate to `notmuch-saved-searches`. Visibility of MIME parts can be toggled Each part of a multi-part MIME email can now be shown or hidden using the button at the top of each part (by pressing RET on it or by clicking). For emails with multiple alternative formats (e.g., plain text and HTML), only the preferred format is shown initially, but other formats can be shown using their part buttons. To control the behavior of this, see `notmuch-multipart/alternative-discouraged` and `notmuch-show-all-multipart/alternative-parts`. Note notmuch-show-print-message (bound to '#' by default) will print all parts of multipart/alternative message regardless of whether they are currently hidden or shown in the buffer. Emacs now buttonizes mid: links mid: links are a standardized way to link to messages by message ID (see RFC 2392). Emacs now hyperlinks mid: links to the appropriate notmuch search. Handle errors from bodypart insertions If displaying the text of a message in show mode causes an error (in the `notmuch-show-insert-part-*` functions), notmuch no longer cuts off thread display at the offending message. The error is now simply displayed in place of the message. Emacs now detects version mismatches with the notmuch CLI Emacs now detects and reports when the Emacs interface version and the notmuch CLI version are incompatible. Improved text/calendar content handling Carriage returns in embedded text/calendar content caused insertion of the calendar content fail. Now CRs are removed before calling icalendar to extract icalendar data. In case icalendar extraction fails an error is thrown for the bodypart insertion function to deal with. Disabled coding conversions when reading in `with-current-notmuch-show-message` Depending on the user's locale, saving attachments containing 8-bit data may have performed an unintentional encoding conversion, corrupting the saved attachment. This has been fixed by making `with-current-notmuch-show-message` disable coding conversion. Fixed errors with HTML email containing images in Emacs 24 Emacs 24 ships with a new HTML renderer that produces better output, but is slightly buggy. We work around a bug that caused it to fail for HTML email containing images. Fixed handling of tags with unusual characters in them Emacs now handles tags containing spaces, quotes, and parenthesis. Fixed buttonization of id: links without quote characters Emacs now correctly buttonizes id: links where the message ID is not quoted. `notmuch-hello` refresh point placement improvements Refreshing the `notmuch-hello` buffer does a better job of keeping the point where it was. Automatic tag changes are now unified and customizable All the automatic tag changes that the Emacs interface makes when reading, archiving, or replying to messages, can now be customized. Any number of tag additions and removals is supported through the `notmuch-show-mark-read`, `notmuch-archive-tags`, and `notmuch-message-replied-tags` customization variables. Support for stashing the thread id in show view Invoking `notmuch-show-stash-message-id` with a prefix argument stashes the (local and database specific) thread id of the current thread instead of the message id. New add-on tool: notmuch-pick ----------------------------- The new contrib/ tool `notmuch-pick` is an experimental threaded message view for the emacs interface. Each message is one line in the results and the thread structure is shown using UTF-8 box drawing characters (similar to Mutt's threaded view). It comes between search and show in terms of amount of output and can be useful for viewing both single threads and multiple threads. See the notmuch-pick README file for further details and installation. Portability ----------- notmuch now builds on OpenBSD. Internal test framework changes ------------------------------- The emacsclient binary is now user-configurable The test framework now accepts `TEST_EMACSCLIENT` in addition to `TEST_EMACS` for configuring the emacsclient to use. This is necessary to avoid using an old emacsclient with a new emacs, which can result in buggy behavior. Notmuch 0.14 (2012-08-20) ========================= General bug fixes ----------------- Maildir tag synchronization Maildir flag-to-tag synchronization now applies only to messages in maildir-like directory structures. Previously, it applied to any message that had a maildir "info" part, which meant it could incorrectly synchronize tags for non-maildir messages, while at the same time failing to synchronize tags for newly received maildir messages (typically causing new messages to not receive the "unread" tag). Command-Line Interface ---------------------- The deprecated positional output file argument to `notmuch dump` has been replaced with an `--output` option. The input file positional argument to `notmuch restore` has been replaced with an `--input` option for consistency with dump. These changes simplify the syntax of dump/restore options and make them more consistent with other notmuch commands. Emacs Interface --------------- Search results now get re-colored when tags are updated The formatting of tags in search results can now be customized Previously, attempting to change the format of tags in `notmuch-search-result-format` would usually break tagging from search-mode. We no longer make assumptions about the format. Experimental support for multi-line search result formats It is now possible to embed newlines in `notmuch-search-result-format` to make individual search results span multiple lines. Next/previous in search and show now move by boundaries All "next" and "previous" commands in the search and show modes now move to the next/previous result or message boundary. This doesn't change the behavior of "next", but "previous" commands will first move to the beginning of the current result or message if point is inside the result or message. Search now uses the JSON format internally This should address problems with unusual characters in authors and subject lines that could confuse the old text-based search parser. The date shown in search results is no longer padded before applying user-specified formatting Previously, the date in the search results was padded to fixed width before being formatted with `notmuch-search-result-format`. It is no longer padded. The default format has been updated, but if you've customized this variable, you may have to change your date format from `"%s "` to `"%12s "`. The thread-id for the `target-thread` argument for `notmuch-search` should now be supplied without the "thread:" prefix. Notmuch 0.13.2 (2012-06-02) =========================== Bug-fix release --------------- Update `contrib/notmuch-deliver` for API changes in 0.13. This fixes a compilation error for this contrib package. Notmuch 0.13.1 (2012-05-29) =========================== Bug-fix release --------------- Fix inserting of UTF-8 characters from *text/plain* parts in reply While notmuch gained ability to insert content from other than *text/plain* parts of email whenever *text/plain* parts are not available (notably HTML-only emails), replying to mails that do have *text/plain* the non-ASCII characters were incorrectly decoded. This is now fixed. `notmuch_database_get_directory` and `notmuch_database_find_message_by_filename` now work on read-only databases Previously, these functions attempted to create directory documents that didn't exist and would return an error or abort when given a read-only database. Now they no longer create directory documents and simply return a `NULL` object if the directory does not exist, as documented. Fix compilation of ruby bindings Revert to dynamic linking, since the statically linked bindings did not work well. Notmuch 0.13 (2012-05-15) ========================= Command-Line Interface ---------------------- JSON reply format `notmuch reply` can now produce JSON output that contains the headers for a reply message and full information about the original message begin replied to. This allows MUAs to create replies intelligently. For example, an MUA that can parse HTML might quote HTML parts. Calling notmuch reply with `--format=json` imposes the restriction that only a single message is returned by the search, as replying to multiple messages does not have a well-defined behavior. The default retains its current behavior for multiple message replies. Tag exclusion Tags can be automatically excluded from search results by adding them to the new `search.exclude_tags` option in the Notmuch config file. This behaviour can be overridden by explicitly including an excluded tag in your query, for example: notmuch search $your_query and tag:$excluded_tag Existing users will probably want to run `notmuch setup` again to add the new well-commented [search] section to the configuration file. For new configurations, accepting the default setting will cause the tags "deleted" and "spam" to be excluded, equivalent to running: notmuch config set search.exclude_tags deleted spam Raw show format changes The output of show `--format=raw` has changed for multipart and message parts. Previously, the output was a mash of somewhat-parsed headers and transfer-decoded bodies. Now, such parts are reproduced faithfully from the original source. Message parts (which includes part 0) output the full message, including the message headers (but not the transfer headers). Multipart parts output the part as encoded in the original message, including the part's headers. Leaf parts, as before, output the part's transfer-decoded body. Listing configuration items The new `config list` command prints out all configuration items and their values. Emacs Interface --------------- Changes to tagging interface The user-facing tagging functions in the Emacs interface have been normalized across all notmuch modes. The tagging functions are now notmuch-search-tag in search-mode, and notmuch-show-tag in show-mode. They accept a string representing a single tag change, or a list of tag changes. See 'M-x describe-function notmuch-tag' for more information. NOTE: This breaks compatibility with old tagging functions, so user may need to update in custom configurations. Reply improvement using the JSON format Emacs now uses the JSON reply format to create replies. It obeys the customization variables message-citation-line-format and message-citation-line-function when creating the first line of the reply body, and it will quote HTML parts if no text/plain parts are available. New add-on tool: notmuch-mutt ----------------------------- The new contrib/ tool `notmuch-mutt` provides Notmuch integration for the Mutt mail user agent. Using it, Mutt users can perform mail search, thread reconstruction, and mail tagging/untagging without leaving Mutt. notmuch-mutt, formerly distributed under the name `mutt-notmuch` by Stefano Zacchiroli, will be maintained as a notmuch contrib/ from now on. Library changes --------------- The API changes detailed below break binary and source compatibility, so libnotmuch has been bumped to version 3.0.0. The function `notmuch_database_close` has been split into `notmuch_database_close` and `notmuch_database_destroy` This makes it possible for long running programs to close the xapian database and thus release the lock associated with it without destroying the data structures obtained from it. `notmuch_database_open`, `notmuch_database_create`, and `notmuch_database_get_directory` now return errors The type signatures of these functions have changed so that the functions now return a `notmuch_status_t` and take an out-argument for returning the new database object or directory object. Go bindings changes ------------------- Go 1 compatibility The go bindings and the `notmuch-addrlookup` utility are now compatible with go 1. Notmuch 0.12 (2012-03-20) ========================= Command-Line Interface ---------------------- Reply to sender `notmuch reply` has gained the ability to create a reply template for replying just to the sender of the message, in addition to reply to all. The feature is available through the new command line option `--reply-to=(all|sender)`. Mail store folder/file ignore A new configuration option, `new.ignore`, lets users specify a ;-separated list of file and directory names that will not be searched for messages by `notmuch new`. NOTE: *Every* file/directory that goes by one of those names will be ignored, independent of its depth/location in the mail store. Unified help and manual pages The notmuch help command now runs man for the appropriate page. If you install notmuch somewhere "unusual", you may need to update MANPATH. Manual page for notmuch configuration options The notmuch CLI configuration file options are now documented in the notmuch-config(1) manual page in addition to the configuration file itself. Emacs Interface --------------- Reply to sender The Emacs interface has, with the new CLI support, gained the ability to reply to sender in addition to reply to all. In both show and search modes, 'r' has been bound to reply to sender, replacing reply to all, which now has key binding 'R'. More flexible and consistent tagging operations All tagging operations ('+', '-', '*') now accept multiple tags with '+' or '-' prefix, like '*' operation in notmuch-search view before. '*' operation (`notmuch-show-tag-all`) is now available in notmuch-show view. `notmuch-show-{add,remove}-tag` functions no longer accept tag argument, `notmuch-show-tag-message` should be used instead. Custom bindings using these functions should be updated, e.g.: (notmuch-show-remove-tag "unread") should be changed to: (notmuch-show-tag-message "-unread") Refreshing the show view ('=' by default) no longer opens or closes messages To get the old behavior of putting messages back in their initial opened/closed state, use a prefix argument, e.g., 'C-u ='. Attachment buttons can be used to view or save attachments. When the cursor is on an attachment button the key 's' can be used to save the attachment, the key 'v' to view the attachment in the default mailcap application, and the key 'o' prompts the user for an application to use to open the attachment. By default Enter or mouse button 1 saves the attachment but this is customisable (option Notmuch Show Part Button Default Action). New functions `notmuch-show-stash-mlarchive-link{,-and-go}` allow stashing and optionally visiting a URI to the current message at one of a number of Mailing List Archives. Fix MML tag quoting in replies The MML tag quoting fix of 0.11.1 unintentionally quoted tags inserted in `message-setup-hook`. Quoting is now limited to the cited message. Show view archiving key binding changes The show view archiving key bindings 'a' and 'x' now remove the "inbox" tag from the current message only (instead of thread), and move to the next message. At the last message, 'a' proceeds to the next thread in search results, and 'x' returns to search results. The thread archiving functions are now available in 'A' and 'X'. Support text/calendar MIME type The text/calendar MIME type is now supported in addition to text/x-vcalendar. Generate inline patch fake attachment file names from message subject Use the message subject to generate file names for the inline patch fake attachments. The names are now similar to the ones generated by 'git format-patch' instead of just "inline patch". See "Notmuch Show Insert Text/Plain Hook" in the notmuch customize interface. Enable `notmuch-search-line-faces` by default Make the `notmuch-search-line-faces` functionality more discoverable for new users by showing "unread" messages bold and "flagged" messages blue by default in the search view. Printing Support notmuch-show mode now has simple printing support, bound to '#' by default. You can customize the variable notmuch-print-mechanism. Library changes --------------- New functions `notmuch_query_add_tag_exclude` supports the new tag exclusion feature. Python bindings changes ----------------------- Python 3.2 compatibility The python bindings are now compatible with both python 2.5+ and 3.2. Added missing unicode conversions Python strings have to be encoded to and decoded from utf-8 when calling libnotmuch functions. Porting the bindings to python 3.2 revealed a few function calls that were missing these conversions. Build fixes ----------- Compatibility with GMime 2.6 It is now possible to build notmuch against both GMime 2.4 and 2.6. However, a bug in GMime 2.6 before 2.6.5 causes notmuch not to report signatures where the signer key is unavailable (GNOME bug 668085). For compatibility with GMime 2.4's tolerance of "From " headers we require GMime 2.6 >= 2.6.7. Notmuch 0.11.1 (2012-02-03) =========================== Bug-fix release --------------- Fix error handling in python bindings The python bindings in 0.11 failed to detect NULL pointers being returned from libnotmuch functions and thus failed to raise exceptions to indicate the error condition. Any subsequent calls into libnotmuch caused segmentation faults. Quote MML tags in replies MML tags are text codes that Emacs uses to indicate attachments (among other things) in messages being composed. The Emacs interface did not quote MML tags in the quoted text of a reply. User could be tricked into replying to a maliciously formatted message and not editing out the MML tags from the quoted text. This could lead to files from the user's machine being attached to the outgoing message. The Emacs interface now quotes these tags in reply text, so that they do not effect outgoing messages. Notmuch 0.11 (2012-01-13) ========================= Command-Line Interface ---------------------- Hooks Hooks have been introduced to notmuch. Hooks are scripts that notmuch invokes before and after certain actions. Initially, `notmuch new` supports `pre-new` and `post-new` hooks that are run before and after importing new messages into the database. `notmuch reply --decrypt bugfix` The `notmuch reply` command with `--decrypt` argument had a rarely occurring bug that caused an encrypted message not to be decrypted sometimes. This is now fixed. Performance ----------- Automatic tag query optimization `notmuch tag` now automatically optimizes the user's query to exclude messages whose tags won't change. In the past, we've suggested that people do this by hand; this is no longer necessary. Don't sort messages when creating a dump file This speeds up tag dumps considerably, without any loss of information. To replicate the old behavior of sorted output (for example to compare two dump files), one can use e.g. `sort(1)`. Memory Management ----------------- Reduction of memory leaks Two memory leaks when searching and showing messages were identified and fixed in this release. Emacs Interface --------------- Bug fixes notmuch-show-advance (bound to the spacebar in notmuch-show-mode) had a bug that caused it to always jump to the next message, even if it should have scrolled down to show more of the current message instead. This is now fixed. Support `notmuch new` as a notmuch-poll-script It's now possible to use `notmuch new` as a notmuch-poll-script directly. This is also the new default. This allows taking better advantage of the `notmuch new` hooks from emacs without intermediate scripts. Improvements in saved search management New saved searches are now appended to the list of saved searches, not inserted in front. It's also possible to define a sort function for displaying saved searches; alphabetical sort is provided. Hooks for notmuch-hello Two new hooks have been added: "notmuch-hello-mode-hook" (called after entering notmuch-hello-mode) and "notmuch-hello-refresh-hook" (called after updating a notmuch-hello buffer). New face for crypto parts headers Crypto parts used to be displayed with a hardcoded color. A new face has been introduced to fix this: notmuch-crypto-part-header. It defaults to the same value as before, but can be customized to match other color themes. Use space as default thousands separator Large numbers in notmuch-hello are now displayed using a space as thousands separator (e.g. "123 456" instead of "123,456"). This can be changed by customizing "notmuch-hello-thousands-separator". Call notmuch-show instead of notmuch-search when clicking on buttonized id: links New function notmuch-show-advance This new function advances through just the current thread, and is less invasive than notmuch-show-advance-and-archive. It can easily be bound to SPC with: (define-key notmuch-show-mode-map " " 'notmuch-show-advance) Various performance improvements New add-on tool --------------- The tool `contrib/notmuch-deliver` helps with initial delivery and tagging of mail (replacing running `notmuch new`). Notmuch 0.10.2 (2011-12-04) =========================== Bug-fix release --------------- Fix crash in python bindings The python bindings did not call `g_type_init`, which caused crashes for some, but not all users. Notmuch 0.10.1 (2011-11-25) =========================== Bug-fix release --------------- Fix `--help` argument Argument processing changes in 0.10 introduced a bug where `notmuch --help` crashed while `notmuch help` worked fine. This is fixed in 0.10.1. Notmuch 0.10 (2011-11-23) ========================= New build and testing features ------------------------------ Emacs tests are now done in `dtach`. This means that dtach is now needed to run the notmuch test suite, at least until the checking for prerequisites is improved. Full test coverage of the stashing feature in Emacs. New command-line features ------------------------- Add `notmuch restore --accumulate` option The `--accumulate` switch causes the union of the existing and new tags to be applied, instead of replacing each message's tags as they are read in from the dump file. Add search terms to `notmuch dump` The dump command now takes an optional search term much like notmuch search/show/tag. The output file argument of dump is deprecated in favour of using stdout. Add `notmuch search` `--offset` and `--limit` options The search command now takes options `--offset=[-]N` and `--limit=N` to limit the number of results shown. Add `notmuch count --output` option The count command is now capable of counting threads in addition to messages. This is selected using the new `--output=(threads|messages)` option. New emacs UI features --------------------- Add tab-completion for `notmuch-search` and `notmuch-search-filter` These functions now support completion tags for query parts starting with "tag:". Turn "id:MSG-ID" links into buttons associated with notmuch searches Text of the form "id:MSG-ID" in mails is now a clickable button that opens a notmuch search for the given message id. Add keybinding ('c I') for stashing Message-ID's without an id: prefix Reduces manual labour when stashing them for use outside notmuch. Do not query on `notmuch-search` exit It is harmless to kill the external notmuch process, so the user is no longer interrogated when they interrupt a search. Performance ----------- Emacs now constructs large search buffers more efficiently Search avoids opening and parsing message files We now store more information in the database so search no longer has to open every message file to get basic headers. This can improve search speed by as much as 10X, but taking advantage of this requires a database rebuild: notmuch dump > notmuch.dump # Backup, then remove notmuch database ($MAIL/.notmuch) notmuch new notmuch restore notmuch.dump New collection of add-on tools ------------------------------ The source directory "contrib" contains tools built on notmuch. These tools are not part of notmuch, and you should check their individual licenses. Feel free to report problems with them to the notmuch mailing list. nmbug - share tags with a given prefix nmbug helps maintain a git repo containing all tags with a given prefix (by default "notmuch::"). Tags can be shared by commiting them to git in one location and restoring in another. Notmuch 0.9 (2011-10-01) ======================== New, general features --------------------- Correct handling of interruptions during `notmuch new` `notmuch new` now operates as a series of small, self-consistent transactions, so it can correctly resume after an interruption or crash. Previously, interruption could lose existing tags, fail to detect messages on resume, or leave the database in a state temporarily or permanently inconsistent with the mail store. Library changes --------------- New functions `notmuch_database_begin_atomic` and `notmuch_database_end_atomic` allow multiple database operations to be performed atomically. `notmuch_database_find_message_by_filename` does exactly what it says. API changes `notmuch_database_find_message` (and `n_d_f_m_by_filename`) now return a status indicator and uses an output parameter for the message. This change required changing the SONAME of libnotmuch to libnotmuch.so.2 Python bindings changes ----------------------- - Re-encode python unicode objects to utf-8 before passing back to libnotmuch. - Support `Database().begin_atomic()/end_atomic()` - Support `Database().find_message_by_filename()` NB! This needs a db opened in READ-WRITE mode currently, or it will crash the python process. The is a limitation (=bug) of the underlying libnotmuch. - Fixes where we would not throw NotmuchErrors when we should (Justus Winter) - Update for `n_d_find_message*` API changes (see above). Ruby bindings changes --------------------- - Wrap new library functions `notmuch_database_{begin,end}_atomic.` - Add new exception `Notmuch::UnbalancedAtomicError.` - Rename destroy to destroy! according to Ruby naming conventions. - Update for `n_d_find_message*` API changes (see above). Emacs improvements ------------------ * Add gpg callback to crypto sigstatus buttons to retrieve/refresh signing key. * Add `notmuch-show-refresh-view` function (and corresponding binding) to refresh the view of a notmuch-show buffer. Reply formatting cleanup ------------------------ `notmuch reply` no longer includes notification that non-leafnode MIME parts are being suppressed. Notmuch 0.8 (2011-09-10) ======================== Improved handling of message/rfc822 parts Both in the CLI and the emacs interface. Output of rfc822 parts now includes the primary headers, as well as the body and all subparts. Output of the completely raw rfc822-formatted message, including all headers, is unfortunately not yet supported (but hopefully will be soon). Improved Build system portability Certain parts of the shell script generating notmuch.sym were specific to the GNU versions of sed and nm. The new version should be more portable to e.g. OpenBSD. Documentation update for Ruby bindings Added documentation, typo fixes, and improved support for rdoc. Unicode, iterator, PEP8 changes for python bindings - PEP8 (code formatting) changes for python files. - Remove `Tags.__len__` ; see 0.6 release notes for motivation. - Decode headers as UTF8, encode (unicode) database paths as UTF8. Notmuch 0.7 (2011-08-01) ======================== Vim interface improvements -------------------------- Jason Woofenden provided a number of bug fixes for the Vim interface * fix citation/signature fold lengths * fix cig/cit parsing within multipart/* * fix on-screen instructions for show-signature * fix from list reformatting in search view * fix space key: now archives (did opposite) Uwe Kleine-König contributed * use full path for sendmail/doc fix * fix compose temp file name Python Bindings changes ----------------------- Sebastian Spaeth contributed two changes related to unicode and UTF8: * message tags are now explicitly unicode * query string is encoded as a UTF8 byte string Build-System improvements ------------------------- Generate notmuch.sym after the relevant object files This fixes a bug in parallel building. Thanks to Thomas Jost for the patch. Notmuch 0.6.1 (2011-07-17) ========================== Bug-fix release --------------- Re-export Xapian exception typeinfo symbols It turned out our aggressive symbol hiding caused problems for people running gcc 4.4.5. Notmuch 0.6 (2011-07-01) ======================= New, general features --------------------- Folder-based searching Notmuch queries can now include a search term to match the directories in which mail files are stored (within the mail storage). The syntax is as follows: folder: For example, one might use things such as: folder:spam folder:2011-* folder:work/todo to match any path containing a directory "spam", "work/todo", or containing a directory starting with "2011-", respectively. This feature is particularly useful for users of delivery-agent software (such as procmail or maildrop) that is filtering mail and delivering it to particular folders, or users of systems such as Gmail that use filesystem directories to indicate message tags. NOTE: Only messages that are newly indexed with this version of notmuch will be searchable with folder: terms. In order to enable this feature for all mail, the entire notmuch index will need to be rebuilt as follows: notmuch dump > notmuch.dump # Backup, then remove notmuch database ($MAIL/.notmuch) notmuch new notmuch restore notmuch.dump Support for PGP/MIME Both the command-line interface and the emacs-interface have new support for PGP/MIME, detailed below. Thanks to Daniel Kahn Gillmor and Jameson Graef Rollins for making this happen. New, automatic tags: "signed" and "encrypted" These tags will automatically be applied to messages containing multipart/signed and multipart/encrypted parts. NOTE: Only messages that are newly indexed with this version of notmuch will receive these tags. New command-line features ------------------------- Add new "notmuch show --verify" option for signature verification This option instruct notmuch to verify the signature of PGP/MIME-signed parts. Add new "notmuch show --decrypt" and "notmuch reply --decrypt" options This option instructs notmuch to decrypt PGP/MIME-encrypted parts. Note that this feature currently requires gpg-agent and a passphrase entry tool (e.g. pinentry-gtk or pinentry-curses). Proper nesting of multipart parts in "notmuch show" output MIME parts are now display with proper nesting to reflect original MIME hierarchy of a message. This allows clients to correctly analyze the MIME structure, (such as, for example, determining to which parts a signature part applies). Add new "notmuch show --part" option This is a replacement for the older "notmuch part" command, (which is now deprecated—it should still work as always, but is no longer documented). Putting part output under "notmuch show" allows for all of the "notmuch show" options to be applied when extracting a single part, (such as --format=json for extracting a message part with JSON formatting). Deprecate "notmuch search-tags" (in favor of "notmuch search --output=tags *") The "notmuch search-tags" sub-command has been redundant since the addition of the --output=tags option to "notmuch search". We now make that more clear by deprecating "notmuch search-tags", (dropping it from the documentation). We do continue to support the old syntax by translating it internally to the new call. Performance improvements ------------------------ Faster searches (by doing fewer searches to construct threads) Whenever a user asks for search results as threads, notmuch first performs a search for messages matching the query, then performs additional searches to find other messages in the resulting threads. Removing inefficiencies and redundancies in these secondary searches results in a measured speedups of 1.5x for a typical search. Faster searches (by doing fewer passes to gather message data) Optimizing Xapian data access patterns (using a single pass to get all message-document data rather than a pass for each data type) results in a measured speedup of 1.7x for a typical search. The benefits of this optimization combine with the preceding optimization. With both in place, Austin Clements measured a speedup of 2.5x for a search of all messages in his inbox (was 4.5s, now 1.8s). Thanks, Austin! Faster initial indexing More efficient indexing of new messages results in a measured speedup of 1.4x for the initial indexing of 3 GB of mail (1h 14m rather than 1h 46m). Thanks to Austin Clements and Michal Sojka. Make "notmuch new" faster for unchanged directories Optimizing to not do any further examinations of sub-directories when the filesystem indicates that a directory is unchanged from the last "notmuch new" results in measured speedups of 8.5 for the "No new mail" case, (was 0.77s, now 0.09s). Thanks to Karel Zak. New emacs-interface features ---------------------------- Support for PGP/MIME (GnuPG) Automatically indicate validity of signatures for multipart/signed messages. Automatically display decrypted content for multipart/encrypted messages. See the emacs variable notmuch-crypto-process-mime for more information. Note that this needs gpg-agent and a pinentry tool just as the command-line tools. Also note there is no support SMIME yet. Output of pipe command is now displayed if pipe command fails This is extremely useful in the common use case of piping a patch to "git am". If git fails to cleanly merge the patch the error messages from the failed merge are now clearly displayed to the user, (where previously they were silently hidden from the user). User-selectable From address A user can choose which configured email addresses should be used as the From address whenever composing a new message. To do so, simply press C-u before the command which will open a new message. Emacs will prompt for the from address to use. The user can customize the "Notmuch Identities" setting in the notmuch customize group in order to use addresses other than those in the notmuch configuration file if desired. The user can also choose to always be prompted for the from address when composing a new message (without having to use C-u) by setting the "Notmuch Always Prompt For Sender" option in the notmuch customize group. Hiding of repeated subjects in collapsed thread view In notmuch-show mode, if a collapsed message has the same subject as its parent, the subject is not shown. Automatic detection and hiding of original message in top-posted message When a message contains a line looking something like: ----- Original Message ----- emacs hides this and all subsequent lines as an "original message", (allowing the user to click or press enter on the "original message" button to display it again). This makes the handling of top-posted citations work much like conventional citations. New hooks for running code when tags are modified Some users want to perform additional actions whenever a particular tag is added/removed from a message. This could be used to, for example, interface with some external spam-recognition training tool. To facilitate this, two new hooks are added which can be modified in the following settings of the notmuch customize group: Notmuch Before Tag Hook Notmuch After Tag Hook New optional support for hiding some multipart/alternative parts Many emails are sent with redundant content within a multipart/alternative group (such as a text/plain part as well as a text/html part). Users can configure the setting: Notmuch Show All Multipart/Alternative Parts to "off" in the notmuch customize group to have the interface automatically hide some part alternatives (such as text/html parts). This new part hiding is not configured by default yet because there's not yet a simple way to re-display such a hidden part if it is not actually redundant with a displayed part. Better rendering of text/x-vcalendar parts These parts are now displayed in a format suitable for use with the emacs diary. Avoid getting confused by Subject and Author fields with newline characters Replacing all characters with ASCII code less than 32 with a question mark. Cleaner display of From line in email messages Remove double quotes, and drop "name" if it's actually just a repeat of the email address. Vim interface improvements -------------------------- Felipe Contreras provided a number of updates for the vim interface: * Using sendmail directly rather than mailx, * Implementing archive in show view * Add support to mark as read in show and search views * Add delete commands * Various cleanups. Bindings improvements --------------------- Ruby bindings are now much more complete Including `QUERY.sort`, `QUERY.to_s`, `MESSAGE.maildir_flags_to_tags`, `MESSAGE.tags_to_maildir_flags`, and `MESSAGE.get_filenames` Python bindings have been updated and extended (docs online at http://packages.python.org/notmuch/) New bindings: - `Message().get_filenames()`, `Message().tags_to_maildir_flags()`, `Message().maildir_flags_to_tags()`, `list(Threads())` and `list(Messages)` works now - `Message().__cmp__()` and `__hash__()` These allow, for example: if msg1 == msg2: ... As well as set arithmetic on `Messages()`: s1, s2 = set(msgs1), set(msgs2) s1.union(s2) s2 -= s1 Removed: - `len(Messages())` as it exhausted the iterator Use `len(list(Messages()))` or `Query.count_messages()` to get the length. Added initial Go bindings in bindings/go New build-system features ------------------------- Added support for building in a directory other than the source directory This can be used with the widely-supported idiom of simply running the configure script from some other directory: mkdir build cd build ../configure make Fix to save configure options for future, implicit runs of configure When a user updates the source (such as with "git pull") calling "make" may cause an automatic re-run of the configure script. When this happens, the configure script will automatically be called with the same options the user originally passed in the most-recent manual invocation of configure. New test-suite feature ---------------------- Binary for bash for running test suite now located via PATH The notmuch test suite requires a fairly recent version of bash (>= bash 4). As some systems supply an older version of bash at /bin/bash, the test suite is now updated to search $PATH to locate the bash binary. This allows users of systems with old /bin/bash to simply install bash >= 4 somewhere on $PATH before /bin and then use the test suite. Support for testing output with a trailing newline Previously, some tests would fail to notice a difference in the presence/absence of a trailing newline in a program output, (which has led to bugs in the past). Now, carefully-written tests (using `test_expect_equal_file` rather than `test_expect_equal`) will detect any change in the presence/absence of a trailing newline. Many tests are updated to take advantage of this. Avoiding accessing user's $HOME while running test suite The test suite now carefully creates its own HOME directory. This allows the test suite to be run with no existing HOME directory, (as some build systems apparently do), and avoids test-suite differences due to configuration files in the users HOME directory. General bug fixes ----------------- Output *all* files for "notmuch search --output=files" For the cases where multiple files have the same Message ID, previous versions of notmuch would output only one such file. This command is now fixed to correctly output all files. Fixed spurious search results from "overlapped" indexing of addresses This fixed a bug where a search for: to:user@elsewhere.com would incorrectly match a message sent: To: user@example,com, someone@elsewhere.com Fix --output=json when search has no results A bug present since notmuch 0.4 had caused searches with no results to produce an invalid json object. This is now fixed to cleanly return a valid json object representing an empty array "[]" as expected. Fix the automatic detection of the From address for "notmuch reply" from the Received headers in some cases Fix core dump on DragonFlyBSD due to -1 return value from `sysconf(_SC_GETPW_R_SIZE_MAX)` Cleaned up several memory leaks Eliminated a few, rare segmentation faults and a double-free Fix libnotmuch library to only export notmuch API functions Previous release of the notmuch library also exported some Xapian C++ exception type symbols. These were never part of the library interface and were never intended to be exported. Emacs-interface bug fixes ------------------------- Display any unexpected output or errors from "notmuch search" invocations Previously any misformatted output or trailing error messages were silently ignored. This output is now clearly displayed. This fix was very helpful in identifying and fixing the bug described below. Fix bug where some threads would be missing from large search results When a search returned a "large" number of results, the emacs interface was incorrectly dropping one thread every time the output of the "notmuch search" process spanned the emacs read-buffer. This is now fixed. Avoid re-compression of .gz files (and similar) when saving attachment Emacs was being too clever for its own good and trying to re-compress pre-compressed .gz files when saving such attachments (potentially corrupting the attachment). The emacs interface is fixed to avoid this bug. Fix hiding of a message when a previously-hidden citation is visible Previously the citation would remain visible in this case. This is fixed so that hiding a message hides all parts. Notmuch 0.5 (2010-11-11) ======================== New, general features --------------------- Maildir-flag synchronization Notmuch now knows how to synchronize flags in maildir filenames with tags in the notmuch database. The following flag/tag mappings are supported: Flag <-> Tag ---- ----- 'D' draft 'F' flagged 'P' passed 'R' replied 'S' unread (added when 'S' flag is not present) The synchronization occurs in both directions, (for example, adding the 'S' flag to a file will cause the "unread" tag to be added, and adding the "replied" tag to a message will cause the file to be renamed with an 'R' flag). This synchronization is enabled by default for users of the command-line interface, (though only files in directories named "cur" or "new" will be renamed). It can be disabled by setting the new `maildir.synchronize_flags` option in the configuration file. For example: notmuch config set maildir.synchronize_flags false Users upgrading may also want to run "notmuch setup" once (just accept the existing configuration) to get a new, nicely-commented [maildir] section added to the configuration file. For users of the notmuch library, the new synchronization functionality is available with the following two new functions: notmuch_message_maildir_flags_to_tags notmuch_message_tags_to_maildir_flags It is anticipated that future improvements to this support will allow for safe synchronization of the 'T' flag with the "deleted" tag, as well as support for custom flag/tag mappings. New library features -------------------- Support for querying multiple filenames for a single message It is common for the mailstore to contain multiple files with the same message ID. Previously, notmuch would always hide these duplicate files, (returning a single, arbitrary filename with `notmuch_message_get_filename`). With this release, library users can access all filenames for a message with the new function: notmuch_message_get_filenames Together with `notmuch_filenames_valid`, `notmuch_filenames_get`, and `notmuch_filenames_move_to_next` it is now possible to iterate over all available filenames for a given message. New command-line features ------------------------- New "notmuch show --format=raw" for getting at original email contents This new feature allows for a fully-functional email client to be built on top of the notmuch command-line without needing any direct access to the mail store itself. For example, it's now possible to run "emacs -f notmuch" on a local machine with only ssh access to the mail store/notmuch database. To do this, simply set the notmuch-command variable in emacs to the name of a script containing: ssh user@host notmuch "$@" If the ssh client has enabled connection sharing (ControlMaster option in OpenSSH), the emacs interface can be quite responsive this way. General bug fixes ----------------- Fix "notmuch search" to print nothing when nothing matches The 0.4 release had a bug in which: notmuch search would produce a single blank line of output, (where previous versions would produce no output. This fix also causes a change in the --format=json output, (which would previously produce "[]" and now produces nothing). Emacs interface improvements ---------------------------- Fix to allow pipe ('|') command to work when using notmuch over ssh Fix count of lines in hidden signatures Omit repeated subject lines in (collapsed) thread display Display current thread subject in a header line Provide a "c i" binding to copy a thread ID from the search view Allow for notmuch-fcc-dirs to have a value of nil Also, the more complex form of notmuch-fcc-dirs now has a slightly different format. It no longer has a special first-element, fallback string. Instead it's now a list of cons cells where the car of each cell is a regular expression to be matched against the sender address, and the cdr is the name of a folder to use for an FCC. So the old fallback behavior can be achieved by including a final cell of (".*" . "default-fcc-folder"). Vim interface improvements -------------------------- Felipe Contreras provided a number of updates for the vim interface These include optimizations, support for newer versions of vim, fixed support for sending mail on modern systems, new commands, and various cleanups. New bindings ------------ Added initial ruby bindings in bindings/ruby Notmuch 0.4 (2010-11-01) ======================== New command-line features ------------------------- `notmuch search --output=(summary|threads|messages|tags|files)` This new option allows for particular items to be returned from notmuch searches. The "summary" option is the default and behaves just as "notmuch search" has historically behaved. The new option values allow for thread IDs, message IDs, lists of tags, and lists of filenames to be returned from searches. It is expected that this new option will be very useful in shell scripts. For example: for file in $(notmuch search --output=files ); do "$file" done `notmuch show --format=mbox ` This new option allows for the messages matching a search specification to be presented as an mbox. Specifically the "mboxrd" format is used which allows for reversible quoting of lines beginning with "From ". A reader should remove a single '>' from the beginning of all lines beginning with one or more '>' characters followed by the 5 characters "From ". `notmuch config [get|set]
. [value ...]` The new top-level "config" command allows for any value in the notmuch configuration file to be queried or set to a new value. Both single-valued and multi-valued items are supported, as our any custom items stored in the configuration file. Avoid setting Bcc header in "notmuch reply" We decided that this was a bit heavy-handed as the actual mail user-agent should be responsible for setting any Bcc option. Also, see below for the notmuch/emacs user-agent now setting an Fcc by default rather than Bcc. New library features -------------------- Add `notmuch_query_get_query_string` and `notmuch_query_get_sort` These are simply functions for querying properties of a `notmuch_query_t` object. New emacs features ------------------ Enable Fcc of all sent messages by default (to "sent" directory) All messages sent from the emacs interface will now be saved to the notmuch mail store where they will be incorporated to the database by the next "notmuch new". By default, messages are saved to the "sent" directory at the top-level of the mail store. This directory can be customized by means of the "Notmuch Fcc Dirs" option in the notmuch customize interface. Ability to all open messages in a thread to a pipe Historically, the '|' keybinding allows for piping a single message to an external command. Now, by prefixing this key with a prefix argument, (for example, by pressing "Control-U |"), all open messages in the current thread will be sent to the external command. Optional support for detecting inline patches This hook is disabled by default but can be enabled with a checkbox under "Notmuch Show Insert Text/Plain Hook" in the notmuch customize interface. It allows for inline patches to be detected and treated as if they were attachments, (with context-sensitive highlighting). Automatically tag messages as "replied" when sending a reply Messages replied to within the emacs interface will now be tagged as "replied". This feature can easily be customized to add or remove other tags as well. For example, a user might use a tag of "needs-reply" and can configure this feature to automatically remove that tag when replying. See "Notmuch Message Mark Replied" in the notmuch customize interface. Allow search-result color specifications to overlay each other For example, one tag can specify the background color of matching lines, while another can specify the foreground. With this change, both settings will now be visible simultaneously, (which was not the case in previous releases). See "Notmuch Search Line Faces" in the notmuch customize interface. Make hidden author names still available for incremental search When there is insufficient space to display all authors of a thread in search results, the names of hidden authors are now still made available to emacs' incremental search commands. As the user searches, matching lines will temporarily expand to show the hidden names. New binding of Control-TAB (works like TAB in reverse) Many notmuch nodes already use TAB to navigate forward through various items allowing actions, (message headers, email attachments, etc.). The new Control-TAB binding operates similarly but in the opposite direction. New build-system features ------------------------- Various portability fixes have been applied These include fixes for build failures on at least Solaris, FreeBSD, and Fedora systems. We're hopeful that the notmuch code base is now more portable than ever before. Arrange for libnotmuch to be found automatically after make install The notmuch build system is now careful to help the user avoid errors of the form "libnotmuch.so could not be found" immediately after installing. This support takes two forms: 1. If the library is installed to a system directory, (configured in /etc/ld.so.conf), then "make install" will automatically run ldconfig. 2. If the library is installed to a non-system directory, the build system adds a `DR_RUNPATH` entry to the final binary pointing to the directory to which the library is installed. When this support works, the user should be able to run notmuch immediately after "make install", without any errors trying to find the notmuch library, and without having to manually set environment variables such as `LD_LIBRARY_PATH`. Check compiler/linker options before using them The configure script now carefully checks that any desired compilation options, (whether for enabling compiler warnings, or for embedding rpath, etc.), are supported. Only supported options are used in the resulting Makefile. New test-suite features ----------------------- New modularization of test suite Thanks to a gracious relicensing of the test-suite infrastructure from the git project, notmuch now has a modular test suite. This provides the ability to run individual sections of the test suite rather than the whole things. It also provides better summary of test results, with support for tests that are expected to fail (BROKEN and FIXED) in addition to PASS and FAIL. Finally, it makes it easy to run the test suite within valgrind (pass --valgrind to notmuch-test or to any sub-script) which has been very useful. New testing of emacs interface The test suite has been augmented to allow automated testing of the emacs interfaces. So far, this includes basic searches, display of threads, and tag manipulation. This also includes a test that a new message can successfully be sent out through a (dummy) SMTP server and that said message is successfully integrated into the notmuch database via the FCC setting. General bug fixes ----------------- Fix potential corruption of database when "notmuch new" is interrupted Previously, an interruption of "notmuch new" would (rarely) result in a corrupt database. The corruption would manifest itself by a persistent error of the form: document ID of 1234 has no thread ID The message-adding code has been carefully audited and reworked to avoid this sort of corruption regardless of when it is interrupted. Fix failure with extremely long message ID headers Previously, a message with an extremely long message ID, (say, more than 300 characters), would fail to be added to notmuch, (triggering Xapian exceptions). This has now been fixed. Fix for messages with "charset=unknown-8bit" Previously, messages with this charset would cause notmuch to emit a GMime warning, (which would then trip up emacs or other interfaces parsing the notmuch results). Fix `notmuch_query_search_threads` function to return NULL on any exception Fix "notmuch search" to return non-zero if `notmuch_query_search_threads` fails Previously, this command could confusingly report a Xapian exception, yet still return an error code of 0. It now correctly returns a failing error code of 1 in this case. Emacs bug fixes --------------- Fix to handle a message with a subject containing, for example "[1234]" Previously, a message subject containing a sequence of digits within square brackets would cause the emacs interface to mis-parse the output of "notmuch search". This would result in the message being mis-displayed and prevent the user from manipulating the message in the emacs interface. Fix to correctly handle message IDs containing ".." The emacs interface now properly quotes message IDs to avoid a Xapian bug in which the ".." within a message ID would be misinterpreted as a numeric range specification. Python-binding fixes -------------------- The python bindings for notmuch have been updated to work with python3. Debian-specific fixes --------------------- Fix emacs initialization so "M-x notmuch" works for users by default Now, a new Debian user can immediately run "emacs -f notmuch" after "apt-get install notmuch". Previously, the user would have had to edit the ~/.emacs file to add "(require 'notmuch)" before this would work. Notmuch 0.3.1 (2010-04-27) ========================== General bug fixes ----------------- Fix an infinite loop in "notmuch reply" This bug could be triggered by replying to a message where the user's primary email address did not appear in the To: header and the user had not configured any secondary email addresses. The bug was a simple re-use of the same iterator variable in nested loops. Fix a potential SEGV in "notmuch search" This bug could be triggered by an author name ending in a ','. Admittedly - that's almost certainly a spam email, but we never want notmuch to crash. Emacs bug fixes --------------- Fix calculations for line wrapping in the primary "notmuch" view Fix Fcc support to prompt to create a directory if the specified Fcc directory does not exist Build fix --------- Fix build on OpenSolaris (at least) due to missing 'extern "C"' block Without this, the C++ sources could not find strcasestr and the final linking of notmuch would fail. Notmuch 0.3 (2010-04-27) ======================== New command-line features ------------------------- User-configurable tags for new messages A new "new.tags" option is available in the configuration file to determine which tags are applied to new messages. Run "notmuch setup" to generate new documentation within ~/.notmuch-config on how to specify this value. Threads search results named based on subjects that match search This means that when new mails arrived to a thread you've previously read, and the new mails have a new subject, you will see that subject in the search results rather than the old subject. Faster operation of "notmuch tag" (avoid unneeded sorting) Since the user just wants to tag all matching messages, we can make things perform a bit faster by avoiding the sort. Even Better guessing of From: header for "notmuch reply" Notmuch now looks at a number of headers when trying to figure out the best From: header to use in a reply. This is helpful if you have several configured email addresses, and you also subscribe to various mailing lists with different addresses, (so that mails you are replying to won't always include your subscribed address in the To: header). Indication of author names that match a search When notmuch displays threads as the result of a search, it now lists the authors that match the search before listing the other authors in the thread. It inserts a pipe '|' symbol between the last matching and first non-matching author. This is especially useful in a search that includes tag:unread. Now the authors of the unread messages in the thread are listed first. New: Python bindings -------------------- Sebastian Spaeth has contributed his python bindings for the notmuch library to the central repository. These bindings were previously known as "cnotmuch" within python but have now been renamed to be accessible with a simple, and more official-looking "import notmuch". The bindings have already proven very useful as people proficient in python have been able to easily develop programs to do notmuch-based searches for email-address completion, maildir-flag synchronization, and other tasks. These bindings are available within the bindings/python directory, but are not yet integrated into the top-level Makefiles, nor the top-level package-building scripts. Improvements are welcome. Emacs interface improvements ---------------------------- An entirely new initial view for notmuch, (friendly yet powerful) Some of us call the new view "notmuch hello" but you can get at it by simply calling "emacs -f notmuch". The new view provides a search bar where new searches can be performed. It also displays a list of recent searches, along with a button to save any of these, giving it a new name as a "saved search". Many people find these "saved searches" one of the most convenient ways of organizing their mail, (providing all of the features of "folders" in other mail clients, but without any of the disadvantages). Finally, this view can also optionally display all of the tags that exist in the database, along with a count for each tag, and a custom search of messages with that tag that's simply a click (or keypress) away. NOTE: For users that liked the original mode of "emacs -f notmuch" immediately displaying a particular search result, we recommend instead running something like: emacs --eval '(notmuch search "tag:inbox" t)' The "t" means to sort the messages in an "oldest first" order, (as notmuch would do previously by default). You can also leave that off to have your search results in "newest first" order. Full-featured "customize" support for configuring notmuch Notmuch now plugs in well to the emacs "customize" mode to make it much simpler to find things about the notmuch interface that can be tweaked by the user. You can get to this mode by starting at the main "Customize" menu in emacs, then browsing through "Applications", "Mail", and "Notmuch". Or you can go straight to "M-x customize-group" "notmuch". Once you're at the customize screen, you'll see a list of documented options that can be manipulated along with checkboxes, drop-down selectors, and text-entry boxes for configuring the various settings. Support for doing tab-completion of email addresses This support currently relies on an external program, (notmuch-addresses), that is not yet shipped with notmuch itself. But multiple, suitable implementations of this program have already been written that generate address completions by doing notmuch searches of your email collection. For example, providing first those addresses that you have composed messages to in the past, etc. One such program (implemented in python with the python bindings to notmuch) is available via: git clone http://jkr.acm.jhu.edu/git/notmuch_addresses.git Install that program as notmuch-addresses on your PATH, and then hitting TAB on a partial email address or name within the To: or Cc: line of an email message will provide matching completions. Support for file-based (Fcc) delivery of sent messages to mail store This isn't yet enabled by default. To enable this, one will have to set the "Notmuch Fcc Dirs" setting within the notmuch customize screen, (see its documentation there for details). We anticipate making this automatic in a future release. New 'G' key binding to trigger mail refresh (G == "Get new mail") The 'G' key works wherever '=' works. Before refreshing the screen it calls an external program that can be used to poll email servers, run notmuch new and setup specific tags for the new emails. The script to be called should be configured with the "Notmuch Poll Script" setting in the customize interface. This script will typically invoke "notmuch new" and then perhaps several "notmuch tag" commands. Implement emacs message display with the JSON output from notmuch This is much more robust than the previous implementation, (where some HTML mails and mail quoting the notmuch code with the delimiter characters in it would cause the parser to fall over). Better handling of HTML messages and MIME attachments (inline images!) Allow for any MIME parts that emacs can display to be displayed inline. This includes inline viewing of image attachments, (provided the window is large enough to fit the image at its natural size). Much more robust handling of HTML messages. Currently both text/plain and text/html alternates will be rendered next to each other. In a future release, users will be able to decide to see only one or the other representation. Each attachment now has its own button so that attachments can be saved individually (the 'w' key is still available to save all attachments). Customizable support for tidying of text/plain message content Many new functions are available for tidying up message content. These include options such as wrapping long lines, compressing duplicate blank lines, etc. Most of these are disabled by default, but can easily be enabled by clicking the available check boxes under the "Notmuch Show Insert Text/Plain Hook" within the notmuch customize screen. New support for searchable citations (even when hidden) When portions of overly-long citations are hidden, the contents of these citations will still be available for emacs' standard "incremental search" functions. When the search matches any portion of a hidden citation, the citation will become visible temporarily to display the search result. More flexible handling of header visibility As an answer to complaints from many users, the To, Cc, and Date headers of messages are no longer hidden by default. For those users that liked that these were hidden, a new "Notmuch Messages Headers Visible" option in the customize interface can be set to nil. The visibility of headers can still be toggled on a per-message basis with the 'h' keybinding. For users that don't want to see some subset of those headers, the new "Notmuch Message Headers" variable can be customized to list only those headers that should be present in the display of a message. The Return key now toggles message visibility anywhere Previously this worked only on the first summary-line of a message. Customizable formatting of search results The user can easily customize the order, width, and formatting of the various fields in a "notmuch search" buffer. See the "Notmuch Search Result Format" section of the customize interface. Generate nicer names for search buffers when using a saved search Add a notmuch User-Agent header when sending mail from notmuch/emacs New keybinding (M-Ret) to open all collapsed messages in a thread New library feature ------------------- Provide a new `NOTMUCH_SORT_UNSORTED` value for queries This can be somewhat faster when sorting simply isn't desired. For example when collecting a set of messages that will all be manipulated identically, (adding a tag, removing a tag, deleting the messages), then there's no advantage to sorting the messages by date. Build fixes ----------- Fix to compile against GMime 2.6 Previously notmuch insisted on being able to find GMime 2.4, (even though GMime 2.6 would have worked all along). Fix configure script to accept (and ignore) various standard options For example, those that the Gentoo build scripts expect configure to accept are now all accepted. Test suite ---------- A large number of new tests for the many new features Better display of output from failed tests Now shows failures with diff rather than forcing the user to gaze at complete actual and expected output looking for deviation. Notmuch 0.2 (2010-04-16) ======================== This is the second release of the notmuch mail system, with actual detailed release notes this time! This release consists of a number of minor new features that make notmuch more pleasant to use, and a few fairly major bug fixes. We didn't quite hit our release target of "about a week" from the 0.1 release, (0.2 is happening 11 days after 0.1), but we hope to do better for next week. Look forward to some major features coming to notmuch in subsequent releases. -Carl General features ---------------- Better guessing of From: header Notmuch now tries harder to guess which configured address should be used as the From: line in a "notmuch reply". It will examine the Received: headers if it fails to find any configured address in To: or Cc:. This allows it to often choose the correct address even when replying to a message sent to a mailing list, and not directly to a configured address. Make "notmuch count" with no arguments count all messages Previously, it was hard to construct a search term that was guaranteed to match all messages. Provide a new special-case search term of "*" to match all messages This can be used in any command accepting a search term, such as "notmuch search '*'". Note that you'll want to take care that the shell doesn't expand * against the current files. And note that the support for "*" is a special case. It's only meaningful as a single search term and loses its special meaning when combined with any other search terms. Automatically detect thread connections even when a parent message is missing Previously, if two or more message were received with a common parent, but that parent was not received, then these messages would not be recognized as belonging to the same thread. This is now fixed so that such messages are properly connected in a thread. General bug fixes ----------------- Fix potential data loss in "notmuch new" with SIGINT One code path in "notmuch new" was not properly handling SIGINT. Previously, this could lead to messages being removed from the database (and their tags being lost) if the user pressed Control-C while "notmuch new" was working. Fix segfault when a message includes a MIME part that is empty Fix handling of non-ASCII characters with --format=json Previously, characters outside the range of 7-bit ASCII were silently dropped from the JSON output. This led to corrupted display of utf-8 content in the upcoming notmuch web-based frontends. Fix headers to be properly decoded in "notmuch reply" Previously, the user might see: Subject: Re: =?iso-8859-2?q?Rozlu=E8ka?= rather than: Subject: Re: Rozlučka The former text is properly encoded to be RFC-compliant SMTP, will be sent correctly, and will be properly decoded by the recipient. But the user trying to edit the reply would likely be unable to read or edit that field in its encoded form. Emacs client features --------------------- Show the last few lines of citations as well as the first few lines It's often the case that the last sentence of a citation is what is being replied to directly, so the last few lines are often much more important. The number of lines shown at the beginning and end of any citation can be configured, (notmuch-show-citation-lines-prefix and notmuch-show-citation-lines-suffix). The '+' and '-' commands in the search view can now add and remove tags by region Selective bulk tagging is now possible by selecting a region of threads and then using either the '+' or '-' keybindings. Bulk tagging is still available for all threads matching the current search with the '*' binding. More meaningful buffer names for thread-view buffers Notmuch now uses the Subject of the thread as the buffer name. Previously it was using the thread ID, which is a meaningless number to the user. Provide for customized colors of threads in search view based on tags See the documentation of notmuch-search-line-faces, (or us "M-x customize" and browse to the "notmuch" group within "Applications" and "Mail"), for details on how to configure this colorization. Build-system features --------------------- Add support to properly build libnotmuch on Darwin systems (OS X) Add support to configure for many standard options We include actual support for: --includedir --mandir --sysconfdir And accept and silently ignore several more: --build --infodir --libexecdir --localstatedir --disable-maintainer-mode --disable-dependency-tracking Install emacs client in "make install" rather than requiring a separate "make install-emacs" Automatically compute versions numbers between releases This support uses the git-describe notation, so a version such as 0.1-144-g43cbbfc indicates a version that is 144 commits since the 0.1 release and is available as git commit "43cbbfc". Add a new "make test" target to run the test suite and actually verify its results Notmuch 0.1 (2010-04-05) ======================== This is the first release of the notmuch mail system. It includes the libnotmuch library, the notmuch command-line interface, and an emacs-based interface to notmuch. Note: Notmuch will work best with Xapian 1.0.18 (or later) or Xapian 1.1.4 (or later). Previous versions of Xapian (whether 1.0 or 1.1) had a performance bug that made notmuch very slow when modifying tags. This would cause distracting pauses when reading mail while notmuch would wait for Xapian when removing the "inbox" and "unread" tags from messages in a thread. notmuch-0.17/README000066400000000000000000000055551226041107500140060ustar00rootroot00000000000000Notmuch - thread-based email index, search and tagging. Notmuch is a system for indexing, searching, reading, and tagging large collections of email messages in maildir or mh format. It uses the Xapian library to provide fast, full-text search with a convenient search syntax. Notmuch is free software, released under the GNU General Public License version 3 (or later). Building notmuch ---------------- See the INSTALL file for notes on compiling and installing notmuch. Running notmuch --------------- After installing notmuch, start by running "notmuch setup" which will interactively prompt for configuration information such as your name, email address, and the directory which contains your mail archive to be indexed. You can change any answers later by running "notmuch setup" again or by editing the .notmuch-config file in your home directory. With notmuch configured you should next run "notmuch new" which will index all of your existing mail. This can take a long time, (several hours) if you have a lot of email, (hundreds of thousands of files). When new mail is delivered to your mail archive in the future, you will want to run "notmuch new" again. These runs will be much faster as they will only index new messages. Finally, you can prove to yourself that things are working by running some command-line searches such as "notmuch search from:someone@example.com" or "notmuch search subject:topic". See "notmuch help search-terms" for more details on the available search syntax. The command-line search output is not expected to be particularly friendly for day-to-day usage. Instead, it is expected that you will use an email interface that builds on the notmuch command-line tool or the libnotmuch library. Notmuch installs a full-featured email interface for use within emacs. To use this, first add the following line to your .emacs file: (require 'notmuch) Then, either run "emacs -f notmuch" or execute the command "M-x notmuch" from within a running emacs. If you're interested in a non-emacs-based interface to notmuch, then please join the notmuch community. Various other interfaces are already in progress, (an interface within vim, a curses interface, graphical interfaces based on evolution, and various web-based interfaces). The authors of these interfaces would love further testing or contribution. See contact information below. Contacting users and developers ------------------------------- The website for Notmuch is: http://notmuchmail.org The mailing list address for the notmuch community is: notmuch@notmuchmail.org We welcome any sort of questions, comments, kudos, or code there. Subscription is not required, (but if you do subscribe you'll avoid any delay due to moderation). See the website for subscription information. There is also an IRC channel dedicated to talk about using and developing notmuch: IRC server: irc.freenode.net Channel: #notmuch notmuch-0.17/bindings/000077500000000000000000000000001226041107500147115ustar00rootroot00000000000000notmuch-0.17/bindings/go/000077500000000000000000000000001226041107500153165ustar00rootroot00000000000000notmuch-0.17/bindings/go/.gitignore000066400000000000000000000000321226041107500173010ustar00rootroot00000000000000src/github.com/ pkg/ bin/ notmuch-0.17/bindings/go/LICENSE000066400000000000000000000636421226041107500163360ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey 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 library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! notmuch-0.17/bindings/go/Makefile000066400000000000000000000015271226041107500167630ustar00rootroot00000000000000# Makefile for the go bindings of notmuch export GOPATH ?= $(shell pwd) export CGO_CFLAGS ?= -I../../../../lib export CGO_LDFLAGS ?= -L../../../../lib GO ?= go GOFMT ?= gofmt all: notmuch notmuch-addrlookup .PHONY: notmuch notmuch: $(GO) install notmuch .PHONY: goconfig goconfig: if [ ! -d github.com/msbranco/goconfig ]; then \ $(GO) get github.com/msbranco/goconfig; \ fi .PHONY: notmuch-addrlookup notmuch-addrlookup: notmuch goconfig $(GO) install notmuch-addrlookup .PHONY: format format: $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch $(GOFMT) -w=true $(GOFMT_OPTS) src/notmuch-addrlookup .PHONY: check-format check-format: $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch $(GOFMT) -d=true $(GOFMT_OPTS) src/notmuch-addrlookup .PHONY: clean clean: $(GO) clean notmuch $(GO) clean notmuch-addrlookup rm -rf pkg bin notmuch-0.17/bindings/go/README000066400000000000000000000004741226041107500162030ustar00rootroot00000000000000go-notmuch ========== simple go bindings to the libnotmuch library[1]. they are heavily inspired from the vala bindings from Sebastian Spaeth: https://github.com/spaetz/vala-notmuch note: the whole library hasn't been wrapped (yet?) todo ---- - improve notmuch-addrlookup regexp [1] http://notmuchmail.org/notmuch-0.17/bindings/go/src/000077500000000000000000000000001226041107500161055ustar00rootroot00000000000000notmuch-0.17/bindings/go/src/notmuch-addrlookup/000077500000000000000000000000001226041107500217245ustar00rootroot00000000000000notmuch-0.17/bindings/go/src/notmuch-addrlookup/addrlookup.go000066400000000000000000000137371226041107500244320ustar00rootroot00000000000000package main // stdlib imports import "os" import "path" import "log" import "fmt" import "regexp" import "strings" import "sort" // 3rd-party imports import "notmuch" import "github.com/msbranco/goconfig" type mail_addr_freq struct { addr string count [3]uint } type frequencies map[string]uint /* Used to sort the email addresses from most to least used */ func sort_by_freq(m1, m2 *mail_addr_freq) int { if m1.count[0] == m2.count[0] && m1.count[1] == m2.count[1] && m1.count[2] == m2.count[2] { return 0 } if m1.count[0] > m2.count[0] || m1.count[0] == m2.count[0] && m1.count[1] > m2.count[1] || m1.count[0] == m2.count[0] && m1.count[1] == m2.count[1] && m1.count[2] > m2.count[2] { return -1 } return 1 } type maddresses []*mail_addr_freq func (self *maddresses) Len() int { return len(*self) } func (self *maddresses) Less(i, j int) bool { m1 := (*self)[i] m2 := (*self)[j] v := sort_by_freq(m1, m2) if v <= 0 { return true } return false } func (self *maddresses) Swap(i, j int) { (*self)[i], (*self)[j] = (*self)[j], (*self)[i] } // find most frequent real name for each mail address func frequent_fullname(freqs frequencies) string { var maxfreq uint = 0 fullname := "" freqs_sz := len(freqs) for mail, freq := range freqs { if (freq > maxfreq && mail != "") || freqs_sz == 1 { // only use the entry if it has a real name // or if this is the only entry maxfreq = freq fullname = mail } } return fullname } func addresses_by_frequency(msgs *notmuch.Messages, name string, pass uint, addr_to_realname *map[string]*frequencies) *frequencies { freqs := make(frequencies) pattern := `\s*(("(\.|[^"])*"|[^,])*?)` // pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" + // "\\b\\w+([-+.]\\w+)*\\@\\w+[-\\.\\w]*\\.([-\\.\\w]+)*\\w\\b)>?)" pattern = `.*` + strings.ToLower(name) + `.*` var re *regexp.Regexp = nil var err error = nil if re, err = regexp.Compile(pattern); err != nil { log.Printf("error: %v\n", err) return &freqs } headers := []string{"from"} if pass == 1 { headers = append(headers, "to", "cc", "bcc") } for ; msgs.Valid(); msgs.MoveToNext() { msg := msgs.Get() //println("==> msg [", msg.GetMessageId(), "]") for _, header := range headers { froms := strings.ToLower(msg.GetHeader(header)) //println(" froms: ["+froms+"]") for _, from := range strings.Split(froms, ",") { from = strings.Trim(from, " ") match := re.FindString(from) //println(" -> match: ["+match+"]") occ, ok := freqs[match] if !ok { freqs[match] = 0 occ = 0 } freqs[match] = occ + 1 } } } return &freqs } func search_address_passes(queries [3]*notmuch.Query, name string) []string { var val []string addr_freq := make(map[string]*mail_addr_freq) addr_to_realname := make(map[string]*frequencies) var pass uint = 0 // 0-based for _, query := range queries { if query == nil { //println("**warning: idx [",idx,"] contains a nil query") continue } msgs := query.SearchMessages() ht := addresses_by_frequency(msgs, name, pass, &addr_to_realname) for addr, count := range *ht { freq, ok := addr_freq[addr] if !ok { freq = &mail_addr_freq{addr: addr, count: [3]uint{0, 0, 0}} } freq.count[pass] = count addr_freq[addr] = freq } msgs.Destroy() pass += 1 } addrs := make(maddresses, len(addr_freq)) { iaddr := 0 for _, freq := range addr_freq { addrs[iaddr] = freq iaddr += 1 } } sort.Sort(&addrs) for _, addr := range addrs { freqs, ok := addr_to_realname[addr.addr] if ok { val = append(val, frequent_fullname(*freqs)) } else { val = append(val, addr.addr) } } //println("val:",val) return val } type address_matcher struct { // the notmuch database db *notmuch.Database // full path of the notmuch database user_db_path string // user primary email user_primary_email string // user tag to mark from addresses as in the address book user_addrbook_tag string } func new_address_matcher() *address_matcher { // honor NOTMUCH_CONFIG home := os.Getenv("NOTMUCH_CONFIG") if home == "" { home = os.Getenv("HOME") } cfg, err := goconfig.ReadConfigFile(path.Join(home, ".notmuch-config")) if err != nil { log.Fatalf("error loading config file:", err) } db_path, _ := cfg.GetString("database", "path") primary_email, _ := cfg.GetString("user", "primary_email") addrbook_tag, err := cfg.GetString("user", "addrbook_tag") if err != nil { addrbook_tag = "addressbook" } self := &address_matcher{db: nil, user_db_path: db_path, user_primary_email: primary_email, user_addrbook_tag: addrbook_tag} return self } func (self *address_matcher) run(name string) { queries := [3]*notmuch.Query{} // open the database if db, status := notmuch.OpenDatabase(self.user_db_path, notmuch.DATABASE_MODE_READ_ONLY); status == notmuch.STATUS_SUCCESS { self.db = db } else { log.Fatalf("Failed to open the database: %v\n", status) } // pass 1: look at all from: addresses with the address book tag query := "tag:" + self.user_addrbook_tag if name != "" { query = query + " and from:" + name + "*" } queries[0] = self.db.CreateQuery(query) // pass 2: look at all to: addresses sent from our primary mail query = "" if name != "" { query = "to:" + name + "*" } if self.user_primary_email != "" { query = query + " from:" + self.user_primary_email } queries[1] = self.db.CreateQuery(query) // if that leads only to a few hits, we check every from too if queries[0].CountMessages()+queries[1].CountMessages() < 10 { query = "" if name != "" { query = "from:" + name + "*" } queries[2] = self.db.CreateQuery(query) } // actually retrieve and sort addresses results := search_address_passes(queries, name) for _, v := range results { if v != "" && v != "\n" { fmt.Println(v) } } return } func main() { //fmt.Println("args:",os.Args) app := new_address_matcher() name := "" if len(os.Args) > 1 { name = os.Args[1] } app.run(name) } notmuch-0.17/bindings/go/src/notmuch/000077500000000000000000000000001226041107500175625ustar00rootroot00000000000000notmuch-0.17/bindings/go/src/notmuch/notmuch.go000066400000000000000000000772211226041107500215770ustar00rootroot00000000000000// Wrapper around the notmuch library package notmuch /* #cgo LDFLAGS: -lnotmuch #include #include #include #include "notmuch.h" */ import "C" import "unsafe" // Status codes used for the return values of most functions type Status C.notmuch_status_t const ( STATUS_SUCCESS Status = iota STATUS_OUT_OF_MEMORY STATUS_READ_ONLY_DATABASE STATUS_XAPIAN_EXCEPTION STATUS_FILE_ERROR STATUS_FILE_NOT_EMAIL STATUS_DUPLICATE_MESSAGE_ID STATUS_NULL_POINTER STATUS_TAG_TOO_LONG STATUS_UNBALANCED_FREEZE_THAW STATUS_UNBALANCED_ATOMIC STATUS_LAST_STATUS ) func (self Status) String() string { var p *C.char // p is read-only p = C.notmuch_status_to_string(C.notmuch_status_t(self)) if p != nil { s := C.GoString(p) return s } return "" } /* Various opaque data types. For each notmuch__t see the various * notmuch_ functions below. */ type Database struct { db *C.notmuch_database_t } type Query struct { query *C.notmuch_query_t } type Threads struct { threads *C.notmuch_threads_t } type Thread struct { thread *C.notmuch_thread_t } type Messages struct { messages *C.notmuch_messages_t } type Message struct { message *C.notmuch_message_t } type Tags struct { tags *C.notmuch_tags_t } type Directory struct { dir *C.notmuch_directory_t } type Filenames struct { fnames *C.notmuch_filenames_t } type DatabaseMode C.notmuch_database_mode_t const ( DATABASE_MODE_READ_ONLY DatabaseMode = 0 DATABASE_MODE_READ_WRITE ) // Create a new, empty notmuch database located at 'path' func NewDatabase(path string) (*Database, Status) { var c_path *C.char = C.CString(path) defer C.free(unsafe.Pointer(c_path)) if c_path == nil { return nil, STATUS_OUT_OF_MEMORY } self := &Database{db: nil} st := Status(C.notmuch_database_create(c_path, &self.db)) if st != STATUS_SUCCESS { return nil, st } return self, st } /* Open an existing notmuch database located at 'path'. * * The database should have been created at some time in the past, * (not necessarily by this process), by calling * notmuch_database_create with 'path'. By default the database should be * opened for reading only. In order to write to the database you need to * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode. * * An existing notmuch database can be identified by the presence of a * directory named ".notmuch" below 'path'. * * The caller should call notmuch_database_destroy when finished with * this database. * * In case of any failure, this function returns NULL, (after printing * an error message on stderr). */ func OpenDatabase(path string, mode DatabaseMode) (*Database, Status) { var c_path *C.char = C.CString(path) defer C.free(unsafe.Pointer(c_path)) if c_path == nil { return nil, STATUS_OUT_OF_MEMORY } self := &Database{db: nil} st := Status(C.notmuch_database_open(c_path, C.notmuch_database_mode_t(mode), &self.db)) if st != STATUS_SUCCESS { return nil, st } return self, st } /* Close the given notmuch database, freeing all associated * resources. See notmuch_database_open. */ func (self *Database) Close() { C.notmuch_database_destroy(self.db) } /* Return the database path of the given database. */ func (self *Database) GetPath() string { /* The return value is a string owned by notmuch so should not be * modified nor freed by the caller. */ var p *C.char = C.notmuch_database_get_path(self.db) if p != nil { s := C.GoString(p) return s } return "" } /* Return the database format version of the given database. */ func (self *Database) GetVersion() uint { return uint(C.notmuch_database_get_version(self.db)) } /* Does this database need to be upgraded before writing to it? * * If this function returns TRUE then no functions that modify the * database (notmuch_database_add_message, notmuch_message_add_tag, * notmuch_directory_set_mtime, etc.) will work unless the function * notmuch_database_upgrade is called successfully first. */ func (self *Database) NeedsUpgrade() bool { do_upgrade := C.notmuch_database_needs_upgrade(self.db) if do_upgrade == 0 { return false } return true } // TODO: notmuch_database_upgrade /* Retrieve a directory object from the database for 'path'. * * Here, 'path' should be a path relative to the path of 'database' * (see notmuch_database_get_path), or else should be an absolute path * with initial components that match the path of 'database'. * * Can return NULL if a Xapian exception occurs. */ func (self *Database) GetDirectory(path string) (*Directory, Status) { var c_path *C.char = C.CString(path) defer C.free(unsafe.Pointer(c_path)) if c_path == nil { return nil, STATUS_OUT_OF_MEMORY } var c_dir *C.notmuch_directory_t st := Status(C.notmuch_database_get_directory(self.db, c_path, &c_dir)) if st != STATUS_SUCCESS || c_dir == nil { return nil, st } return &Directory{dir: c_dir}, st } /* Add a new message to the given notmuch database. * * Here,'filename' should be a path relative to the path of * 'database' (see notmuch_database_get_path), or else should be an * absolute filename with initial components that match the path of * 'database'. * * The file should be a single mail message (not a multi-message mbox) * that is expected to remain at its current location, (since the * notmuch database will reference the filename, and will not copy the * entire contents of the file. * * If 'message' is not NULL, then, on successful return '*message' * will be initialized to a message object that can be used for things * such as adding tags to the just-added message. The user should call * notmuch_message_destroy when done with the message. On any failure * '*message' will be set to NULL. * * Return value: * * NOTMUCH_STATUS_SUCCESS: Message successfully added to database. * * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, * message not added. * * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: Message has the same message * ID as another message already in the database. The new * filename was successfully added to the message in the database * (if not already present). * * NOTMUCH_STATUS_FILE_ERROR: an error occurred trying to open the * file, (such as permission denied, or file not found, * etc.). Nothing added to the database. * * NOTMUCH_STATUS_FILE_NOT_EMAIL: the contents of filename don't look * like an email message. Nothing added to the database. * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be added. */ func (self *Database) AddMessage(fname string) (*Message, Status) { var c_fname *C.char = C.CString(fname) defer C.free(unsafe.Pointer(c_fname)) if c_fname == nil { return nil, STATUS_OUT_OF_MEMORY } var c_msg *C.notmuch_message_t = new(C.notmuch_message_t) st := Status(C.notmuch_database_add_message(self.db, c_fname, &c_msg)) return &Message{message: c_msg}, st } /* Remove a message from the given notmuch database. * * Note that only this particular filename association is removed from * the database. If the same message (as determined by the message ID) * is still available via other filenames, then the message will * persist in the database for those filenames. When the last filename * is removed for a particular message, the database content for that * message will be entirely removed. * * Return value: * * NOTMUCH_STATUS_SUCCESS: The last filename was removed and the * message was removed from the database. * * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred, * message not removed. * * NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: This filename was removed but * the message persists in the database with at least one other * filename. * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be removed. */ func (self *Database) RemoveMessage(fname string) Status { var c_fname *C.char = C.CString(fname) defer C.free(unsafe.Pointer(c_fname)) if c_fname == nil { return STATUS_OUT_OF_MEMORY } st := C.notmuch_database_remove_message(self.db, c_fname) return Status(st) } /* Find a message with the given message_id. * * If the database contains a message with the given message_id, then * a new notmuch_message_t object is returned. The caller should call * notmuch_message_destroy when done with the message. * * This function returns NULL in the following situations: * * * No message is found with the given message_id * * An out-of-memory situation occurs * * A Xapian exception occurs */ func (self *Database) FindMessage(message_id string) (*Message, Status) { var c_msg_id *C.char = C.CString(message_id) defer C.free(unsafe.Pointer(c_msg_id)) if c_msg_id == nil { return nil, STATUS_OUT_OF_MEMORY } msg := &Message{message: nil} st := Status(C.notmuch_database_find_message(self.db, c_msg_id, &msg.message)) if st != STATUS_SUCCESS { return nil, st } return msg, st } /* Return a list of all tags found in the database. * * This function creates a list of all tags found in the database. The * resulting list contains all tags from all messages found in the database. * * On error this function returns NULL. */ func (self *Database) GetAllTags() *Tags { tags := C.notmuch_database_get_all_tags(self.db) if tags == nil { return nil } return &Tags{tags: tags} } /* Create a new query for 'database'. * * Here, 'database' should be an open database, (see * notmuch_database_open and notmuch_database_create). * * For the query string, we'll document the syntax here more * completely in the future, but it's likely to be a specialized * version of the general Xapian query syntax: * * http://xapian.org/docs/queryparser.html * * As a special case, passing either a length-zero string, (that is ""), * or a string consisting of a single asterisk (that is "*"), will * result in a query that returns all messages in the database. * * See notmuch_query_set_sort for controlling the order of results. * See notmuch_query_search_messages and notmuch_query_search_threads * to actually execute the query. * * User should call notmuch_query_destroy when finished with this * query. * * Will return NULL if insufficient memory is available. */ func (self *Database) CreateQuery(query string) *Query { var c_query *C.char = C.CString(query) defer C.free(unsafe.Pointer(c_query)) if c_query == nil { return nil } q := C.notmuch_query_create(self.db, c_query) if q == nil { return nil } return &Query{query: q} } /* Sort values for notmuch_query_set_sort */ type Sort C.notmuch_sort_t const ( SORT_OLDEST_FIRST Sort = 0 SORT_NEWEST_FIRST SORT_MESSAGE_ID SORT_UNSORTED ) /* Return the query_string of this query. See notmuch_query_create. */ func (self *Query) String() string { // FIXME: do we own 'q' or not ? q := C.notmuch_query_get_query_string(self.query) //defer C.free(unsafe.Pointer(q)) if q != nil { s := C.GoString(q) return s } return "" } /* Specify the sorting desired for this query. */ func (self *Query) SetSort(sort Sort) { C.notmuch_query_set_sort(self.query, C.notmuch_sort_t(sort)) } /* Return the sort specified for this query. See notmuch_query_set_sort. */ func (self *Query) GetSort() Sort { return Sort(C.notmuch_query_get_sort(self.query)) } /* Execute a query for threads, returning a notmuch_threads_t object * which can be used to iterate over the results. The returned threads * object is owned by the query and as such, will only be valid until * notmuch_query_destroy. * * Typical usage might be: * * notmuch_query_t *query; * notmuch_threads_t *threads; * notmuch_thread_t *thread; * * query = notmuch_query_create (database, query_string); * * for (threads = notmuch_query_search_threads (query); * notmuch_threads_valid (threads); * notmuch_threads_move_to_next (threads)) * { * thread = notmuch_threads_get (threads); * .... * notmuch_thread_destroy (thread); * } * * notmuch_query_destroy (query); * * Note: If you are finished with a thread before its containing * query, you can call notmuch_thread_destroy to clean up some memory * sooner (as in the above example). Otherwise, if your thread objects * are long-lived, then you don't need to call notmuch_thread_destroy * and all the memory will still be reclaimed when the query is * destroyed. * * Note that there's no explicit destructor needed for the * notmuch_threads_t object. (For consistency, we do provide a * notmuch_threads_destroy function, but there's no good reason * to call it if the query is about to be destroyed). * * If a Xapian exception occurs this function will return NULL. */ func (self *Query) SearchThreads() *Threads { threads := C.notmuch_query_search_threads(self.query) if threads == nil { return nil } return &Threads{threads: threads} } /* Execute a query for messages, returning a notmuch_messages_t object * which can be used to iterate over the results. The returned * messages object is owned by the query and as such, will only be * valid until notmuch_query_destroy. * * Typical usage might be: * * notmuch_query_t *query; * notmuch_messages_t *messages; * notmuch_message_t *message; * * query = notmuch_query_create (database, query_string); * * for (messages = notmuch_query_search_messages (query); * notmuch_messages_valid (messages); * notmuch_messages_move_to_next (messages)) * { * message = notmuch_messages_get (messages); * .... * notmuch_message_destroy (message); * } * * notmuch_query_destroy (query); * * Note: If you are finished with a message before its containing * query, you can call notmuch_message_destroy to clean up some memory * sooner (as in the above example). Otherwise, if your message * objects are long-lived, then you don't need to call * notmuch_message_destroy and all the memory will still be reclaimed * when the query is destroyed. * * Note that there's no explicit destructor needed for the * notmuch_messages_t object. (For consistency, we do provide a * notmuch_messages_destroy function, but there's no good * reason to call it if the query is about to be destroyed). * * If a Xapian exception occurs this function will return NULL. */ func (self *Query) SearchMessages() *Messages { msgs := C.notmuch_query_search_messages(self.query) if msgs == nil { return nil } return &Messages{messages: msgs} } /* Destroy a notmuch_query_t along with any associated resources. * * This will in turn destroy any notmuch_threads_t and * notmuch_messages_t objects generated by this query, (and in * turn any notmuch_thread_t and notmuch_message_t objects generated * from those results, etc.), if such objects haven't already been * destroyed. */ func (self *Query) Destroy() { if self.query != nil { C.notmuch_query_destroy(self.query) } } /* Return an estimate of the number of messages matching a search * * This function performs a search and returns Xapian's best * guess as to number of matching messages. * * If a Xapian exception occurs, this function may return 0 (after * printing a message). */ func (self *Query) CountMessages() uint { return uint(C.notmuch_query_count_messages(self.query)) } // TODO: wrap threads and thread /* Is the given 'threads' iterator pointing at a valid thread. * * When this function returns TRUE, notmuch_threads_get will return a * valid object. Whereas when this function returns FALSE, * notmuch_threads_get will return NULL. * * See the documentation of notmuch_query_search_threads for example * code showing how to iterate over a notmuch_threads_t object. */ func (self *Threads) Valid() bool { if self.threads == nil { return false } valid := C.notmuch_threads_valid(self.threads) if valid == 0 { return false } return true } /* Destroy a notmuch_threads_t object. * * It's not strictly necessary to call this function. All memory from * the notmuch_threads_t object will be reclaimed when the * containg query object is destroyed. */ func (self *Threads) Destroy() { if self.threads != nil { C.notmuch_threads_destroy(self.threads) } } /* Is the given 'messages' iterator pointing at a valid message. * * When this function returns TRUE, notmuch_messages_get will return a * valid object. Whereas when this function returns FALSE, * notmuch_messages_get will return NULL. * * See the documentation of notmuch_query_search_messages for example * code showing how to iterate over a notmuch_messages_t object. */ func (self *Messages) Valid() bool { if self.messages == nil { return false } valid := C.notmuch_messages_valid(self.messages) if valid == 0 { return false } return true } /* Get the current message from 'messages' as a notmuch_message_t. * * Note: The returned message belongs to 'messages' and has a lifetime * identical to it (and the query to which it belongs). * * See the documentation of notmuch_query_search_messages for example * code showing how to iterate over a notmuch_messages_t object. * * If an out-of-memory situation occurs, this function will return * NULL. */ func (self *Messages) Get() *Message { if self.messages == nil { return nil } msg := C.notmuch_messages_get(self.messages) if msg == nil { return nil } return &Message{message: msg} } /* Move the 'messages' iterator to the next message. * * If 'messages' is already pointing at the last message then the * iterator will be moved to a point just beyond that last message, * (where notmuch_messages_valid will return FALSE and * notmuch_messages_get will return NULL). * * See the documentation of notmuch_query_search_messages for example * code showing how to iterate over a notmuch_messages_t object. */ func (self *Messages) MoveToNext() { if self.messages == nil { return } C.notmuch_messages_move_to_next(self.messages) } /* Destroy a notmuch_messages_t object. * * It's not strictly necessary to call this function. All memory from * the notmuch_messages_t object will be reclaimed when the containing * query object is destroyed. */ func (self *Messages) Destroy() { if self.messages != nil { C.notmuch_messages_destroy(self.messages) } } /* Return a list of tags from all messages. * * The resulting list is guaranteed not to contain duplicated tags. * * WARNING: You can no longer iterate over messages after calling this * function, because the iterator will point at the end of the list. * We do not have a function to reset the iterator yet and the only * way how you can iterate over the list again is to recreate the * message list. * * The function returns NULL on error. */ func (self *Messages) CollectTags() *Tags { if self.messages == nil { return nil } tags := C.notmuch_messages_collect_tags(self.messages) if tags == nil { return nil } return &Tags{tags: tags} } /* Get the message ID of 'message'. * * The returned string belongs to 'message' and as such, should not be * modified by the caller and will only be valid for as long as the * message is valid, (which is until the query from which it derived * is destroyed). * * This function will not return NULL since Notmuch ensures that every * message has a unique message ID, (Notmuch will generate an ID for a * message if the original file does not contain one). */ func (self *Message) GetMessageId() string { if self.message == nil { return "" } id := C.notmuch_message_get_message_id(self.message) // we dont own id // defer C.free(unsafe.Pointer(id)) if id == nil { return "" } return C.GoString(id) } /* Get the thread ID of 'message'. * * The returned string belongs to 'message' and as such, should not be * modified by the caller and will only be valid for as long as the * message is valid, (for example, until the user calls * notmuch_message_destroy on 'message' or until a query from which it * derived is destroyed). * * This function will not return NULL since Notmuch ensures that every * message belongs to a single thread. */ func (self *Message) GetThreadId() string { if self.message == nil { return "" } id := C.notmuch_message_get_thread_id(self.message) // we dont own id // defer C.free(unsafe.Pointer(id)) if id == nil { return "" } return C.GoString(id) } /* Get a notmuch_messages_t iterator for all of the replies to * 'message'. * * Note: This call only makes sense if 'message' was ultimately * obtained from a notmuch_thread_t object, (such as by coming * directly from the result of calling notmuch_thread_get_ * toplevel_messages or by any number of subsequent * calls to notmuch_message_get_replies). * * If 'message' was obtained through some non-thread means, (such as * by a call to notmuch_query_search_messages), then this function * will return NULL. * * If there are no replies to 'message', this function will return * NULL. (Note that notmuch_messages_valid will accept that NULL * value as legitimate, and simply return FALSE for it.) */ func (self *Message) GetReplies() *Messages { if self.message == nil { return nil } msgs := C.notmuch_message_get_replies(self.message) if msgs == nil { return nil } return &Messages{messages: msgs} } /* Get a filename for the email corresponding to 'message'. * * The returned filename is an absolute filename, (the initial * component will match notmuch_database_get_path() ). * * The returned string belongs to the message so should not be * modified or freed by the caller (nor should it be referenced after * the message is destroyed). * * Note: If this message corresponds to multiple files in the mail * store, (that is, multiple files contain identical message IDs), * this function will arbitrarily return a single one of those * filenames. */ func (self *Message) GetFileName() string { if self.message == nil { return "" } fname := C.notmuch_message_get_filename(self.message) // we dont own fname // defer C.free(unsafe.Pointer(fname)) if fname == nil { return "" } return C.GoString(fname) } type Flag C.notmuch_message_flag_t const ( MESSAGE_FLAG_MATCH Flag = 0 ) /* Get a value of a flag for the email corresponding to 'message'. */ func (self *Message) GetFlag(flag Flag) bool { if self.message == nil { return false } v := C.notmuch_message_get_flag(self.message, C.notmuch_message_flag_t(flag)) if v == 0 { return false } return true } /* Set a value of a flag for the email corresponding to 'message'. */ func (self *Message) SetFlag(flag Flag, value bool) { if self.message == nil { return } var v C.notmuch_bool_t = 0 if value { v = 1 } C.notmuch_message_set_flag(self.message, C.notmuch_message_flag_t(flag), v) } // TODO: wrap notmuch_message_get_date /* Get the value of the specified header from 'message'. * * The value will be read from the actual message file, not from the * notmuch database. The header name is case insensitive. * * The returned string belongs to the message so should not be * modified or freed by the caller (nor should it be referenced after * the message is destroyed). * * Returns an empty string ("") if the message does not contain a * header line matching 'header'. Returns NULL if any error occurs. */ func (self *Message) GetHeader(header string) string { if self.message == nil { return "" } var c_header *C.char = C.CString(header) defer C.free(unsafe.Pointer(c_header)) /* we dont own value */ value := C.notmuch_message_get_header(self.message, c_header) if value == nil { return "" } return C.GoString(value) } /* Get the tags for 'message', returning a notmuch_tags_t object which * can be used to iterate over all tags. * * The tags object is owned by the message and as such, will only be * valid for as long as the message is valid, (which is until the * query from which it derived is destroyed). * * Typical usage might be: * * notmuch_message_t *message; * notmuch_tags_t *tags; * const char *tag; * * message = notmuch_database_find_message (database, message_id); * * for (tags = notmuch_message_get_tags (message); * notmuch_tags_valid (tags); * notmuch_result_move_to_next (tags)) * { * tag = notmuch_tags_get (tags); * .... * } * * notmuch_message_destroy (message); * * Note that there's no explicit destructor needed for the * notmuch_tags_t object. (For consistency, we do provide a * notmuch_tags_destroy function, but there's no good reason to call * it if the message is about to be destroyed). */ func (self *Message) GetTags() *Tags { if self.message == nil { return nil } tags := C.notmuch_message_get_tags(self.message) if tags == nil { return nil } return &Tags{tags: tags} } /* The longest possible tag value. */ const TAG_MAX = 200 /* Add a tag to the given message. * * Return value: * * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message * * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL * * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long * (exceeds NOTMUCH_TAG_MAX) * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so message cannot be modified. */ func (self *Message) AddTag(tag string) Status { if self.message == nil { return STATUS_NULL_POINTER } c_tag := C.CString(tag) defer C.free(unsafe.Pointer(c_tag)) return Status(C.notmuch_message_add_tag(self.message, c_tag)) } /* Remove a tag from the given message. * * Return value: * * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message * * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL * * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long * (exceeds NOTMUCH_TAG_MAX) * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so message cannot be modified. */ func (self *Message) RemoveTag(tag string) Status { if self.message == nil { return STATUS_NULL_POINTER } c_tag := C.CString(tag) defer C.free(unsafe.Pointer(c_tag)) return Status(C.notmuch_message_remove_tag(self.message, c_tag)) } /* Remove all tags from the given message. * * See notmuch_message_freeze for an example showing how to safely * replace tag values. * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so message cannot be modified. */ func (self *Message) RemoveAllTags() Status { if self.message == nil { return STATUS_NULL_POINTER } return Status(C.notmuch_message_remove_all_tags(self.message)) } /* Freeze the current state of 'message' within the database. * * This means that changes to the message state, (via * notmuch_message_add_tag, notmuch_message_remove_tag, and * notmuch_message_remove_all_tags), will not be committed to the * database until the message is thawed with notmuch_message_thaw. * * Multiple calls to freeze/thaw are valid and these calls will * "stack". That is there must be as many calls to thaw as to freeze * before a message is actually thawed. * * The ability to do freeze/thaw allows for safe transactions to * change tag values. For example, explicitly setting a message to * have a given set of tags might look like this: * * notmuch_message_freeze (message); * * notmuch_message_remove_all_tags (message); * * for (i = 0; i < NUM_TAGS; i++) * notmuch_message_add_tag (message, tags[i]); * * notmuch_message_thaw (message); * * With freeze/thaw used like this, the message in the database is * guaranteed to have either the full set of original tag values, or * the full set of new tag values, but nothing in between. * * Imagine the example above without freeze/thaw and the operation * somehow getting interrupted. This could result in the message being * left with no tags if the interruption happened after * notmuch_message_remove_all_tags but before notmuch_message_add_tag. * * Return value: * * NOTMUCH_STATUS_SUCCESS: Message successfully frozen. * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so message cannot be modified. */ func (self *Message) Freeze() Status { if self.message == nil { return STATUS_NULL_POINTER } return Status(C.notmuch_message_freeze(self.message)) } /* Thaw the current 'message', synchronizing any changes that may have * occurred while 'message' was frozen into the notmuch database. * * See notmuch_message_freeze for an example of how to use this * function to safely provide tag changes. * * Multiple calls to freeze/thaw are valid and these calls with * "stack". That is there must be as many calls to thaw as to freeze * before a message is actually thawed. * * Return value: * * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least * its frozen count has successfully been reduced by 1). * * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: An attempt was made to thaw * an unfrozen message. That is, there have been an unbalanced * number of calls to notmuch_message_freeze and * notmuch_message_thaw. */ func (self *Message) Thaw() Status { if self.message == nil { return STATUS_NULL_POINTER } return Status(C.notmuch_message_thaw(self.message)) } /* Destroy a notmuch_message_t object. * * It can be useful to call this function in the case of a single * query object with many messages in the result, (such as iterating * over the entire database). Otherwise, it's fine to never call this * function and there will still be no memory leaks. (The memory from * the messages get reclaimed when the containing query is destroyed.) */ func (self *Message) Destroy() { if self.message == nil { return } C.notmuch_message_destroy(self.message) } /* Is the given 'tags' iterator pointing at a valid tag. * * When this function returns TRUE, notmuch_tags_get will return a * valid string. Whereas when this function returns FALSE, * notmuch_tags_get will return NULL. * * See the documentation of notmuch_message_get_tags for example code * showing how to iterate over a notmuch_tags_t object. */ func (self *Tags) Valid() bool { if self.tags == nil { return false } v := C.notmuch_tags_valid(self.tags) if v == 0 { return false } return true } /* Get the current tag from 'tags' as a string. * * Note: The returned string belongs to 'tags' and has a lifetime * identical to it (and the query to which it ultimately belongs). * * See the documentation of notmuch_message_get_tags for example code * showing how to iterate over a notmuch_tags_t object. */ func (self *Tags) Get() string { if self.tags == nil { return "" } s := C.notmuch_tags_get(self.tags) // we dont own 's' return C.GoString(s) } func (self *Tags) String() string { return self.Get() } /* Move the 'tags' iterator to the next tag. * * If 'tags' is already pointing at the last tag then the iterator * will be moved to a point just beyond that last tag, (where * notmuch_tags_valid will return FALSE and notmuch_tags_get will * return NULL). * * See the documentation of notmuch_message_get_tags for example code * showing how to iterate over a notmuch_tags_t object. */ func (self *Tags) MoveToNext() { if self.tags == nil { return } C.notmuch_tags_move_to_next(self.tags) } /* Destroy a notmuch_tags_t object. * * It's not strictly necessary to call this function. All memory from * the notmuch_tags_t object will be reclaimed when the containing * message or query objects are destroyed. */ func (self *Tags) Destroy() { if self.tags == nil { return } C.notmuch_tags_destroy(self.tags) } // TODO: wrap notmuch_directory_ /* Destroy a notmuch_directory_t object. */ func (self *Directory) Destroy() { if self.dir == nil { return } C.notmuch_directory_destroy(self.dir) } // TODO: wrap notmuch_filenames_ /* Destroy a notmuch_filenames_t object. * * It's not strictly necessary to call this function. All memory from * the notmuch_filenames_t object will be reclaimed when the * containing directory object is destroyed. * * It is acceptable to pass NULL for 'filenames', in which case this * function will do nothing. */ func (self *Filenames) Destroy() { if self.fnames == nil { return } C.notmuch_filenames_destroy(self.fnames) } /* EOF */ notmuch-0.17/bindings/python/000077500000000000000000000000001226041107500162325ustar00rootroot00000000000000notmuch-0.17/bindings/python/.gitignore000066400000000000000000000000471226041107500202230ustar00rootroot00000000000000*.py[co] /docs/build /docs/html build/ notmuch-0.17/bindings/python/MANIFEST.in000066400000000000000000000000561226041107500177710ustar00rootroot00000000000000include notmuch #recursive-include docs/html *notmuch-0.17/bindings/python/README000066400000000000000000000055211226041107500171150ustar00rootroot00000000000000notmuch -- The python interface to notmuch.so ============================================== This module makes the functionality of the notmuch library (`http://notmuchmail.org`_) available to python. Successful import of this modul depends on a libnotmuch.so|dll being available on the user's system. If you have downloaded the full source tarball, you can create the documentation with sphinx installed, go to the docs directory and "make html". A static version of the documentation is available at: http://packages.python.org/notmuch/ The current source code is being hosted at http://bitbucket.org/spaetz/cnotmuch which also provides an issue tracker, and release downloads. This package is tracked by the python package index repository at `http://pypi.python.org/pypi/notmuch`_ and can thus be installed on a user's computer easily via "sudo easy_install notmuch" (you will still need to install the notmuch shared library separately as it is not included in this package). The original source has been provided by (c)Sebastian Spaeth, 2010. All code is available under the GNU GPLv3+ (see docs/COPYING) unless specified otherwise. INSTALLATION & DEINSTALL ------------------------ The notmuch python module is available on pypi.python.org. This means you can do "easy_install notmuch" on your linux box and it will get installed into: /usr/local/lib/python2.x/dist-packages/ For uninstalling, you'll need to remove the "notmuch-0.4-py2.x.egg" (or similar) directory and delete one entry in the "easy-install.pth" file in that directory. It needs to have a libnotmuch.so or libnotmuch.so.1 available in some library folder or will raise an exception when loading. "OSError: libnotmuch.so.1: cannot open shared object file: No such file or directory" Usage ----- For more examples of how to use the notmuch interface, have a look at the notmuch "binary" and the generated documentation. Example session: >>>import notmuch >>>db = notmuch.Database("/home/spaetz/mail") db.get_path() '/home/spaetz/mail' >>>tags = db.get_all_tags() >>>for tag in tags: >>> print tag inbox ... maildir::draft #--------------------------------------------- q = notmuch.Query(db,'from:Sebastian') count = len(q.search_messages()) 1300 #--------------------------------------------- >>>db = notmuch.Database("/home/spaetz/mailHAHA") NotmuchError: Could not open the specified database #--------------------------------------------- >>>tags = notmuch.Database("/home/spaetz/mail").get_all_tags() >>>del(tags) Building for a Debian package ------------------------------ dpkg-buildpackage -i"\.hg|\/build" Changelog --------- 0.1 First public release 0.1.1 Fixed Database.create_query() 0.2.0 Implemented Thread() and Threads() methods 0.2.1 Implemented the remaining API methods, notably Directory() and Filenames() 0.2.2 Bug fixes 0.3.0 Incorporated in the notmuchmail.org git repositorynotmuch-0.17/bindings/python/debian/000077500000000000000000000000001226041107500174545ustar00rootroot00000000000000notmuch-0.17/bindings/python/debian/changelog000066400000000000000000000002121226041107500213210ustar00rootroot00000000000000cnotmuch (0.2.1-1) karmic; urgency=low * Initial release -- Sebastian Spaeth Tue, 30 Mar 2010 11:31:39 +0200 notmuch-0.17/bindings/python/debian/compat000066400000000000000000000000021226041107500206520ustar00rootroot000000000000007 notmuch-0.17/bindings/python/debian/control000066400000000000000000000013771226041107500210670ustar00rootroot00000000000000Source: cnotmuch Section: python Priority: extra Maintainer: Sebastian Spaeth Build-Depends: debhelper (>= 7.3.0), python-support (>= 0.5.3) Build-Depends-Indep: python (>= 2.5), python-support Standards-Version: 3.8.1 Homepage: http://pypi.python.org/pypi/cnotmuch Package: cnotmuch Architecture: all XB-Python-Version: ${python:Versions} Depends: ${misc:Depends},${python:Depends}, notmuch (> 0.0+201001211401) Description: Interface to the notmuch mail search and index library The cnotmuch module provides an interface to the notmuch functionality, directly interfacing with a shared notmuch library. Notmuch provides a maildatabase that allows for extremely quick searching and filtering of your email according to various criteria. notmuch-0.17/bindings/python/debian/copyright000066400000000000000000000025631226041107500214150ustar00rootroot00000000000000This package was debianized by: Sebastian Spaeth on Tue, 30 Mar 2010 10:02:22 +0200 It was downloaded from: http://pypi.python.org/packages/source/c/cnotmuch/cnotmuch-0.2.1.tar.gz Upstream Author(s): Sebastian Spaeth Copyright: Copyright (C) 2010 Sebastian Spaeth License: This package 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 2 of the License, or (at your option) any later version. This package 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian systems, the complete text of the GNU General Public License version 2 can be found in `/usr/share/common-licenses/GPL-2'. The Debian packaging is: Copyright (C) 2010 Sebastian Spaeth and is licensed under the GPL version 3, see `/usr/share/common-licenses/GPL-3'. notmuch-0.17/bindings/python/debian/docs000066400000000000000000000000071226041107500203240ustar00rootroot00000000000000README notmuch-0.17/bindings/python/debian/rules000077500000000000000000000000361226041107500205330ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ notmuch-0.17/bindings/python/docs/000077500000000000000000000000001226041107500171625ustar00rootroot00000000000000notmuch-0.17/bindings/python/docs/COPYING000066400000000000000000001045131226041107500202210ustar00rootroot00000000000000 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 . notmuch-0.17/bindings/python/docs/Makefile000066400000000000000000000056221226041107500206270ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf build/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html @echo @echo "Build finished. The HTML pages are in html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml @echo @echo "Build finished. The HTML pages are in build/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in build/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in build/qthelp, like this:" @echo "# qcollectiongenerator build/qthelp/pyDNS.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile build/qthelp/pyDNS.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex @echo @echo "Build finished; the LaTeX files are in build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes @echo @echo "The overview file is in build/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in build/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in build/doctest/output.txt." notmuch-0.17/bindings/python/docs/source/000077500000000000000000000000001226041107500204625ustar00rootroot00000000000000notmuch-0.17/bindings/python/docs/source/conf.py000066400000000000000000000154771226041107500217770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # pyDNS documentation build configuration file, created by # sphinx-quickstart on Tue Feb 2 10:00:47 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0,os.path.abspath('../..')) class Mock(object): def __init__(self, *args, **kwargs): pass def __call__(self, *args, **kwargs): return Mock() @classmethod def __getattr__(self, name): return Mock() if name not in ('__file__', '__path__') else '/dev/null' MOCK_MODULES = [ 'ctypes', ] for mod_name in MOCK_MODULES: sys.modules[mod_name] = Mock() from notmuch import __VERSION__,__AUTHOR__ # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo'] autoclass_content = "both" # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'notmuch' copyright = u'2010-2012, ' + __AUTHOR__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __VERSION__ # The full version, including alpha/beta/rc tags. release = __VERSION__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['../html'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. html_use_modindex = False # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'notmuchdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'notmuch.tex', u'notmuch Documentation', u'notmuch contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} notmuch-0.17/bindings/python/docs/source/database.rst000066400000000000000000000017401226041107500227620ustar00rootroot00000000000000:class:`Database` -- The underlying notmuch database ==================================================== .. currentmodule:: notmuch .. autoclass:: Database([path=None[, create=False[, mode=MODE.READ_ONLY]]]) .. automethod:: create .. automethod:: open(path, status=MODE.READ_ONLY) .. automethod:: close .. automethod:: get_path .. automethod:: get_version .. automethod:: needs_upgrade .. automethod:: upgrade .. automethod:: begin_atomic .. automethod:: end_atomic .. automethod:: get_directory .. automethod:: add_message .. automethod:: remove_message .. automethod:: find_message .. automethod:: find_message_by_filename .. automethod:: get_all_tags .. automethod:: create_query .. attribute:: Database.MODE Defines constants that are used as the mode in which to open a database. MODE.READ_ONLY Open the database in read-only mode MODE.READ_WRITE Open the database in read-write mode notmuch-0.17/bindings/python/docs/source/filesystem.rst000066400000000000000000000011631226041107500234010ustar00rootroot00000000000000Files and directories ===================== .. currentmodule:: notmuch :class:`Filenames` -- An iterator over filenames ------------------------------------------------ .. autoclass:: Filenames .. automethod:: Filenames.__len__ :class:`Directoy` -- A directory entry in the database ------------------------------------------------------ .. autoclass:: Directory .. automethod:: Directory.get_child_files .. automethod:: Directory.get_child_directories .. automethod:: Directory.get_mtime .. automethod:: Directory.set_mtime .. autoattribute:: Directory.mtime .. autoattribute:: Directory.path notmuch-0.17/bindings/python/docs/source/index.rst000066400000000000000000000014461226041107500223300ustar00rootroot00000000000000Welcome to :mod:`notmuch`'s documentation ========================================= .. currentmodule:: notmuch The :mod:`notmuch` module provides an interface to the `notmuch `_ functionality, directly interfacing to a shared notmuch library. Within :mod:`notmuch`, the classes :class:`Database`, :class:`Query` provide most of the core functionality, returning :class:`Threads`, :class:`Messages` and :class:`Tags`. .. moduleauthor:: Sebastian Spaeth :License: This module is covered under the GNU GPL v3 (or later). .. toctree:: :maxdepth: 1 quickstart notes status_and_errors database query messages message tags threads thread filesystem Indices and tables ================== * :ref:`genindex` * :ref:`search` notmuch-0.17/bindings/python/docs/source/message.rst000066400000000000000000000016601226041107500226430ustar00rootroot00000000000000:class:`Message` -- A single message ==================================== .. currentmodule:: notmuch .. autoclass:: Message .. automethod:: get_message_id .. automethod:: get_thread_id .. automethod:: get_replies .. automethod:: get_filename .. automethod:: get_filenames .. attribute:: FLAG FLAG.MATCH This flag is automatically set by a Query.search_threads on those messages that match the query. This allows us to distinguish matches from the rest of the messages in that thread. .. automethod:: get_flag .. automethod:: set_flag .. automethod:: get_date .. automethod:: get_header .. automethod:: get_tags .. automethod:: maildir_flags_to_tags .. automethod:: tags_to_maildir_flags .. automethod:: remove_tag .. automethod:: add_tag .. automethod:: remove_all_tags .. automethod:: freeze .. automethod:: thaw .. automethod:: __str__ notmuch-0.17/bindings/python/docs/source/messages.rst000066400000000000000000000006141226041107500230240ustar00rootroot00000000000000:class:`Messages` -- A bunch of messages ======================================== .. currentmodule:: notmuch .. autoclass:: Messages .. automethod:: collect_tags .. method:: __len__() .. warning:: :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke list(Messages()). Use the :meth:`Query.count_messages` function or use `len(list(msgs))`. notmuch-0.17/bindings/python/docs/source/notes.rst000066400000000000000000000002011226041107500223350ustar00rootroot00000000000000Interfacing with notmuch ======================== .. todo:: move the note about talloc out of the code .. automodule:: notmuch notmuch-0.17/bindings/python/docs/source/query.rst000066400000000000000000000016321226041107500223630ustar00rootroot00000000000000:class:`Query` -- A search query ================================ .. currentmodule:: notmuch .. autoclass:: Query .. automethod:: create .. attribute:: Query.SORT Defines constants that are used as the mode in which to open a database. SORT.OLDEST_FIRST Sort by message date, oldest first. SORT.NEWEST_FIRST Sort by message date, newest first. SORT.MESSAGE_ID Sort by email message ID. SORT.UNSORTED Do not apply a special sort order (returns results in document id order). .. automethod:: set_sort .. attribute:: sort Instance attribute :attr:`sort` contains the sort order (see :attr:`Query.SORT`) if explicitely specified via :meth:`set_sort`. By default it is set to `None`. .. automethod:: search_threads .. automethod:: search_messages .. automethod:: count_messages .. automethod:: count_threads notmuch-0.17/bindings/python/docs/source/quickstart.rst000066400000000000000000000005341226041107500234100ustar00rootroot00000000000000Quickstart and examples ======================= .. todo:: write a nice introduction .. todo:: improve the examples Notmuch can be imported as:: import notmuch or:: from notmuch import Query, Database db = Database('path', create=True) msgs = Query(db, 'from:myself').search_messages() for msg in msgs: print(msg) notmuch-0.17/bindings/python/docs/source/status_and_errors.rst000066400000000000000000000040761226041107500247640ustar00rootroot00000000000000.. currentmodule:: notmuch Status and Errors ================= Some methods return a status, indicating if an operation was successful and what the error was. Most of these status codes are expressed as a specific value, the :class:`notmuch.STATUS`. .. note:: Prior to version 0.12 the exception classes and the enumeration :class:`notmuch.STATUS` were defined in `notmuch.globals`. They have since then been moved into `notmuch.errors`. :class:`STATUS` -- Notmuch operation return value -------------------------------------------------- .. autoclass:: notmuch.STATUS :inherited-members: .. automethod:: notmuch.STATUS.status2str :exc:`NotmuchError` -- A Notmuch execution error ------------------------------------------------ Whenever an error occurs, we throw a special Exception :exc:`NotmuchError`, or a more fine grained Exception which is derived from it. This means it is always safe to check for NotmuchErrors if you want to catch all errors. If you are interested in more fine grained exceptions, you can use those below. .. autoexception:: NotmuchError The following exceptions are all directly derived from NotmuchError. Each of them corresponds to a specific :class:`notmuch.STATUS` value. You can either check the :attr:`status` attribute of a NotmuchError to see if a specific error has occurred, or you can directly check for the following Exception types: .. autoexception:: OutOfMemoryError(message=None) :members: .. autoexception:: ReadOnlyDatabaseError(message=None) :members: .. autoexception:: XapianError(message=None) :members: .. autoexception:: FileError(message=None) :members: .. autoexception:: FileNotEmailError(message=None) :members: .. autoexception:: DuplicateMessageIdError(message=None) :members: .. autoexception:: NullPointerError(message=None) :members: .. autoexception:: TagTooLongError(message=None) :members: .. autoexception:: UnbalancedFreezeThawError(message=None) :members: .. autoexception:: UnbalancedAtomicError(message=None) :members: .. autoexception:: NotInitializedError(message=None) :members: notmuch-0.17/bindings/python/docs/source/tags.rst000066400000000000000000000006241226041107500221540ustar00rootroot00000000000000:class:`Tags` -- Notmuch tags ----------------------------- .. currentmodule:: notmuch .. autoclass:: Tags :members: .. method:: __len__ .. warning:: :meth:`__len__` was removed in version 0.6 as it exhausted the iterator and broke list(Tags()). Use :meth:`len(list(msgs))` instead if you need to know the number of tags. .. automethod:: __str__ notmuch-0.17/bindings/python/docs/source/thread.rst000066400000000000000000000007141226041107500224650ustar00rootroot00000000000000:class:`Thread` -- A single thread ================================== .. currentmodule:: notmuch .. autoclass:: Thread .. automethod:: get_thread_id .. automethod:: get_total_messages .. automethod:: get_toplevel_messages .. automethod:: get_matched_messages .. automethod:: get_authors .. automethod:: get_subject .. automethod:: get_oldest_date .. automethod:: get_newest_date .. automethod:: get_tags .. automethod:: __str__ notmuch-0.17/bindings/python/docs/source/threads.rst000066400000000000000000000002661226041107500226520ustar00rootroot00000000000000:class:`Threads` -- Threads iterator ==================================== .. currentmodule:: notmuch .. autoclass:: Threads .. automethod:: __len__ .. automethod:: __str__ notmuch-0.17/bindings/python/notmuch/000077500000000000000000000000001226041107500177075ustar00rootroot00000000000000notmuch-0.17/bindings/python/notmuch/__init__.py000066400000000000000000000055121226041107500220230ustar00rootroot00000000000000"""The :mod:`notmuch` module provides most of the functionality that a user is likely to need. .. note:: The underlying notmuch library is build on a hierarchical memory allocator called talloc. All objects derive from a top-level :class:`Database` object. This means that as soon as an object is deleted, all underlying derived objects such as Queries, Messages, Message, and Tags will be freed by the underlying library as well. Accessing these objects will then lead to segfaults and other unexpected behavior. We implement reference counting, so that parent objects can be automatically freed when they are not needed anymore. For example:: db = Database('path',create=True) msgs = Query(db,'from:myself').search_messages() This returns :class:`Messages` which internally contains a reference to its parent :class:`Query` object. Otherwise the Query() would be immediately freed, taking our *msgs* down with it. In this case, the above Query() object will be automatically freed whenever we delete all derived objects, ie in our case: `del(msgs)` would also delete the parent Query. It would not delete the parent Database() though, as that is still referenced from the variable *db* in which it is stored. Pretty much the same is valid for all other objects in the hierarchy, such as :class:`Query`, :class:`Messages`, :class:`Message`, and :class:`Tags`. """ """ This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010-2011 Sebastian Spaeth """ from .database import Database from .directory import Directory from .filenames import Filenames from .message import Message from .messages import Messages from .query import Query from .tag import Tags from .thread import Thread from .threads import Threads from .globals import nmlib from .errors import ( STATUS, NotmuchError, OutOfMemoryError, ReadOnlyDatabaseError, XapianError, FileError, FileNotEmailError, DuplicateMessageIdError, NullPointerError, TagTooLongError, UnbalancedFreezeThawError, UnbalancedAtomicError, NotInitializedError, ) from .version import __VERSION__ __LICENSE__ = "GPL v3+" __AUTHOR__ = 'Sebastian Spaeth ' notmuch-0.17/bindings/python/notmuch/compat.py000066400000000000000000000041651226041107500215520ustar00rootroot00000000000000''' This file is part of notmuch. This module handles differences between python2.x and python3.x and allows the notmuch bindings to support both version families with one source tree. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth Copyright 2012 Justus Winter <4winter@informatik.uni-hamburg.de> ''' import sys if sys.version_info[0] == 2: from ConfigParser import SafeConfigParser class Python3StringMixIn(object): def __str__(self): return unicode(self).encode('utf-8') def encode_utf8(value): ''' Ensure a nicely utf-8 encoded string to pass to wrapped libnotmuch functions. C++ code expects strings to be well formatted and unicode strings to have no null bytes. ''' if not isinstance(value, basestring): raise TypeError('Expected str or unicode, got %s' % type(value)) if isinstance(value, unicode): return value.encode('utf-8', 'replace') return value else: from configparser import SafeConfigParser class Python3StringMixIn(object): def __str__(self): return self.__unicode__() def encode_utf8(value): ''' Ensure a nicely utf-8 encoded string to pass to wrapped libnotmuch functions. C++ code expects strings to be well formatted and unicode strings to have no null bytes. ''' if not isinstance(value, str): raise TypeError('Expected str, got %s' % type(value)) return value.encode('utf-8', 'replace') notmuch-0.17/bindings/python/notmuch/database.py000066400000000000000000000546571226041107500220460ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ import os import codecs from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER from .compat import SafeConfigParser from .globals import ( nmlib, Enum, _str, NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP, ) from .errors import ( STATUS, FileError, NotmuchError, NullPointerError, NotInitializedError, ReadOnlyDatabaseError, ) from .message import Message from .tag import Tags from .query import Query from .directory import Directory class Database(object): """The :class:`Database` is the highest-level object that notmuch provides. It references a notmuch database, and can be opened in read-only or read-write mode. A :class:`Query` can be derived from or be applied to a specific database to find messages. Also adding and removing messages to the database happens via this object. Modifications to the database are not atmic by default (see :meth:`begin_atomic`) and once a database has been modified, all other database objects pointing to the same data-base will throw an :exc:`XapianError` as the underlying database has been modified. Close and reopen the database to continue working with it. :class:`Database` objects implement the context manager protocol so you can use the :keyword:`with` statement to ensure that the database is properly closed. See :meth:`close` for more information. .. note:: Any function in this class can and will throw an :exc:`NotInitializedError` if the database was not intitialized properly. """ _std_db_path = None """Class attribute to cache user's default database""" MODE = Enum(['READ_ONLY', 'READ_WRITE']) """Constants: Mode in which to open the database""" """notmuch_database_get_directory""" _get_directory = nmlib.notmuch_database_get_directory _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)] _get_directory.restype = c_uint """notmuch_database_get_path""" _get_path = nmlib.notmuch_database_get_path _get_path.argtypes = [NotmuchDatabaseP] _get_path.restype = c_char_p """notmuch_database_get_version""" _get_version = nmlib.notmuch_database_get_version _get_version.argtypes = [NotmuchDatabaseP] _get_version.restype = c_uint """notmuch_database_open""" _open = nmlib.notmuch_database_open _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)] _open.restype = c_uint """notmuch_database_upgrade""" _upgrade = nmlib.notmuch_database_upgrade _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p] _upgrade.restype = c_uint """ notmuch_database_find_message""" _find_message = nmlib.notmuch_database_find_message _find_message.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchMessageP)] _find_message.restype = c_uint """notmuch_database_find_message_by_filename""" _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchMessageP)] _find_message_by_filename.restype = c_uint """notmuch_database_get_all_tags""" _get_all_tags = nmlib.notmuch_database_get_all_tags _get_all_tags.argtypes = [NotmuchDatabaseP] _get_all_tags.restype = NotmuchTagsP """notmuch_database_create""" _create = nmlib.notmuch_database_create _create.argtypes = [c_char_p, POINTER(NotmuchDatabaseP)] _create.restype = c_uint def __init__(self, path = None, create = False, mode = MODE.READ_ONLY): """If *path* is `None`, we will try to read a users notmuch configuration and use his configured database. The location of the configuration file can be specified through the environment variable *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`. If *create* is `True`, the database will always be created in :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY. :param path: Directory to open/create the database in (see above for behavior if `None`) :type path: `str` or `None` :param create: Pass `False` to open an existing, `True` to create a new database. :type create: bool :param mode: Mode to open a database in. Is always :attr:`MODE`.READ_WRITE when creating a new one. :type mode: :attr:`MODE` :raises: :exc:`NotmuchError` or derived exception in case of failure. """ self._db = None self.mode = mode if path is None: # no path specified. use a user's default database if Database._std_db_path is None: #the following line throws a NotmuchError if it fails Database._std_db_path = self._get_user_default_db() path = Database._std_db_path if create == False: self.open(path, mode) else: self.create(path) _destroy = nmlib.notmuch_database_destroy _destroy.argtypes = [NotmuchDatabaseP] _destroy.restype = None def __del__(self): if self._db: self._destroy(self._db) def _assert_db_is_initialized(self): """Raises :exc:`NotInitializedError` if self._db is `None`""" if not self._db: raise NotInitializedError() def create(self, path): """Creates a new notmuch database This function is used by __init__() and usually does not need to be called directly. It wraps the underlying *notmuch_database_create* function and creates a new notmuch database at *path*. It will always return a database in :attr:`MODE` .READ_WRITE mode as creating an empty database for reading only does not make a great deal of sense. :param path: A directory in which we should create the database. :type path: str :raises: :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ if self._db: raise NotmuchError(message="Cannot create db, this Database() " "already has an open one.") db = NotmuchDatabaseP() status = Database._create(_str(path), byref(db)) if status != STATUS.SUCCESS: raise NotmuchError(status) self._db = db return status def open(self, path, mode=0): """Opens an existing database This function is used by __init__() and usually does not need to be called directly. It wraps the underlying *notmuch_database_open* function. :param status: Open the database in read-only or read-write mode :type status: :attr:`MODE` :raises: Raises :exc:`NotmuchError` in case of any failure (possibly after printing an error message on stderr). """ db = NotmuchDatabaseP() status = Database._open(_str(path), mode, byref(db)) if status != STATUS.SUCCESS: raise NotmuchError(status) self._db = db return status _close = nmlib.notmuch_database_close _close.argtypes = [NotmuchDatabaseP] _close.restype = None def close(self): ''' Closes the notmuch database. .. warning:: This function closes the notmuch database. From that point on every method invoked on any object ever derived from the closed database may cease to function and raise a NotmuchError. ''' if self._db: self._close(self._db) def __enter__(self): ''' Implements the context manager protocol. ''' return self def __exit__(self, exc_type, exc_value, traceback): ''' Implements the context manager protocol. ''' self.close() def get_path(self): """Returns the file path of an open database""" self._assert_db_is_initialized() return Database._get_path(self._db).decode('utf-8') def get_version(self): """Returns the database format version :returns: The database version as positive integer """ self._assert_db_is_initialized() return Database._get_version(self._db) _needs_upgrade = nmlib.notmuch_database_needs_upgrade _needs_upgrade.argtypes = [NotmuchDatabaseP] _needs_upgrade.restype = bool def needs_upgrade(self): """Does this database need to be upgraded before writing to it? If this function returns `True` then no functions that modify the database (:meth:`add_message`, :meth:`Message.add_tag`, :meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade` is called successfully first. :returns: `True` or `False` """ self._assert_db_is_initialized() return self._needs_upgrade(self._db) def upgrade(self): """Upgrades the current database After opening a database in read-write mode, the client should check if an upgrade is needed (notmuch_database_needs_upgrade) and if so, upgrade with this function before making any modifications. NOT IMPLEMENTED: The optional progress_notify callback can be used by the caller to provide progress indication to the user. If non-NULL it will be called periodically with 'progress' as a floating-point value in the range of [0.0..1.0] indicating the progress made so far in the upgrade process. :TODO: catch exceptions, document return values and etc... """ self._assert_db_is_initialized() status = Database._upgrade(self._db, None, None) #TODO: catch exceptions, document return values and etc return status _begin_atomic = nmlib.notmuch_database_begin_atomic _begin_atomic.argtypes = [NotmuchDatabaseP] _begin_atomic.restype = c_uint def begin_atomic(self): """Begin an atomic database operation Any modifications performed between a successful :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to the database atomically. Note that, unlike a typical database transaction, this only ensures atomicity, not durability; neither begin nor end necessarily flush modifications to disk. :returns: :attr:`STATUS`.SUCCESS or raises :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION Xapian exception occurred; atomic section not entered. *Added in notmuch 0.9*""" self._assert_db_is_initialized() status = self._begin_atomic(self._db) if status != STATUS.SUCCESS: raise NotmuchError(status) return status _end_atomic = nmlib.notmuch_database_end_atomic _end_atomic.argtypes = [NotmuchDatabaseP] _end_atomic.restype = c_uint def end_atomic(self): """Indicate the end of an atomic database operation See :meth:`begin_atomic` for details. :returns: :attr:`STATUS`.SUCCESS or raises :raises: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION A Xapian exception occurred; atomic section not ended. :attr:`STATUS`.UNBALANCED_ATOMIC: end_atomic has been called more times than begin_atomic. *Added in notmuch 0.9*""" self._assert_db_is_initialized() status = self._end_atomic(self._db) if status != STATUS.SUCCESS: raise NotmuchError(status) return status def get_directory(self, path): """Returns a :class:`Directory` of path, :param path: An unicode string containing the path relative to the path of database (see :meth:`get_path`), or else should be an absolute path with initial components that match the path of 'database'. :returns: :class:`Directory` or raises an exception. :raises: :exc:`FileError` if path is not relative database or absolute with initial components same as database. """ self._assert_db_is_initialized() # sanity checking if path is valid, and make path absolute if path and path[0] == os.sep: # we got an absolute path if not path.startswith(self.get_path()): # but its initial components are not equal to the db path raise FileError('Database().get_directory() called ' 'with a wrong absolute path') abs_dirpath = path else: #we got a relative path, make it absolute abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path)) dir_p = NotmuchDirectoryP() status = Database._get_directory(self._db, _str(path), byref(dir_p)) if status != STATUS.SUCCESS: raise NotmuchError(status) if not dir_p: return None # return the Directory, init it with the absolute path return Directory(abs_dirpath, dir_p, self) _add_message = nmlib.notmuch_database_add_message _add_message.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchMessageP)] _add_message.restype = c_uint def add_message(self, filename, sync_maildir_flags=False): """Adds a new message to the database :param filename: should be a path relative to the path of the open database (see :meth:`get_path`), or else should be an absolute filename with initial components that match the path of the database. The file should be a single mail message (not a multi-message mbox) that is expected to remain at its current location, since the notmuch database will reference the filename, and will not copy the entire contents of the file. :param sync_maildir_flags: If the message contains Maildir flags, we will -depending on the notmuch configuration- sync those tags to initial notmuch tags, if set to `True`. It is `False` by default to remain consistent with the libnotmuch API. You might want to look into the underlying method :meth:`Message.maildir_flags_to_tags`. :returns: On success, we return 1) a :class:`Message` object that can be used for things such as adding tags to the just-added message. 2) one of the following :attr:`STATUS` values: :attr:`STATUS`.SUCCESS Message successfully added to database. :attr:`STATUS`.DUPLICATE_MESSAGE_ID Message has the same message ID as another message already in the database. The new filename was successfully added to the list of the filenames for the existing message. :rtype: 2-tuple(:class:`Message`, :attr:`STATUS`) :raises: Raises a :exc:`NotmuchError` with the following meaning. If such an exception occurs, nothing was added to the database. :attr:`STATUS`.FILE_ERROR An error occurred trying to open the file, (such as permission denied, or file not found, etc.). :attr:`STATUS`.FILE_NOT_EMAIL The contents of filename don't look like an email message. :attr:`STATUS`.READ_ONLY_DATABASE Database was opened in read-only mode so no message can be added. """ self._assert_db_is_initialized() msg_p = NotmuchMessageP() status = self._add_message(self._db, _str(filename), byref(msg_p)) if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]: raise NotmuchError(status) #construct Message() and return msg = Message(msg_p, self) #automatic sync initial tags from Maildir flags if sync_maildir_flags: msg.maildir_flags_to_tags() return (msg, status) _remove_message = nmlib.notmuch_database_remove_message _remove_message.argtypes = [NotmuchDatabaseP, c_char_p] _remove_message.restype = c_uint def remove_message(self, filename): """Removes a message (filename) from the given notmuch database Note that only this particular filename association is removed from the database. If the same message (as determined by the message ID) is still available via other filenames, then the message will persist in the database for those filenames. When the last filename is removed for a particular message, the database content for that message will be entirely removed. :returns: A :attr:`STATUS` value with the following meaning: :attr:`STATUS`.SUCCESS The last filename was removed and the message was removed from the database. :attr:`STATUS`.DUPLICATE_MESSAGE_ID This filename was removed but the message persists in the database with at least one other filename. :raises: Raises a :exc:`NotmuchError` with the following meaning. If such an exception occurs, nothing was removed from the database. :attr:`STATUS`.READ_ONLY_DATABASE Database was opened in read-only mode so no message can be removed. """ self._assert_db_is_initialized() return self._remove_message(self._db, _str(filename)) def find_message(self, msgid): """Returns a :class:`Message` as identified by its message ID Wraps the underlying *notmuch_database_find_message* function. :param msgid: The message ID :type msgid: unicode or str :returns: :class:`Message` or `None` if no message is found. :raises: :exc:`OutOfMemoryError` If an Out-of-memory occured while constructing the message. :exc:`XapianError` In case of a Xapian Exception. These exceptions include "Database modified" situations, e.g. when the notmuch database has been modified by another program in the meantime. In this case, you should close and reopen the database and retry. :exc:`NotInitializedError` if the database was not intitialized. """ self._assert_db_is_initialized() msg_p = NotmuchMessageP() status = Database._find_message(self._db, _str(msgid), byref(msg_p)) if status != STATUS.SUCCESS: raise NotmuchError(status) return msg_p and Message(msg_p, self) or None def find_message_by_filename(self, filename): """Find a message with the given filename :returns: If the database contains a message with the given filename, then a class:`Message:` is returned. This function returns None if no message is found with the given filename. :raises: :exc:`OutOfMemoryError` if an Out-of-memory occured while constructing the message. :raises: :exc:`XapianError` in case of a Xapian Exception. These exceptions include "Database modified" situations, e.g. when the notmuch database has been modified by another program in the meantime. In this case, you should close and reopen the database and retry. :raises: :exc:`NotInitializedError` if the database was not intitialized. *Added in notmuch 0.9*""" self._assert_db_is_initialized() msg_p = NotmuchMessageP() status = Database._find_message_by_filename(self._db, _str(filename), byref(msg_p)) if status != STATUS.SUCCESS: raise NotmuchError(status) return msg_p and Message(msg_p, self) or None def get_all_tags(self): """Returns :class:`Tags` with a list of all tags found in the database :returns: :class:`Tags` :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER on error """ self._assert_db_is_initialized() tags_p = Database._get_all_tags(self._db) if not tags_p: raise NullPointerError() return Tags(tags_p, self) def create_query(self, querystring): """Returns a :class:`Query` derived from this database This is a shorthand method for doing:: # short version # Automatically frees the Database() when 'q' is deleted q = Database(dbpath).create_query('from:"Biene Maja"') # long version, which is functionally equivalent but will keep the # Database in the 'db' variable around after we delete 'q': db = Database(dbpath) q = Query(db,'from:"Biene Maja"') This function is a python extension and not in the underlying C API. """ return Query(self, querystring) def __repr__(self): return "'Notmuch DB " + self.get_path() + "'" def _get_user_default_db(self): """ Reads a user's notmuch config and returns his db location Throws a NotmuchError if it cannot find it""" config = SafeConfigParser() conf_f = os.getenv('NOTMUCH_CONFIG', os.path.expanduser('~/.notmuch-config')) config.readfp(codecs.open(conf_f, 'r', 'utf-8')) if not config.has_option('database', 'path'): raise NotmuchError(message="No DB path specified" " and no user default found") return config.get('database', 'path') notmuch-0.17/bindings/python/notmuch/directory.py000066400000000000000000000153261226041107500222740ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_uint, c_long from .globals import ( nmlib, NotmuchDirectoryP, NotmuchFilenamesP ) from .errors import ( STATUS, NotmuchError, NotInitializedError, ) from .filenames import Filenames class Directory(object): """Represents a directory entry in the notmuch directory Modifying attributes of this object will modify the database, not the real directory attributes. The Directory object is usually derived from another object e.g. via :meth:`Database.get_directory`, and will automatically be become invalid whenever that parent is deleted. You should therefore initialized this object handing it a reference to the parent, preventing the parent from automatically being garbage collected. """ """notmuch_directory_get_mtime""" _get_mtime = nmlib.notmuch_directory_get_mtime _get_mtime.argtypes = [NotmuchDirectoryP] _get_mtime.restype = c_long """notmuch_directory_set_mtime""" _set_mtime = nmlib.notmuch_directory_set_mtime _set_mtime.argtypes = [NotmuchDirectoryP, c_long] _set_mtime.restype = c_uint """notmuch_directory_get_child_files""" _get_child_files = nmlib.notmuch_directory_get_child_files _get_child_files.argtypes = [NotmuchDirectoryP] _get_child_files.restype = NotmuchFilenamesP """notmuch_directory_get_child_directories""" _get_child_directories = nmlib.notmuch_directory_get_child_directories _get_child_directories.argtypes = [NotmuchDirectoryP] _get_child_directories.restype = NotmuchFilenamesP def _assert_dir_is_initialized(self): """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED) if dir_p is None""" if not self._dir_p: raise NotInitializedError() def __init__(self, path, dir_p, parent): """ :param path: The absolute path of the directory object. :param dir_p: The pointer to an internal notmuch_directory_t object. :param parent: The object this Directory is derived from (usually a :class:`Database`). We do not directly use this, but store a reference to it as long as this Directory object lives. This keeps the parent object alive. """ self._path = path self._dir_p = dir_p self._parent = parent def set_mtime(self, mtime): """Sets the mtime value of this directory in the database The intention is for the caller to use the mtime to allow efficient identification of new messages to be added to the database. The recommended usage is as follows: * Read the mtime of a directory from the filesystem * Call :meth:`Database.add_message` for all mail files in the directory * Call notmuch_directory_set_mtime with the mtime read from the filesystem. Then, when wanting to check for updates to the directory in the future, the client can call :meth:`get_mtime` and know that it only needs to add files if the mtime of the directory and files are newer than the stored timestamp. .. note:: :meth:`get_mtime` function does not allow the caller to distinguish a timestamp of 0 from a non-existent timestamp. So don't store a timestamp of 0 unless you are comfortable with that. :param mtime: A (time_t) timestamp :raises: :exc:`XapianError` a Xapian exception occurred, mtime not stored :raises: :exc:`ReadOnlyDatabaseError` the database was opened in read-only mode so directory mtime cannot be modified :raises: :exc:`NotInitializedError` the directory object has not been initialized """ self._assert_dir_is_initialized() status = Directory._set_mtime(self._dir_p, mtime) if status != STATUS.SUCCESS: raise NotmuchError(status) def get_mtime(self): """Gets the mtime value of this directory in the database Retrieves a previously stored mtime for this directory. :param mtime: A (time_t) timestamp :raises: :exc:`NotmuchError`: :attr:`STATUS`.NOT_INITIALIZED The directory has not been initialized """ self._assert_dir_is_initialized() return Directory._get_mtime(self._dir_p) # Make mtime attribute a property of Directory() mtime = property(get_mtime, set_mtime, doc="""Property that allows getting and setting of the Directory *mtime* (read-write) See :meth:`get_mtime` and :meth:`set_mtime` for usage and possible exceptions.""") def get_child_files(self): """Gets a Filenames iterator listing all the filenames of messages in the database within the given directory. The returned filenames will be the basename-entries only (not complete paths. """ self._assert_dir_is_initialized() files_p = Directory._get_child_files(self._dir_p) return Filenames(files_p, self) def get_child_directories(self): """Gets a :class:`Filenames` iterator listing all the filenames of sub-directories in the database within the given directory The returned filenames will be the basename-entries only (not complete paths. """ self._assert_dir_is_initialized() files_p = Directory._get_child_directories(self._dir_p) return Filenames(files_p, self) @property def path(self): """Returns the absolute path of this Directory (read-only)""" return self._path def __repr__(self): """Object representation""" return "" % self._path _destroy = nmlib.notmuch_directory_destroy _destroy.argtypes = [NotmuchDirectoryP] _destroy.restype = None def __del__(self): """Close and free the Directory""" if self._dir_p: self._destroy(self._dir_p) notmuch-0.17/bindings/python/notmuch/errors.py000066400000000000000000000134721226041107500216040ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_char_p, c_int from .globals import ( nmlib, Enum, Python3StringMixIn, ) class Status(Enum): """Enum with a string representation of a notmuch_status_t value.""" _status2str = nmlib.notmuch_status_to_string _status2str.restype = c_char_p _status2str.argtypes = [c_int] def __init__(self, statuslist): """It is initialized with a list of strings that are available as Status().string1 - Status().stringn attributes. """ super(Status, self).__init__(statuslist) @classmethod def status2str(self, status): """Get a (unicode) string representation of a notmuch_status_t value.""" # define strings for custom error messages if status == STATUS.NOT_INITIALIZED: return "Operation on uninitialized object impossible." return unicode(Status._status2str(status)) STATUS = Status(['SUCCESS', 'OUT_OF_MEMORY', 'READ_ONLY_DATABASE', 'XAPIAN_EXCEPTION', 'FILE_ERROR', 'FILE_NOT_EMAIL', 'DUPLICATE_MESSAGE_ID', 'NULL_POINTER', 'TAG_TOO_LONG', 'UNBALANCED_FREEZE_THAW', 'UNBALANCED_ATOMIC', 'NOT_INITIALIZED']) """STATUS is a class, whose attributes provide constants that serve as return indicators for notmuch functions. Currently the following ones are defined. For possible return values and specific meaning for each method, see the method description. * SUCCESS * OUT_OF_MEMORY * READ_ONLY_DATABASE * XAPIAN_EXCEPTION * FILE_ERROR * FILE_NOT_EMAIL * DUPLICATE_MESSAGE_ID * NULL_POINTER * TAG_TOO_LONG * UNBALANCED_FREEZE_THAW * UNBALANCED_ATOMIC * NOT_INITIALIZED Invoke the class method `notmuch.STATUS.status2str` with a status value as argument to receive a human readable string""" STATUS.__name__ = 'STATUS' class NotmuchError(Exception, Python3StringMixIn): """Is initiated with a (notmuch.STATUS[, message=None]). It will not return an instance of the class NotmuchError, but a derived instance of a more specific Error Message, e.g. OutOfMemoryError. Each status but SUCCESS has a corresponding subclassed Exception.""" @classmethod def get_exc_subclass(cls, status): """Returns a fine grained Exception() type, detailing the error status""" subclasses = { STATUS.OUT_OF_MEMORY: OutOfMemoryError, STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError, STATUS.XAPIAN_EXCEPTION: XapianError, STATUS.FILE_ERROR: FileError, STATUS.FILE_NOT_EMAIL: FileNotEmailError, STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError, STATUS.NULL_POINTER: NullPointerError, STATUS.TAG_TOO_LONG: TagTooLongError, STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError, STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError, STATUS.NOT_INITIALIZED: NotInitializedError, } assert 0 < status <= len(subclasses) return subclasses[status] def __new__(cls, *args, **kwargs): """Return a correct subclass of NotmuchError if needed We return a NotmuchError instance if status is None (or 0) and a subclass that inherits from NotmuchError depending on the 'status' parameter otherwise.""" # get 'status'. Passed in as arg or kwarg? status = args[0] if len(args) else kwargs.get('status', None) # no 'status' or cls is subclass already, return 'cls' instance if not status or cls != NotmuchError: return super(NotmuchError, cls).__new__(cls) subclass = cls.get_exc_subclass(status) # which class to use? return subclass.__new__(subclass, *args, **kwargs) def __init__(self, status=None, message=None): self.status = status self.message = message def __unicode__(self): if self.message is not None: return self.message elif self.status is not None: return STATUS.status2str(self.status) else: return 'Unknown error' # List of Subclassed exceptions that correspond to STATUS values and are # subclasses of NotmuchError. class OutOfMemoryError(NotmuchError): status = STATUS.OUT_OF_MEMORY class ReadOnlyDatabaseError(NotmuchError): status = STATUS.READ_ONLY_DATABASE class XapianError(NotmuchError): status = STATUS.XAPIAN_EXCEPTION class FileError(NotmuchError): status = STATUS.FILE_ERROR class FileNotEmailError(NotmuchError): status = STATUS.FILE_NOT_EMAIL class DuplicateMessageIdError(NotmuchError): status = STATUS.DUPLICATE_MESSAGE_ID class NullPointerError(NotmuchError): status = STATUS.NULL_POINTER class TagTooLongError(NotmuchError): status = STATUS.TAG_TOO_LONG class UnbalancedFreezeThawError(NotmuchError): status = STATUS.UNBALANCED_FREEZE_THAW class UnbalancedAtomicError(NotmuchError): status = STATUS.UNBALANCED_ATOMIC class NotInitializedError(NotmuchError): """Derived from NotmuchError, this occurs if the underlying data structure (e.g. database is not initialized (yet) or an iterator has been exhausted. You can test for NotmuchError with .status = STATUS.NOT_INITIALIZED""" status = STATUS.NOT_INITIALIZED notmuch-0.17/bindings/python/notmuch/filenames.py000066400000000000000000000112711226041107500222260ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_char_p from .globals import ( nmlib, NotmuchMessageP, NotmuchFilenamesP, Python3StringMixIn, ) from .errors import ( NullPointerError, NotInitializedError, ) class Filenames(Python3StringMixIn): """Represents a list of filenames as returned by notmuch Objects of this class implement the iterator protocol. .. note:: The underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of tags, and a subsequent iteration attempt will raise a :exc:`NotInitializedError`. Also note, that any function that uses iteration (nearly all) will also exhaust the tags. So both:: for name in filenames: print name as well as:: number_of_names = len(names) and even a simple:: #str() iterates over all tags to construct a space separated list print(str(filenames)) will "exhaust" the Filenames. However, you can use :meth:`Message.get_filenames` repeatedly to get fresh Filenames objects to perform various actions on filenames. """ #notmuch_filenames_get _get = nmlib.notmuch_filenames_get _get.argtypes = [NotmuchFilenamesP] _get.restype = c_char_p def __init__(self, files_p, parent): """ :param files_p: A pointer to an underlying *notmuch_tags_t* structure. These are not publically exposed, so a user will almost never instantiate a :class:`Tags` object herself. They are usually handed back as a result, e.g. in :meth:`Database.get_all_tags`. *tags_p* must be valid, we will raise an :exc:`NullPointerError` if it is `None`. :type files_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Message` these filenames are derived from, and saves a reference to it, so we can automatically delete the db object once all derived objects are dead. """ if not files_p: raise NullPointerError() self._files_p = files_p #save reference to parent object so we keep it alive self._parent = parent def __iter__(self): """ Make Filenames an iterator """ return self _valid = nmlib.notmuch_filenames_valid _valid.argtypes = [NotmuchFilenamesP] _valid.restype = bool _move_to_next = nmlib.notmuch_filenames_move_to_next _move_to_next.argtypes = [NotmuchFilenamesP] _move_to_next.restype = None def __next__(self): if not self._files_p: raise NotInitializedError() if not self._valid(self._files_p): self._files_p = None raise StopIteration file_ = Filenames._get(self._files_p) self._move_to_next(self._files_p) return file_.decode('utf-8', 'ignore') next = __next__ # python2.x iterator protocol compatibility def __unicode__(self): """Represent Filenames() as newline-separated list of full paths .. note:: This method exhausts the iterator object, so you will not be able to iterate over them again. """ return "\n".join(self) _destroy = nmlib.notmuch_filenames_destroy _destroy.argtypes = [NotmuchMessageP] _destroy.restype = None def __del__(self): """Close and free the notmuch filenames""" if self._files_p: self._destroy(self._files_p) def __len__(self): """len(:class:`Filenames`) returns the number of contained files .. note:: This method exhausts the iterator object, so you will not be able to iterate over them again. """ if not self._files_p: raise NotInitializedError() i = 0 while self._valid(self._files_p): self._move_to_next(self._files_p) i += 1 self._files_p = None return i notmuch-0.17/bindings/python/notmuch/globals.py000066400000000000000000000041611226041107500217060ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import CDLL, Structure, POINTER #----------------------------------------------------------------------------- #package-global instance of the notmuch library try: from os import uname if uname()[0] == 'Darwin': nmlib = CDLL("libnotmuch.3.dylib") else: nmlib = CDLL("libnotmuch.so.3") except: raise ImportError("Could not find shared 'notmuch' library.") from .compat import Python3StringMixIn, encode_utf8 as _str class Enum(object): """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc...""" def __init__(self, names): for number, name in enumerate(names): setattr(self, name, number) class NotmuchDatabaseS(Structure): pass NotmuchDatabaseP = POINTER(NotmuchDatabaseS) class NotmuchQueryS(Structure): pass NotmuchQueryP = POINTER(NotmuchQueryS) class NotmuchThreadsS(Structure): pass NotmuchThreadsP = POINTER(NotmuchThreadsS) class NotmuchThreadS(Structure): pass NotmuchThreadP = POINTER(NotmuchThreadS) class NotmuchMessagesS(Structure): pass NotmuchMessagesP = POINTER(NotmuchMessagesS) class NotmuchMessageS(Structure): pass NotmuchMessageP = POINTER(NotmuchMessageS) class NotmuchTagsS(Structure): pass NotmuchTagsP = POINTER(NotmuchTagsS) class NotmuchDirectoryS(Structure): pass NotmuchDirectoryP = POINTER(NotmuchDirectoryS) class NotmuchFilenamesS(Structure): pass NotmuchFilenamesP = POINTER(NotmuchFilenamesS) notmuch-0.17/bindings/python/notmuch/message.py000066400000000000000000000560151226041107500217140ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth Jesse Rosenthal """ from ctypes import c_char_p, c_long, c_uint, c_int from datetime import date from .globals import ( nmlib, Enum, _str, Python3StringMixIn, NotmuchTagsP, NotmuchMessageP, NotmuchMessagesP, NotmuchFilenamesP, ) from .errors import ( STATUS, NotmuchError, NullPointerError, NotInitializedError, ) from .tag import Tags from .filenames import Filenames import email class Message(Python3StringMixIn): """Represents a single Email message Technically, this wraps the underlying *notmuch_message_t* structure. A user will usually not create these objects themselves but get them as search results. As it implements :meth:`__cmp__`, it is possible to compare two :class:`Message`\s using `if msg1 == msg2: ...`. """ """notmuch_message_get_filename (notmuch_message_t *message)""" _get_filename = nmlib.notmuch_message_get_filename _get_filename.argtypes = [NotmuchMessageP] _get_filename.restype = c_char_p """return all filenames for a message""" _get_filenames = nmlib.notmuch_message_get_filenames _get_filenames.argtypes = [NotmuchMessageP] _get_filenames.restype = NotmuchFilenamesP """notmuch_message_get_flag""" _get_flag = nmlib.notmuch_message_get_flag _get_flag.argtypes = [NotmuchMessageP, c_uint] _get_flag.restype = bool """notmuch_message_set_flag""" _set_flag = nmlib.notmuch_message_set_flag _set_flag.argtypes = [NotmuchMessageP, c_uint, c_int] _set_flag.restype = None """notmuch_message_get_message_id (notmuch_message_t *message)""" _get_message_id = nmlib.notmuch_message_get_message_id _get_message_id.argtypes = [NotmuchMessageP] _get_message_id.restype = c_char_p """notmuch_message_get_thread_id""" _get_thread_id = nmlib.notmuch_message_get_thread_id _get_thread_id.argtypes = [NotmuchMessageP] _get_thread_id.restype = c_char_p """notmuch_message_get_replies""" _get_replies = nmlib.notmuch_message_get_replies _get_replies.argtypes = [NotmuchMessageP] _get_replies.restype = NotmuchMessagesP """notmuch_message_get_tags (notmuch_message_t *message)""" _get_tags = nmlib.notmuch_message_get_tags _get_tags.argtypes = [NotmuchMessageP] _get_tags.restype = NotmuchTagsP _get_date = nmlib.notmuch_message_get_date _get_date.argtypes = [NotmuchMessageP] _get_date.restype = c_long _get_header = nmlib.notmuch_message_get_header _get_header.argtypes = [NotmuchMessageP, c_char_p] _get_header.restype = c_char_p """notmuch_status_t ..._maildir_flags_to_tags (notmuch_message_t *)""" _tags_to_maildir_flags = nmlib.notmuch_message_tags_to_maildir_flags _tags_to_maildir_flags.argtypes = [NotmuchMessageP] _tags_to_maildir_flags.restype = c_int """notmuch_status_t ..._tags_to_maildir_flags (notmuch_message_t *)""" _maildir_flags_to_tags = nmlib.notmuch_message_maildir_flags_to_tags _maildir_flags_to_tags.argtypes = [NotmuchMessageP] _maildir_flags_to_tags.restype = c_int #Constants: Flags that can be set/get with set_flag FLAG = Enum(['MATCH']) def __init__(self, msg_p, parent=None): """ :param msg_p: A pointer to an internal notmuch_message_t Structure. If it is `None`, we will raise an :exc:`NullPointerError`. :param parent: A 'parent' object is passed which this message is derived from. We save a reference to it, so we can automatically delete the parent object once all derived objects are dead. """ if not msg_p: raise NullPointerError() self._msg = msg_p #keep reference to parent, so we keep it alive self._parent = parent def get_message_id(self): """Returns the message ID :returns: String with a message ID :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() return Message._get_message_id(self._msg).decode('utf-8', 'ignore') def get_thread_id(self): """Returns the thread ID The returned string belongs to 'message' will only be valid for as long as the message is valid. This function will not return `None` since Notmuch ensures that every message belongs to a single thread. :returns: String with a thread ID :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() return Message._get_thread_id(self._msg).decode('utf-8', 'ignore') def get_replies(self): """Gets all direct replies to this message as :class:`Messages` iterator .. note:: This call only makes sense if 'message' was ultimately obtained from a :class:`Thread` object, (such as by coming directly from the result of calling :meth:`Thread.get_toplevel_messages` or by any number of subsequent calls to :meth:`get_replies`). If this message was obtained through some non-thread means, (such as by a call to :meth:`Query.search_messages`), then this function will return an empty Messages iterator. :returns: :class:`Messages`. :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() msgs_p = Message._get_replies(self._msg) from .messages import Messages, EmptyMessagesResult if not msgs_p: return EmptyMessagesResult(self) return Messages(msgs_p, self) def get_date(self): """Returns time_t of the message date For the original textual representation of the Date header from the message call notmuch_message_get_header() with a header value of "date". :returns: A time_t timestamp. :rtype: c_unit64 :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() return Message._get_date(self._msg) def get_header(self, header): """Get the value of the specified header. The value will be read from the actual message file, not from the notmuch database. The header name is case insensitive. Returns an empty string ("") if the message does not contain a header line matching 'header'. :param header: The name of the header to be retrieved. It is not case-sensitive. :type header: str :returns: The header value as string :raises: :exc:`NotInitializedError` if the message is not initialized :raises: :exc:`NullPointerError` if any error occured """ if not self._msg: raise NotInitializedError() #Returns NULL if any error occurs. header = Message._get_header(self._msg, _str(header)) if header == None: raise NullPointerError() return header.decode('UTF-8', 'ignore') def get_filename(self): """Returns the file path of the message file :returns: Absolute file path & name of the message file :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() return Message._get_filename(self._msg).decode('utf-8', 'ignore') def get_filenames(self): """Get all filenames for the email corresponding to 'message' Returns a Filenames() generator with all absolute filepaths for messages recorded to have the same Message-ID. These files must not necessarily have identical content.""" if not self._msg: raise NotInitializedError() files_p = Message._get_filenames(self._msg) return Filenames(files_p, self) def get_flag(self, flag): """Checks whether a specific flag is set for this message The method :meth:`Query.search_threads` sets *Message.FLAG.MATCH* for those messages that match the query. This method allows us to get the value of this flag. :param flag: One of the :attr:`Message.FLAG` values (currently only *Message.FLAG.MATCH* :returns: An unsigned int (0/1), indicating whether the flag is set. :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() return Message._get_flag(self._msg, flag) def set_flag(self, flag, value): """Sets/Unsets a specific flag for this message :param flag: One of the :attr:`Message.FLAG` values (currently only *Message.FLAG.MATCH* :param value: A bool indicating whether to set or unset the flag. :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._msg: raise NotInitializedError() self._set_flag(self._msg, flag, value) def get_tags(self): """Returns the message tags :returns: A :class:`Tags` iterator. :raises: :exc:`NotInitializedError` if the message is not initialized :raises: :exc:`NullPointerError` if any error occured """ if not self._msg: raise NotInitializedError() tags_p = Message._get_tags(self._msg) if not tags_p: raise NullPointerError() return Tags(tags_p, self) _add_tag = nmlib.notmuch_message_add_tag _add_tag.argtypes = [NotmuchMessageP, c_char_p] _add_tag.restype = c_uint def add_tag(self, tag, sync_maildir_flags=False): """Adds a tag to the given message Adds a tag to the current message. The maximal tag length is defined in the notmuch library and is currently 200 bytes. :param tag: String with a 'tag' to be added. :param sync_maildir_flags: If notmuch configuration is set to do this, add maildir flags corresponding to notmuch tags. See underlying method :meth:`tags_to_maildir_flags`. Use False if you want to add/remove many tags on a message without having to physically rename the file every time. Do note, that this will do nothing when a message is frozen, as tag changes will not be committed to the database yet. :returns: STATUS.SUCCESS if the tag was successfully added. Raises an exception otherwise. :raises: :exc:`NullPointerError` if the `tag` argument is NULL :raises: :exc:`TagTooLongError` if the length of `tag` exceeds Message.NOTMUCH_TAG_MAX) :raises: :exc:`ReadOnlyDatabaseError` if the database was opened in read-only mode so message cannot be modified :raises: :exc:`NotInitializedError` if message has not been initialized """ if not self._msg: raise NotInitializedError() status = self._add_tag(self._msg, _str(tag)) # bail out on failure if status != STATUS.SUCCESS: raise NotmuchError(status) if sync_maildir_flags: self.tags_to_maildir_flags() return STATUS.SUCCESS _remove_tag = nmlib.notmuch_message_remove_tag _remove_tag.argtypes = [NotmuchMessageP, c_char_p] _remove_tag.restype = c_uint def remove_tag(self, tag, sync_maildir_flags=False): """Removes a tag from the given message If the message has no such tag, this is a non-operation and will report success anyway. :param tag: String with a 'tag' to be removed. :param sync_maildir_flags: If notmuch configuration is set to do this, add maildir flags corresponding to notmuch tags. See underlying method :meth:`tags_to_maildir_flags`. Use False if you want to add/remove many tags on a message without having to physically rename the file every time. Do note, that this will do nothing when a message is frozen, as tag changes will not be committed to the database yet. :returns: STATUS.SUCCESS if the tag was successfully removed or if the message had no such tag. Raises an exception otherwise. :raises: :exc:`NullPointerError` if the `tag` argument is NULL :raises: :exc:`TagTooLongError` if the length of `tag` exceeds Message.NOTMUCH_TAG_MAX) :raises: :exc:`ReadOnlyDatabaseError` if the database was opened in read-only mode so message cannot be modified :raises: :exc:`NotInitializedError` if message has not been initialized """ if not self._msg: raise NotInitializedError() status = self._remove_tag(self._msg, _str(tag)) # bail out on error if status != STATUS.SUCCESS: raise NotmuchError(status) if sync_maildir_flags: self.tags_to_maildir_flags() return STATUS.SUCCESS _remove_all_tags = nmlib.notmuch_message_remove_all_tags _remove_all_tags.argtypes = [NotmuchMessageP] _remove_all_tags.restype = c_uint def remove_all_tags(self, sync_maildir_flags=False): """Removes all tags from the given message. See :meth:`freeze` for an example showing how to safely replace tag values. :param sync_maildir_flags: If notmuch configuration is set to do this, add maildir flags corresponding to notmuch tags. See :meth:`tags_to_maildir_flags`. Use False if you want to add/remove many tags on a message without having to physically rename the file every time. Do note, that this will do nothing when a message is frozen, as tag changes will not be committed to the database yet. :returns: STATUS.SUCCESS if the tags were successfully removed. Raises an exception otherwise. :raises: :exc:`ReadOnlyDatabaseError` if the database was opened in read-only mode so message cannot be modified :raises: :exc:`NotInitializedError` if message has not been initialized """ if not self._msg: raise NotInitializedError() status = self._remove_all_tags(self._msg) # bail out on error if status != STATUS.SUCCESS: raise NotmuchError(status) if sync_maildir_flags: self.tags_to_maildir_flags() return STATUS.SUCCESS _freeze = nmlib.notmuch_message_freeze _freeze.argtypes = [NotmuchMessageP] _freeze.restype = c_uint def freeze(self): """Freezes the current state of 'message' within the database This means that changes to the message state, (via :meth:`add_tag`, :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be committed to the database until the message is :meth:`thaw` ed. Multiple calls to freeze/thaw are valid and these calls will "stack". That is there must be as many calls to thaw as to freeze before a message is actually thawed. The ability to do freeze/thaw allows for safe transactions to change tag values. For example, explicitly setting a message to have a given set of tags might look like this:: msg.freeze() msg.remove_all_tags(False) for tag in new_tags: msg.add_tag(tag, False) msg.thaw() msg.tags_to_maildir_flags() With freeze/thaw used like this, the message in the database is guaranteed to have either the full set of original tag values, or the full set of new tag values, but nothing in between. Imagine the example above without freeze/thaw and the operation somehow getting interrupted. This could result in the message being left with no tags if the interruption happened after :meth:`remove_all_tags` but before :meth:`add_tag`. :returns: STATUS.SUCCESS if the message was successfully frozen. Raises an exception otherwise. :raises: :exc:`ReadOnlyDatabaseError` if the database was opened in read-only mode so message cannot be modified :raises: :exc:`NotInitializedError` if message has not been initialized """ if not self._msg: raise NotInitializedError() status = self._freeze(self._msg) if STATUS.SUCCESS == status: # return on success return status raise NotmuchError(status) _thaw = nmlib.notmuch_message_thaw _thaw.argtypes = [NotmuchMessageP] _thaw.restype = c_uint def thaw(self): """Thaws the current 'message' Thaw the current 'message', synchronizing any changes that may have occurred while 'message' was frozen into the notmuch database. See :meth:`freeze` for an example of how to use this function to safely provide tag changes. Multiple calls to freeze/thaw are valid and these calls with "stack". That is there must be as many calls to thaw as to freeze before a message is actually thawed. :returns: STATUS.SUCCESS if the message was successfully frozen. Raises an exception otherwise. :raises: :exc:`UnbalancedFreezeThawError` if an attempt was made to thaw an unfrozen message. That is, there have been an unbalanced number of calls to :meth:`freeze` and :meth:`thaw`. :raises: :exc:`NotInitializedError` if message has not been initialized """ if not self._msg: raise NotInitializedError() status = self._thaw(self._msg) if STATUS.SUCCESS == status: # return on success return status raise NotmuchError(status) def is_match(self): """(Not implemented)""" return self.get_flag(Message.FLAG.MATCH) def tags_to_maildir_flags(self): """Synchronize notmuch tags to file Maildir flags 'D' if the message has the "draft" tag 'F' if the message has the "flagged" tag 'P' if the message has the "passed" tag 'R' if the message has the "replied" tag 'S' if the message does not have the "unread" tag Any existing flags unmentioned in the list above will be preserved in the renaming. Also, if this filename is in a directory named "new", rename it to be within the neighboring directory named "cur". Do note that calling this method while a message is frozen might not work yet, as the modified tags have not been committed yet to the database. :returns: a :class:`STATUS` value. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" if not self._msg: raise NotInitializedError() return Message._tags_to_maildir_flags(self._msg) def maildir_flags_to_tags(self): """Synchronize file Maildir flags to notmuch tags Flag Action if present ---- ----------------- 'D' Adds the "draft" tag to the message 'F' Adds the "flagged" tag to the message 'P' Adds the "passed" tag to the message 'R' Adds the "replied" tag to the message 'S' Removes the "unread" tag from the message For each flag that is not present, the opposite action (add/remove) is performed for the corresponding tags. If there are multiple filenames associated with this message, the flag is considered present if it appears in one or more filenames. (That is, the flags from the multiple filenames are combined with the logical OR operator.) As a convenience, you can set the sync_maildir_flags parameter in :meth:`Database.add_message` to implicitly call this. :returns: a :class:`STATUS`. In short, you want to see notmuch.STATUS.SUCCESS here. See there for details.""" if not self._msg: raise NotInitializedError() return Message._tags_to_maildir_flags(self._msg) def __repr__(self): """Represent a Message() object by str()""" return self.__str__() def __unicode__(self): format = "%s (%s) (%s)" return format % (self.get_header('from'), self.get_tags(), date.fromtimestamp(self.get_date()), ) def get_message_parts(self): """Output like notmuch show""" fp = open(self.get_filename()) email_msg = email.message_from_file(fp) fp.close() out = [] for msg in email_msg.walk(): if not msg.is_multipart(): out.append(msg) return out def get_part(self, num): """Returns the nth message body part""" parts = self.get_message_parts() if (num <= 0 or num > len(parts)): return "" else: out_part = parts[(num - 1)] return out_part.get_payload(decode=True) def __hash__(self): """Implement hash(), so we can use Message() sets""" file = self.get_filename() if not file: return None return hash(file) def __cmp__(self, other): """Implement cmp(), so we can compare Message()s 2 messages are considered equal if they point to the same Message-Id and if they point to the same file names. If 2 Messages derive from different queries where some files have been added or removed, the same messages would not be considered equal (as they do not point to the same set of files any more).""" res = cmp(self.get_message_id(), other.get_message_id()) if res: res = cmp(list(self.get_filenames()), list(other.get_filenames())) return res _destroy = nmlib.notmuch_message_destroy _destroy.argtypes = [NotmuchMessageP] _destroy.restype = None def __del__(self): """Close and free the notmuch Message""" if self._msg: self._destroy(self._msg) notmuch-0.17/bindings/python/notmuch/messages.py000066400000000000000000000155601226041107500220770ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth Jesse Rosenthal """ from .globals import ( nmlib, NotmuchTagsP, NotmuchMessageP, NotmuchMessagesP, ) from .errors import ( NullPointerError, NotInitializedError, ) from .tag import Tags from .message import Message class Messages(object): """Represents a list of notmuch messages This object provides an iterator over a list of notmuch messages (Technically, it provides a wrapper for the underlying *notmuch_messages_t* structure). Do note that the underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of messages, and a subsequent iteration attempt will raise a :exc:`NotInitializedError`. If you need to re-iterate over a list of messages you will need to retrieve a new :class:`Messages` object or cache your :class:`Message`\s in a list via:: msglist = list(msgs) You can store and reuse the single :class:`Message` objects as often as you want as long as you keep the parent :class:`Messages` object around. (Due to hierarchical memory allocation, all derived :class:`Message` objects will be invalid when we delete the parent :class:`Messages` object, even if it was already exhausted.) So this works:: db = Database() msgs = Query(db,'').search_messages() #get a Messages() object msglist = list(msgs) # msgs is "exhausted" now and msgs.next() will raise an exception. # However it will be kept alive until all retrieved Message() # objects are also deleted. If you do e.g. an explicit del(msgs) # here, the following lines would fail. # You can reiterate over *msglist* however as often as you want. # It is simply a list with :class:`Message`s. print (msglist[0].get_filename()) print (msglist[1].get_filename()) print (msglist[0].get_message_id()) As :class:`Message` implements both __hash__() and __cmp__(), it is possible to make sets out of :class:`Messages` and use set arithmetic (this happens in python and will of course be *much* slower than redoing a proper query with the appropriate filters:: s1, s2 = set(msgs1), set(msgs2) s.union(s2) s1 -= s2 ... Be careful when using set arithmetic between message sets derived from different Databases (ie the same database reopened after messages have changed). If messages have added or removed associated files in the meantime, it is possible that the same message would be considered as a different object (as it points to a different file). """ #notmuch_messages_get _get = nmlib.notmuch_messages_get _get.argtypes = [NotmuchMessagesP] _get.restype = NotmuchMessageP _collect_tags = nmlib.notmuch_messages_collect_tags _collect_tags.argtypes = [NotmuchMessagesP] _collect_tags.restype = NotmuchTagsP def __init__(self, msgs_p, parent=None): """ :param msgs_p: A pointer to an underlying *notmuch_messages_t* structure. These are not publically exposed, so a user will almost never instantiate a :class:`Messages` object herself. They are usually handed back as a result, e.g. in :meth:`Query.search_messages`. *msgs_p* must be valid, we will raise an :exc:`NullPointerError` if it is `None`. :type msgs_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Query`) these tags are derived from. It saves a reference to it, so we can automatically delete the db object once all derived objects are dead. :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ if not msgs_p: raise NullPointerError() self._msgs = msgs_p #store parent, so we keep them alive as long as self is alive self._parent = parent def collect_tags(self): """Return the unique :class:`Tags` in the contained messages :returns: :class:`Tags` :exceptions: :exc:`NotInitializedError` if not init'ed .. note:: :meth:`collect_tags` will iterate over the messages and therefore will not allow further iterations. """ if not self._msgs: raise NotInitializedError() # collect all tags (returns NULL on error) tags_p = Messages._collect_tags(self._msgs) #reset _msgs as we iterated over it and can do so only once self._msgs = None if not tags_p: raise NullPointerError() return Tags(tags_p, self) def __iter__(self): """ Make Messages an iterator """ return self _valid = nmlib.notmuch_messages_valid _valid.argtypes = [NotmuchMessagesP] _valid.restype = bool _move_to_next = nmlib.notmuch_messages_move_to_next _move_to_next.argtypes = [NotmuchMessagesP] _move_to_next.restype = None def __next__(self): if not self._msgs: raise NotInitializedError() if not self._valid(self._msgs): self._msgs = None raise StopIteration msg = Message(Messages._get(self._msgs), self) self._move_to_next(self._msgs) return msg next = __next__ # python2.x iterator protocol compatibility def __nonzero__(self): ''' Implement truth value testing. If __nonzero__ is not implemented, the python runtime would fall back to `len(..) > 0` thus exhausting the iterator. :returns: True if the wrapped iterator has at least one more object left. ''' return self._msgs and self._valid(self._msgs) _destroy = nmlib.notmuch_messages_destroy _destroy.argtypes = [NotmuchMessagesP] _destroy.restype = None def __del__(self): """Close and free the notmuch Messages""" if self._msgs: self._destroy(self._msgs) class EmptyMessagesResult(Messages): def __init__(self, parent): self._msgs = None self._parent = parent def __next__(self): raise StopIteration() next = __next__ notmuch-0.17/bindings/python/notmuch/query.py000066400000000000000000000156271226041107500214410ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_char_p, c_uint from .globals import ( nmlib, Enum, _str, NotmuchQueryP, NotmuchThreadsP, NotmuchDatabaseP, NotmuchMessagesP, ) from .errors import ( NullPointerError, NotInitializedError, ) from .threads import Threads from .messages import Messages class Query(object): """Represents a search query on an opened :class:`Database`. A query selects and filters a subset of messages from the notmuch database we derive from. :class:`Query` provides an instance attribute :attr:`sort`, which contains the sort order (if specified via :meth:`set_sort`) or `None`. Any function in this class may throw an :exc:`NotInitializedError` in case the underlying query object was not set up correctly. .. note:: Do remember that as soon as we tear down this object, all underlying derived objects such as threads, messages, tags etc will be freed by the underlying library as well. Accessing these objects will lead to segfaults and other unexpected behavior. See above for more details. """ # constants SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED']) """Constants: Sort order in which to return results""" def __init__(self, db, querystr): """ :param db: An open database which we derive the Query from. :type db: :class:`Database` :param querystr: The query string for the message. :type querystr: utf-8 encoded str or unicode """ self._db = None self._query = None self.sort = None self.create(db, querystr) def _assert_query_is_initialized(self): """Raises :exc:`NotInitializedError` if self._query is `None`""" if not self._query: raise NotInitializedError() """notmuch_query_create""" _create = nmlib.notmuch_query_create _create.argtypes = [NotmuchDatabaseP, c_char_p] _create.restype = NotmuchQueryP def create(self, db, querystr): """Creates a new query derived from a Database This function is utilized by __init__() and usually does not need to be called directly. :param db: Database to create the query from. :type db: :class:`Database` :param querystr: The query string :type querystr: utf-8 encoded str or unicode :raises: :exc:`NullPointerError` if the query creation failed (e.g. too little memory). :exc:`NotInitializedError` if the underlying db was not intitialized. """ db._assert_db_is_initialized() # create reference to parent db to keep it alive self._db = db # create query, return None if too little mem available query_p = Query._create(db._db, _str(querystr)) if not query_p: raise NullPointerError self._query = query_p _set_sort = nmlib.notmuch_query_set_sort _set_sort.argtypes = [NotmuchQueryP, c_uint] _set_sort.argtypes = None def set_sort(self, sort): """Set the sort order future results will be delivered in :param sort: Sort order (see :attr:`Query.SORT`) """ self._assert_query_is_initialized() self.sort = sort self._set_sort(self._query, sort) """notmuch_query_search_threads""" _search_threads = nmlib.notmuch_query_search_threads _search_threads.argtypes = [NotmuchQueryP] _search_threads.restype = NotmuchThreadsP def search_threads(self): """Execute a query for threads Execute a query for threads, returning a :class:`Threads` iterator. The returned threads are owned by the query and as such, will only be valid until the Query is deleted. The method sets :attr:`Message.FLAG`\.MATCH for those messages that match the query. The method :meth:`Message.get_flag` allows us to get the value of this flag. :returns: :class:`Threads` :raises: :exc:`NullPointerError` if search_threads failed """ self._assert_query_is_initialized() threads_p = Query._search_threads(self._query) if not threads_p: raise NullPointerError return Threads(threads_p, self) """notmuch_query_search_messages""" _search_messages = nmlib.notmuch_query_search_messages _search_messages.argtypes = [NotmuchQueryP] _search_messages.restype = NotmuchMessagesP def search_messages(self): """Filter messages according to the query and return :class:`Messages` in the defined sort order :returns: :class:`Messages` :raises: :exc:`NullPointerError` if search_messages failed """ self._assert_query_is_initialized() msgs_p = Query._search_messages(self._query) if not msgs_p: raise NullPointerError return Messages(msgs_p, self) _count_messages = nmlib.notmuch_query_count_messages _count_messages.argtypes = [NotmuchQueryP] _count_messages.restype = c_uint def count_messages(self): ''' This function performs a search and returns Xapian's best guess as to the number of matching messages. :returns: the estimated number of messages matching this query :rtype: int ''' self._assert_query_is_initialized() return Query._count_messages(self._query) _count_threads = nmlib.notmuch_query_count_threads _count_threads.argtypes = [NotmuchQueryP] _count_threads.restype = c_uint def count_threads(self): ''' This function performs a search and returns the number of unique thread IDs in the matching messages. This is the same as number of threads matching a search. Note that this is a significantly heavier operation than meth:`Query.count_messages`. :returns: the number of threads returned by this query :rtype: int ''' self._assert_query_is_initialized() return Query._count_threads(self._query) _destroy = nmlib.notmuch_query_destroy _destroy.argtypes = [NotmuchQueryP] _destroy.restype = None def __del__(self): """Close and free the Query""" if self._query: self._destroy(self._query) notmuch-0.17/bindings/python/notmuch/tag.py000066400000000000000000000112011226041107500210270ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_char_p from .globals import ( nmlib, Python3StringMixIn, NotmuchTagsP, ) from .errors import ( NullPointerError, NotInitializedError, ) class Tags(Python3StringMixIn): """Represents a list of notmuch tags This object provides an iterator over a list of notmuch tags (which are unicode instances). Do note that the underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of tags, and a subsequent iteration attempt will raise a :exc:`NotInitializedError`. Also note, that any function that uses iteration (nearly all) will also exhaust the tags. So both:: for tag in tags: print tag as well as:: number_of_tags = len(tags) and even a simple:: #str() iterates over all tags to construct a space separated list print(str(tags)) will "exhaust" the Tags. If you need to re-iterate over a list of tags you will need to retrieve a new :class:`Tags` object. """ #notmuch_tags_get _get = nmlib.notmuch_tags_get _get.argtypes = [NotmuchTagsP] _get.restype = c_char_p def __init__(self, tags_p, parent=None): """ :param tags_p: A pointer to an underlying *notmuch_tags_t* structure. These are not publically exposed, so a user will almost never instantiate a :class:`Tags` object herself. They are usually handed back as a result, e.g. in :meth:`Database.get_all_tags`. *tags_p* must be valid, we will raise an :exc:`NullPointerError` if it is `None`. :type tags_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Database` or :class:`Message` these tags are derived from, and saves a reference to it, so we can automatically delete the db object once all derived objects are dead. :TODO: Make the iterator optionally work more than once by cache the tags in the Python object(?) """ if not tags_p: raise NullPointerError() self._tags = tags_p #save reference to parent object so we keep it alive self._parent = parent def __iter__(self): """ Make Tags an iterator """ return self _valid = nmlib.notmuch_tags_valid _valid.argtypes = [NotmuchTagsP] _valid.restype = bool _move_to_next = nmlib.notmuch_tags_move_to_next _move_to_next.argtypes = [NotmuchTagsP] _move_to_next.restype = None def __next__(self): if not self._tags: raise NotInitializedError() if not self._valid(self._tags): self._tags = None raise StopIteration tag = Tags._get(self._tags).decode('UTF-8') self._move_to_next(self._tags) return tag next = __next__ # python2.x iterator protocol compatibility def __nonzero__(self): ''' Implement truth value testing. If __nonzero__ is not implemented, the python runtime would fall back to `len(..) > 0` thus exhausting the iterator. :returns: True if the wrapped iterator has at least one more object left. ''' return self._tags and self._valid(self._tags) def __unicode__(self): """string representation of :class:`Tags`: a space separated list of tags .. note:: As this iterates over the tags, we will not be able to iterate over them again (as in retrieve them)! If the tags have been exhausted already, this will raise a :exc:`NotInitializedError`on subsequent attempts. """ return " ".join(self) _destroy = nmlib.notmuch_tags_destroy _destroy.argtypes = [NotmuchTagsP] _destroy.restype = None def __del__(self): """Close and free the notmuch tags""" if self._tags: self._destroy(self._tags) notmuch-0.17/bindings/python/notmuch/thread.py000066400000000000000000000232751226041107500215410ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from ctypes import c_char_p, c_long, c_int from .globals import ( nmlib, NotmuchThreadP, NotmuchMessagesP, NotmuchTagsP, ) from .errors import ( NullPointerError, NotInitializedError, ) from .messages import Messages from .tag import Tags from datetime import date class Thread(object): """Represents a single message thread.""" """notmuch_thread_get_thread_id""" _get_thread_id = nmlib.notmuch_thread_get_thread_id _get_thread_id.argtypes = [NotmuchThreadP] _get_thread_id.restype = c_char_p """notmuch_thread_get_authors""" _get_authors = nmlib.notmuch_thread_get_authors _get_authors.argtypes = [NotmuchThreadP] _get_authors.restype = c_char_p """notmuch_thread_get_subject""" _get_subject = nmlib.notmuch_thread_get_subject _get_subject.argtypes = [NotmuchThreadP] _get_subject.restype = c_char_p """notmuch_thread_get_toplevel_messages""" _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages _get_toplevel_messages.argtypes = [NotmuchThreadP] _get_toplevel_messages.restype = NotmuchMessagesP _get_newest_date = nmlib.notmuch_thread_get_newest_date _get_newest_date.argtypes = [NotmuchThreadP] _get_newest_date.restype = c_long _get_oldest_date = nmlib.notmuch_thread_get_oldest_date _get_oldest_date.argtypes = [NotmuchThreadP] _get_oldest_date.restype = c_long """notmuch_thread_get_tags""" _get_tags = nmlib.notmuch_thread_get_tags _get_tags.argtypes = [NotmuchThreadP] _get_tags.restype = NotmuchTagsP def __init__(self, thread_p, parent=None): """ :param thread_p: A pointer to an internal notmuch_thread_t Structure. These are not publically exposed, so a user will almost never instantiate a :class:`Thread` object herself. They are usually handed back as a result, e.g. when iterating through :class:`Threads`. *thread_p* must be valid, we will raise an :exc:`NullPointerError` if it is `None`. :param parent: A 'parent' object is passed which this message is derived from. We save a reference to it, so we can automatically delete the parent object once all derived objects are dead. """ if not thread_p: raise NullPointerError() self._thread = thread_p #keep reference to parent, so we keep it alive self._parent = parent def get_thread_id(self): """Get the thread ID of 'thread' The returned string belongs to 'thread' and will only be valid for as long as the thread is valid. :returns: String with a message ID :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: raise NotInitializedError() return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore') _get_total_messages = nmlib.notmuch_thread_get_total_messages _get_total_messages.argtypes = [NotmuchThreadP] _get_total_messages.restype = c_int def get_total_messages(self): """Get the total number of messages in 'thread' :returns: The number of all messages in the database belonging to this thread. Contrast with :meth:`get_matched_messages`. :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: raise NotInitializedError() return self._get_total_messages(self._thread) def get_toplevel_messages(self): """Returns a :class:`Messages` iterator for the top-level messages in 'thread' This iterator will not necessarily iterate over all of the messages in the thread. It will only iterate over the messages in the thread which are not replies to other messages in the thread. :returns: :class:`Messages` :raises: :exc:`NotInitializedError` if query is not initialized :raises: :exc:`NullPointerError` if search_messages failed """ if not self._thread: raise NotInitializedError() msgs_p = Thread._get_toplevel_messages(self._thread) if not msgs_p: raise NullPointerError() return Messages(msgs_p, self) """notmuch_thread_get_messages""" _get_messages = nmlib.notmuch_thread_get_messages _get_messages.argtypes = [NotmuchThreadP] _get_messages.restype = NotmuchMessagesP def get_messages(self): """Returns a :class:`Messages` iterator for all messages in 'thread' :returns: :class:`Messages` :raises: :exc:`NotInitializedError` if query is not initialized :raises: :exc:`NullPointerError` if get_messages failed """ if not self._thread: raise NotInitializedError() msgs_p = Thread._get_messages(self._thread) if not msgs_p: raise NullPointerError() return Messages(msgs_p, self) _get_matched_messages = nmlib.notmuch_thread_get_matched_messages _get_matched_messages.argtypes = [NotmuchThreadP] _get_matched_messages.restype = c_int def get_matched_messages(self): """Returns the number of messages in 'thread' that matched the query :returns: The number of all messages belonging to this thread that matched the :class:`Query`from which this thread was created. Contrast with :meth:`get_total_messages`. :raises: :exc:`NotInitializedError` if the thread is not initialized. """ if not self._thread: raise NotInitializedError() return self._get_matched_messages(self._thread) def get_authors(self): """Returns the authors of 'thread' The returned string is a comma-separated list of the names of the authors of mail messages in the query results that belong to this thread. The returned string belongs to 'thread' and will only be valid for as long as this Thread() is not deleted. """ if not self._thread: raise NotInitializedError() authors = Thread._get_authors(self._thread) if not authors: return None return authors.decode('UTF-8', 'ignore') def get_subject(self): """Returns the Subject of 'thread' The returned string belongs to 'thread' and will only be valid for as long as this Thread() is not deleted. """ if not self._thread: raise NotInitializedError() subject = Thread._get_subject(self._thread) if not subject: return None return subject.decode('UTF-8', 'ignore') def get_newest_date(self): """Returns time_t of the newest message date :returns: A time_t timestamp. :rtype: c_unit64 :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: raise NotInitializedError() return Thread._get_newest_date(self._thread) def get_oldest_date(self): """Returns time_t of the oldest message date :returns: A time_t timestamp. :rtype: c_unit64 :raises: :exc:`NotInitializedError` if the message is not initialized. """ if not self._thread: raise NotInitializedError() return Thread._get_oldest_date(self._thread) def get_tags(self): """ Returns the message tags In the Notmuch database, tags are stored on individual messages, not on threads. So the tags returned here will be all tags of the messages which matched the search and which belong to this thread. The :class:`Tags` object is owned by the thread and as such, will only be valid for as long as this :class:`Thread` is valid (e.g. until the query from which it derived is explicitely deleted). :returns: A :class:`Tags` iterator. :raises: :exc:`NotInitializedError` if query is not initialized :raises: :exc:`NullPointerError` if search_messages failed """ if not self._thread: raise NotInitializedError() tags_p = Thread._get_tags(self._thread) if not tags_p: raise NullPointerError() return Tags(tags_p, self) def __unicode__(self): frm = "thread:%s %12s [%d/%d] %s; %s (%s)" return frm % (self.get_thread_id(), date.fromtimestamp(self.get_newest_date()), self.get_matched_messages(), self.get_total_messages(), self.get_authors(), self.get_subject(), self.get_tags(), ) _destroy = nmlib.notmuch_thread_destroy _destroy.argtypes = [NotmuchThreadP] _destroy.restype = None def __del__(self): """Close and free the notmuch Thread""" if self._thread: self._destroy(self._thread) notmuch-0.17/bindings/python/notmuch/threads.py000066400000000000000000000143521226041107500217200ustar00rootroot00000000000000""" This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ from .globals import ( nmlib, Python3StringMixIn, NotmuchThreadP, NotmuchThreadsP, ) from .errors import ( NullPointerError, NotInitializedError, ) from .thread import Thread class Threads(Python3StringMixIn): """Represents a list of notmuch threads This object provides an iterator over a list of notmuch threads (Technically, it provides a wrapper for the underlying *notmuch_threads_t* structure). Do note that the underlying library only provides a one-time iterator (it cannot reset the iterator to the start). Thus iterating over the function will "exhaust" the list of threads, and a subsequent iteration attempt will raise a :exc:`NotInitializedError`. Also note, that any function that uses iteration will also exhaust the messages. So both:: for thread in threads: print thread as well as:: number_of_msgs = len(threads) will "exhaust" the threads. If you need to re-iterate over a list of messages you will need to retrieve a new :class:`Threads` object. Things are not as bad as it seems though, you can store and reuse the single Thread objects as often as you want as long as you keep the parent Threads object around. (Recall that due to hierarchical memory allocation, all derived Threads objects will be invalid when we delete the parent Threads() object, even if it was already "exhausted".) So this works:: db = Database() threads = Query(db,'').search_threads() #get a Threads() object threadlist = [] for thread in threads: threadlist.append(thread) # threads is "exhausted" now and even len(threads) will raise an # exception. # However it will be kept around until all retrieved Thread() objects are # also deleted. If you did e.g. an explicit del(threads) here, the # following lines would fail. # You can reiterate over *threadlist* however as often as you want. # It is simply a list with Thread objects. print (threadlist[0].get_thread_id()) print (threadlist[1].get_thread_id()) print (threadlist[0].get_total_messages()) """ #notmuch_threads_get _get = nmlib.notmuch_threads_get _get.argtypes = [NotmuchThreadsP] _get.restype = NotmuchThreadP def __init__(self, threads_p, parent=None): """ :param threads_p: A pointer to an underlying *notmuch_threads_t* structure. These are not publically exposed, so a user will almost never instantiate a :class:`Threads` object herself. They are usually handed back as a result, e.g. in :meth:`Query.search_threads`. *threads_p* must be valid, we will raise an :exc:`NullPointerError` if it is `None`. :type threads_p: :class:`ctypes.c_void_p` :param parent: The parent object (ie :class:`Query`) these tags are derived from. It saves a reference to it, so we can automatically delete the db object once all derived objects are dead. :TODO: Make the iterator work more than once and cache the tags in the Python object.(?) """ if not threads_p: raise NullPointerError() self._threads = threads_p #store parent, so we keep them alive as long as self is alive self._parent = parent def __iter__(self): """ Make Threads an iterator """ return self _valid = nmlib.notmuch_threads_valid _valid.argtypes = [NotmuchThreadsP] _valid.restype = bool _move_to_next = nmlib.notmuch_threads_move_to_next _move_to_next.argtypes = [NotmuchThreadsP] _move_to_next.restype = None def __next__(self): if not self._threads: raise NotInitializedError() if not self._valid(self._threads): self._threads = None raise StopIteration thread = Thread(Threads._get(self._threads), self) self._move_to_next(self._threads) return thread next = __next__ # python2.x iterator protocol compatibility def __len__(self): """len(:class:`Threads`) returns the number of contained Threads .. note:: As this iterates over the threads, we will not be able to iterate over them again! So this will fail:: #THIS FAILS threads = Database().create_query('').search_threads() if len(threads) > 0: #this 'exhausts' threads # next line raises :exc:`NotInitializedError`!!! for thread in threads: print thread """ if not self._threads: raise NotInitializedError() i = 0 # returns 'bool'. On out-of-memory it returns None while self._valid(self._threads): self._move_to_next(self._threads) i += 1 # reset self._threads to mark as "exhausted" self._threads = None return i def __nonzero__(self): ''' Implement truth value testing. If __nonzero__ is not implemented, the python runtime would fall back to `len(..) > 0` thus exhausting the iterator. :returns: True if the wrapped iterator has at least one more object left. ''' return self._threads and self._valid(self._threads) _destroy = nmlib.notmuch_threads_destroy _destroy.argtypes = [NotmuchThreadsP] _destroy.restype = None def __del__(self): """Close and free the notmuch Threads""" if self._threads: self._destroy(self._threads) notmuch-0.17/bindings/python/notmuch/version.py000066400000000000000000000001161226041107500217440ustar00rootroot00000000000000# this file should be kept in sync with ../../../version __VERSION__ = '0.17' notmuch-0.17/bindings/python/setup.py000066400000000000000000000052101226041107500177420ustar00rootroot00000000000000#!/usr/bin/env python """ This file is part of notmuch. Notmuch 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. Notmuch 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 notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ import os from distutils.core import setup # get the notmuch version number without importing the notmuch module version_file = os.path.join(os.path.dirname(__file__), 'notmuch', 'version.py') exec(compile(open(version_file).read(), version_file, 'exec')) assert '__VERSION__' in globals(), \ 'Failed to read the notmuch binding version number' setup(name='notmuch', version=__VERSION__, description='Python binding of the notmuch mail search and indexing library.', author='Sebastian Spaeth', author_email='Sebastian@SSpaeth.de', url='http://notmuchmail.org/', download_url='http://notmuchmail.org/releases/notmuch-%s.tar.gz' % __VERSION__, packages=['notmuch'], keywords=['library', 'email'], long_description='''Overview ======== The notmuch module provides an interface to the `notmuch `_ functionality, directly interfacing with a shared notmuch library. Notmuch provides a maildatabase that allows for extremely quick searching and filtering of your email according to various criteria. The documentation for the latest notmuch release can be `viewed online `_. Requirements ------------ You need to have notmuch installed (or rather libnotmuch.so.1). Also, notmuch makes use of the ctypes library, and has only been tested with python >= 2.5. It will not work on earlier python versions. ''', classifiers=['Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Communications :: Email', 'Topic :: Software Development :: Libraries' ], platforms='', license='http://www.gnu.org/licenses/gpl-3.0.txt', ) notmuch-0.17/bindings/ruby/000077500000000000000000000000001226041107500156725ustar00rootroot00000000000000notmuch-0.17/bindings/ruby/.gitignore000066400000000000000000000001171226041107500176610ustar00rootroot00000000000000# .gitignore for bindings/ruby # Generated files Makefile mkmf.log notmuch.so notmuch-0.17/bindings/ruby/database.c000066400000000000000000000221211226041107500176000ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" VALUE notmuch_rb_database_alloc (VALUE klass) { return Data_Wrap_Struct (klass, NULL, NULL, NULL); } /* * call-seq: Notmuch::Database.new(path [, {:create => false, :mode => Notmuch::MODE_READ_ONLY}]) => DB * * Create or open a notmuch database using the given path. * * If :create is +true+, create the database instead of opening. * * The argument :mode specifies the open mode of the database. */ VALUE notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE self) { const char *path; int create, mode; VALUE pathv, hashv; VALUE modev; notmuch_database_t *database; notmuch_status_t ret; /* Check arguments */ rb_scan_args (argc, argv, "11", &pathv, &hashv); SafeStringValue (pathv); path = RSTRING_PTR (pathv); if (!NIL_P (hashv)) { Check_Type (hashv, T_HASH); create = RTEST (rb_hash_aref (hashv, ID2SYM (ID_db_create))); modev = rb_hash_aref (hashv, ID2SYM (ID_db_mode)); if (NIL_P (modev)) mode = NOTMUCH_DATABASE_MODE_READ_ONLY; else if (!FIXNUM_P (modev)) rb_raise (rb_eTypeError, ":mode isn't a Fixnum"); else { mode = FIX2INT (modev); switch (mode) { case NOTMUCH_DATABASE_MODE_READ_ONLY: case NOTMUCH_DATABASE_MODE_READ_WRITE: break; default: rb_raise ( rb_eTypeError, "Invalid mode"); } } } else { create = 0; mode = NOTMUCH_DATABASE_MODE_READ_ONLY; } Check_Type (self, T_DATA); if (create) ret = notmuch_database_create (path, &database); else ret = notmuch_database_open (path, mode, &database); notmuch_rb_status_raise (ret); DATA_PTR (self) = database; return self; } /* * call-seq: Notmuch::Database.open(path [, ahash]) {|db| ...} * * Identical to new, except that when it is called with a block, it yields with * the new instance and closes it, and returns the result which is returned from * the block. */ VALUE notmuch_rb_database_open (int argc, VALUE *argv, VALUE klass) { VALUE obj; obj = rb_class_new_instance (argc, argv, klass); if (!rb_block_given_p ()) return obj; return rb_ensure (rb_yield, obj, notmuch_rb_database_close, obj); } /* * call-seq: DB.close => nil * * Close the notmuch database. */ VALUE notmuch_rb_database_close (VALUE self) { notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); notmuch_database_destroy (db); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: DB.path => String * * Return the path of the database */ VALUE notmuch_rb_database_path (VALUE self) { notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); return rb_str_new2 (notmuch_database_get_path (db)); } /* * call-seq: DB.version => Fixnum * * Return the version of the database */ VALUE notmuch_rb_database_version (VALUE self) { notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); return INT2FIX (notmuch_database_get_version (db)); } /* * call-seq: DB.needs_upgrade? => true or false * * Return the +true+ if the database needs upgrading, +false+ otherwise */ VALUE notmuch_rb_database_needs_upgrade (VALUE self) { notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); return notmuch_database_needs_upgrade (db) ? Qtrue : Qfalse; } static void notmuch_rb_upgrade_notify (void *closure, double progress) { VALUE *block = (VALUE *) closure; rb_funcall (*block, ID_call, 1, rb_float_new (progress)); } /* * call-seq: DB.upgrade! [{|progress| block }] => nil * * Upgrade the database. * * If a block is given the block is called with a progress indicator as a * floating point value in the range of [0.0..1.0]. */ VALUE notmuch_rb_database_upgrade (VALUE self) { notmuch_status_t ret; void (*pnotify) (void *closure, double progress); notmuch_database_t *db; VALUE block; Data_Get_Notmuch_Database (self, db); if (rb_block_given_p ()) { pnotify = notmuch_rb_upgrade_notify; block = rb_block_proc (); } else pnotify = NULL; ret = notmuch_database_upgrade (db, pnotify, pnotify ? &block : NULL); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: DB.begin_atomic => nil * * Begin an atomic database operation. */ VALUE notmuch_rb_database_begin_atomic (VALUE self) { notmuch_status_t ret; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); ret = notmuch_database_begin_atomic (db); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: DB.end_atomic => nil * * Indicate the end of an atomic database operation. */ VALUE notmuch_rb_database_end_atomic (VALUE self) { notmuch_status_t ret; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); ret = notmuch_database_end_atomic (db); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: DB.get_directory(path) => DIR * * Retrieve a directory object from the database for 'path' */ VALUE notmuch_rb_database_get_directory (VALUE self, VALUE pathv) { const char *path; notmuch_status_t ret; notmuch_directory_t *dir; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); SafeStringValue (pathv); path = RSTRING_PTR (pathv); ret = notmuch_database_get_directory (db, path, &dir); notmuch_rb_status_raise (ret); if (dir) return Data_Wrap_Struct (notmuch_rb_cDirectory, NULL, NULL, dir); return Qnil; } /* * call-seq: DB.add_message(path) => MESSAGE, isdup * * Add a message to the database and return it. * * +isdup+ is a boolean that specifies whether the added message was a * duplicate. */ VALUE notmuch_rb_database_add_message (VALUE self, VALUE pathv) { const char *path; notmuch_status_t ret; notmuch_message_t *message; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); SafeStringValue (pathv); path = RSTRING_PTR (pathv); ret = notmuch_database_add_message (db, path, &message); notmuch_rb_status_raise (ret); return rb_assoc_new (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message), (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse); } /* * call-seq: DB.remove_message (path) => isdup * * Remove a message from the database. * * +isdup+ is a boolean that specifies whether the removed message was a * duplicate. */ VALUE notmuch_rb_database_remove_message (VALUE self, VALUE pathv) { const char *path; notmuch_status_t ret; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); SafeStringValue (pathv); path = RSTRING_PTR (pathv); ret = notmuch_database_remove_message (db, path); notmuch_rb_status_raise (ret); return (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse; } /* * call-seq: DB.find_message(id) => MESSAGE or nil * * Find a message by message id. */ VALUE notmuch_rb_database_find_message (VALUE self, VALUE idv) { const char *id; notmuch_status_t ret; notmuch_database_t *db; notmuch_message_t *message; Data_Get_Notmuch_Database (self, db); SafeStringValue (idv); id = RSTRING_PTR (idv); ret = notmuch_database_find_message (db, id, &message); notmuch_rb_status_raise (ret); if (message) return Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message); return Qnil; } /* * call-seq: DB.find_message_by_filename(path) => MESSAGE or nil * * Find a message by filename. */ VALUE notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv) { const char *path; notmuch_status_t ret; notmuch_database_t *db; notmuch_message_t *message; Data_Get_Notmuch_Database (self, db); SafeStringValue (pathv); path = RSTRING_PTR (pathv); ret = notmuch_database_find_message_by_filename (db, path, &message); notmuch_rb_status_raise (ret); if (message) return Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message); return Qnil; } /* * call-seq: DB.query(query) => QUERY * * Retrieve a query object for the query string 'query' */ VALUE notmuch_rb_database_query_create (VALUE self, VALUE qstrv) { const char *qstr; notmuch_query_t *query; notmuch_database_t *db; Data_Get_Notmuch_Database (self, db); SafeStringValue (qstrv); qstr = RSTRING_PTR (qstrv); query = notmuch_query_create (db, qstr); if (!query) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cQuery, NULL, NULL, query); } notmuch-0.17/bindings/ruby/defs.h000066400000000000000000000200121226041107500167570ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011, 2012 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #ifndef DEFS_H #define DEFS_H #include #include extern VALUE notmuch_rb_cDatabase; extern VALUE notmuch_rb_cDirectory; extern VALUE notmuch_rb_cFileNames; extern VALUE notmuch_rb_cQuery; extern VALUE notmuch_rb_cThreads; extern VALUE notmuch_rb_cThread; extern VALUE notmuch_rb_cMessages; extern VALUE notmuch_rb_cMessage; extern VALUE notmuch_rb_cTags; extern VALUE notmuch_rb_eBaseError; extern VALUE notmuch_rb_eDatabaseError; extern VALUE notmuch_rb_eMemoryError; extern VALUE notmuch_rb_eReadOnlyError; extern VALUE notmuch_rb_eXapianError; extern VALUE notmuch_rb_eFileError; extern VALUE notmuch_rb_eFileNotEmailError; extern VALUE notmuch_rb_eNullPointerError; extern VALUE notmuch_rb_eTagTooLongError; extern VALUE notmuch_rb_eUnbalancedFreezeThawError; extern VALUE notmuch_rb_eUnbalancedAtomicError; extern ID ID_call; extern ID ID_db_create; extern ID ID_db_mode; /* RSTRING_PTR() is new in ruby-1.9 */ #if !defined(RSTRING_PTR) # define RSTRING_PTR(v) (RSTRING((v))->ptr) #endif /* !defined (RSTRING_PTR) */ #define Data_Get_Notmuch_Database(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "database closed"); \ Data_Get_Struct ((obj), notmuch_database_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Directory(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "directory destroyed"); \ Data_Get_Struct ((obj), notmuch_directory_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_FileNames(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "filenames destroyed"); \ Data_Get_Struct ((obj), notmuch_filenames_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Query(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "query destroyed"); \ Data_Get_Struct ((obj), notmuch_query_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Threads(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "threads destroyed"); \ Data_Get_Struct ((obj), notmuch_threads_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Messages(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "messages destroyed"); \ Data_Get_Struct ((obj), notmuch_messages_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Thread(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "thread destroyed"); \ Data_Get_Struct ((obj), notmuch_thread_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Message(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "message destroyed"); \ Data_Get_Struct ((obj), notmuch_message_t, (ptr)); \ } while (0) #define Data_Get_Notmuch_Tags(obj, ptr) \ do { \ Check_Type ((obj), T_DATA); \ if (DATA_PTR ((obj)) == NULL) \ rb_raise (rb_eRuntimeError, "tags destroyed"); \ Data_Get_Struct ((obj), notmuch_tags_t, (ptr)); \ } while (0) /* status.c */ void notmuch_rb_status_raise (notmuch_status_t status); /* database.c */ VALUE notmuch_rb_database_alloc (VALUE klass); VALUE notmuch_rb_database_initialize (int argc, VALUE *argv, VALUE klass); VALUE notmuch_rb_database_open (int argc, VALUE *argv, VALUE klass); VALUE notmuch_rb_database_close (VALUE self); VALUE notmuch_rb_database_path (VALUE self); VALUE notmuch_rb_database_version (VALUE self); VALUE notmuch_rb_database_needs_upgrade (VALUE self); VALUE notmuch_rb_database_upgrade (VALUE self); VALUE notmuch_rb_database_begin_atomic (VALUE self); VALUE notmuch_rb_database_end_atomic (VALUE self); VALUE notmuch_rb_database_get_directory (VALUE self, VALUE pathv); VALUE notmuch_rb_database_add_message (VALUE self, VALUE pathv); VALUE notmuch_rb_database_remove_message (VALUE self, VALUE pathv); VALUE notmuch_rb_database_find_message (VALUE self, VALUE idv); VALUE notmuch_rb_database_find_message_by_filename (VALUE self, VALUE pathv); VALUE notmuch_rb_database_query_create (VALUE self, VALUE qstrv); /* directory.c */ VALUE notmuch_rb_directory_destroy (VALUE self); VALUE notmuch_rb_directory_get_mtime (VALUE self); VALUE notmuch_rb_directory_set_mtime (VALUE self, VALUE mtimev); VALUE notmuch_rb_directory_get_child_files (VALUE self); VALUE notmuch_rb_directory_get_child_directories (VALUE self); /* filenames.c */ VALUE notmuch_rb_filenames_destroy (VALUE self); VALUE notmuch_rb_filenames_each (VALUE self); /* query.c */ VALUE notmuch_rb_query_destroy (VALUE self); VALUE notmuch_rb_query_get_sort (VALUE self); VALUE notmuch_rb_query_set_sort (VALUE self, VALUE sortv); VALUE notmuch_rb_query_get_string (VALUE self); VALUE notmuch_rb_query_add_tag_exclude (VALUE self, VALUE tagv); VALUE notmuch_rb_query_set_omit_excluded (VALUE self, VALUE omitv); VALUE notmuch_rb_query_search_threads (VALUE self); VALUE notmuch_rb_query_search_messages (VALUE self); VALUE notmuch_rb_query_count_messages (VALUE self); /* threads.c */ VALUE notmuch_rb_threads_destroy (VALUE self); VALUE notmuch_rb_threads_each (VALUE self); /* messages.c */ VALUE notmuch_rb_messages_destroy (VALUE self); VALUE notmuch_rb_messages_each (VALUE self); VALUE notmuch_rb_messages_collect_tags (VALUE self); /* thread.c */ VALUE notmuch_rb_thread_destroy (VALUE self); VALUE notmuch_rb_thread_get_thread_id (VALUE self); VALUE notmuch_rb_thread_get_total_messages (VALUE self); VALUE notmuch_rb_thread_get_toplevel_messages (VALUE self); VALUE notmuch_rb_thread_get_messages (VALUE self); VALUE notmuch_rb_thread_get_matched_messages (VALUE self); VALUE notmuch_rb_thread_get_authors (VALUE self); VALUE notmuch_rb_thread_get_subject (VALUE self); VALUE notmuch_rb_thread_get_oldest_date (VALUE self); VALUE notmuch_rb_thread_get_newest_date (VALUE self); VALUE notmuch_rb_thread_get_tags (VALUE self); /* message.c */ VALUE notmuch_rb_message_destroy (VALUE self); VALUE notmuch_rb_message_get_message_id (VALUE self); VALUE notmuch_rb_message_get_thread_id (VALUE self); VALUE notmuch_rb_message_get_replies (VALUE self); VALUE notmuch_rb_message_get_filename (VALUE self); VALUE notmuch_rb_message_get_filenames (VALUE self); VALUE notmuch_rb_message_get_flag (VALUE self, VALUE flagv); VALUE notmuch_rb_message_set_flag (VALUE self, VALUE flagv, VALUE valuev); VALUE notmuch_rb_message_get_date (VALUE self); VALUE notmuch_rb_message_get_header (VALUE self, VALUE headerv); VALUE notmuch_rb_message_get_tags (VALUE self); VALUE notmuch_rb_message_add_tag (VALUE self, VALUE tagv); VALUE notmuch_rb_message_remove_tag (VALUE self, VALUE tagv); VALUE notmuch_rb_message_remove_all_tags (VALUE self); VALUE notmuch_rb_message_maildir_flags_to_tags (VALUE self); VALUE notmuch_rb_message_tags_to_maildir_flags (VALUE self); VALUE notmuch_rb_message_freeze (VALUE self); VALUE notmuch_rb_message_thaw (VALUE self); /* tags.c */ VALUE notmuch_rb_tags_destroy (VALUE self); VALUE notmuch_rb_tags_each (VALUE self); /* init.c */ void Init_notmuch (void); #endif notmuch-0.17/bindings/ruby/directory.c000066400000000000000000000056121226041107500200460ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: DIR.destroy! => nil * * Destroys the directory, freeing all resources allocated for it. */ VALUE notmuch_rb_directory_destroy (VALUE self) { notmuch_directory_t *dir; Data_Get_Struct (self, notmuch_directory_t, dir); notmuch_directory_destroy (dir); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: DIR.mtime => fixnum * * Returns the mtime of the directory or +0+ if no mtime has been previously * stored. */ VALUE notmuch_rb_directory_get_mtime (VALUE self) { notmuch_directory_t *dir; Data_Get_Notmuch_Directory (self, dir); return UINT2NUM (notmuch_directory_get_mtime (dir)); } /* * call-seq: DIR.mtime=(fixnum) => nil * * Store an mtime within the database for the directory object. */ VALUE notmuch_rb_directory_set_mtime (VALUE self, VALUE mtimev) { notmuch_status_t ret; notmuch_directory_t *dir; Data_Get_Notmuch_Directory (self, dir); if (!FIXNUM_P (mtimev)) rb_raise (rb_eTypeError, "First argument not a fixnum"); ret = notmuch_directory_set_mtime (dir, FIX2UINT (mtimev)); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: DIR.child_files => FILENAMES * * Return a Notmuch::FileNames object, which is an +Enumerable+ listing all the * filenames of messages in the database within the given directory. */ VALUE notmuch_rb_directory_get_child_files (VALUE self) { notmuch_directory_t *dir; notmuch_filenames_t *fnames; Data_Get_Notmuch_Directory (self, dir); fnames = notmuch_directory_get_child_files (dir); return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames); } /* * call-seq: DIR.child_directories => FILENAMES * * Return a Notmuch::FileNames object, which is an +Enumerable+ listing all the * directories in the database within the given directory. */ VALUE notmuch_rb_directory_get_child_directories (VALUE self) { notmuch_directory_t *dir; notmuch_filenames_t *fnames; Data_Get_Notmuch_Directory (self, dir); fnames = notmuch_directory_get_child_directories (dir); return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames); } notmuch-0.17/bindings/ruby/extconf.rb000066400000000000000000000013231226041107500176640ustar00rootroot00000000000000#!/usr/bin/env ruby # coding: utf-8 # Copyright 2010, 2011, 2012 Ali Polatel # Distributed under the terms of the GNU General Public License v3 require 'mkmf' dir = File.join('..', '..', 'lib') # includes $INCFLAGS = "-I#{dir} #{$INCFLAGS}" # make sure there are no undefined symbols $LDFLAGS += ' -Wl,--no-undefined' def have_local_library(lib, path, func, headers = nil) checking_for checking_message(func, lib) do lib = File.join(path, lib) if try_func(func, lib, headers) $LOCAL_LIBS += lib end end end if not have_local_library('libnotmuch.so', dir, 'notmuch_database_create', 'notmuch.h') exit 1 end # Create Makefile dir_config('notmuch') create_makefile('notmuch') notmuch-0.17/bindings/ruby/filenames.c000066400000000000000000000030661226041107500200060ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: FILENAMES.destroy! => nil * * Destroys the filenames, freeing all resources allocated for it. */ VALUE notmuch_rb_filenames_destroy (VALUE self) { notmuch_filenames_t *fnames; Data_Get_Notmuch_FileNames (self, fnames); notmuch_filenames_destroy (fnames); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: FILENAMES.each {|item| block } => FILENAMES * * Calls +block+ once for each element in +self+, passing that element as a * parameter. */ VALUE notmuch_rb_filenames_each (VALUE self) { notmuch_filenames_t *fnames; Data_Get_Notmuch_FileNames (self, fnames); for (; notmuch_filenames_valid (fnames); notmuch_filenames_move_to_next (fnames)) rb_yield (rb_str_new2 (notmuch_filenames_get (fnames))); return self; } notmuch-0.17/bindings/ruby/init.c000066400000000000000000000400241226041107500170010ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011, 2012 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" VALUE notmuch_rb_cDatabase; VALUE notmuch_rb_cDirectory; VALUE notmuch_rb_cFileNames; VALUE notmuch_rb_cQuery; VALUE notmuch_rb_cThreads; VALUE notmuch_rb_cThread; VALUE notmuch_rb_cMessages; VALUE notmuch_rb_cMessage; VALUE notmuch_rb_cTags; VALUE notmuch_rb_eBaseError; VALUE notmuch_rb_eDatabaseError; VALUE notmuch_rb_eMemoryError; VALUE notmuch_rb_eReadOnlyError; VALUE notmuch_rb_eXapianError; VALUE notmuch_rb_eFileError; VALUE notmuch_rb_eFileNotEmailError; VALUE notmuch_rb_eNullPointerError; VALUE notmuch_rb_eTagTooLongError; VALUE notmuch_rb_eUnbalancedFreezeThawError; VALUE notmuch_rb_eUnbalancedAtomicError; ID ID_call; ID ID_db_create; ID ID_db_mode; /* * Document-module: Notmuch * * == Summary * * Ruby extension to the notmuch mail library. * * == Classes * * Following are the classes that are most likely to be of interest to * the user: * * - Notmuch::Database * - Notmuch::FileNames * - Notmuch::Query * - Notmuch::Threads * - Notmuch::Messages * - Notmuch::Thread * - Notmuch::Message * - Notmuch::Tags */ void Init_notmuch (void) { VALUE mod; ID_call = rb_intern ("call"); ID_db_create = rb_intern ("create"); ID_db_mode = rb_intern ("mode"); mod = rb_define_module ("Notmuch"); /* * Document-const: Notmuch::MODE_READ_ONLY * * Open the database in read only mode */ rb_define_const (mod, "MODE_READ_ONLY", INT2FIX (NOTMUCH_DATABASE_MODE_READ_ONLY)); /* * Document-const: Notmuch::MODE_READ_WRITE * * Open the database in read write mode */ rb_define_const (mod, "MODE_READ_WRITE", INT2FIX (NOTMUCH_DATABASE_MODE_READ_WRITE)); /* * Document-const: Notmuch::SORT_OLDEST_FIRST * * Sort query results by oldest first */ rb_define_const (mod, "SORT_OLDEST_FIRST", INT2FIX (NOTMUCH_SORT_OLDEST_FIRST)); /* * Document-const: Notmuch::SORT_NEWEST_FIRST * * Sort query results by newest first */ rb_define_const (mod, "SORT_NEWEST_FIRST", INT2FIX (NOTMUCH_SORT_NEWEST_FIRST)); /* * Document-const: Notmuch::SORT_MESSAGE_ID * * Sort query results by message id */ rb_define_const (mod, "SORT_MESSAGE_ID", INT2FIX (NOTMUCH_SORT_MESSAGE_ID)); /* * Document-const: Notmuch::SORT_UNSORTED * * Do not sort query results */ rb_define_const (mod, "SORT_UNSORTED", INT2FIX (NOTMUCH_SORT_UNSORTED)); /* * Document-const: Notmuch::MESSAGE_FLAG_MATCH * * Message flag "match" */ rb_define_const (mod, "MESSAGE_FLAG_MATCH", INT2FIX (NOTMUCH_MESSAGE_FLAG_MATCH)); /* * Document-const: Notmuch::MESSAGE_FLAG_EXCLUDED * * Message flag "excluded" */ rb_define_const (mod, "MESSAGE_FLAG_EXCLUDED", INT2FIX (NOTMUCH_MESSAGE_FLAG_EXCLUDED)); /* * Document-const: Notmuch::TAG_MAX * * Maximum allowed length of a tag */ rb_define_const (mod, "TAG_MAX", INT2FIX (NOTMUCH_TAG_MAX)); /* * Document-class: Notmuch::BaseError * * Base class for all notmuch exceptions */ notmuch_rb_eBaseError = rb_define_class_under (mod, "BaseError", rb_eStandardError); /* * Document-class: Notmuch::DatabaseError * * Raised when the database can't be created or opened. */ notmuch_rb_eDatabaseError = rb_define_class_under (mod, "DatabaseError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::MemoryError * * Raised when notmuch is out of memory */ notmuch_rb_eMemoryError = rb_define_class_under (mod, "MemoryError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::ReadOnlyError * * Raised when an attempt was made to write to a database opened in read-only * mode. */ notmuch_rb_eReadOnlyError = rb_define_class_under (mod, "ReadOnlyError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::XapianError * * Raised when a Xapian exception occurs */ notmuch_rb_eXapianError = rb_define_class_under (mod, "XapianError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::FileError * * Raised when an error occurs trying to read or write to a file. */ notmuch_rb_eFileError = rb_define_class_under (mod, "FileError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::FileNotEmailError * * Raised when a file is presented that doesn't appear to be an email message. */ notmuch_rb_eFileNotEmailError = rb_define_class_under (mod, "FileNotEmailError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::NullPointerError * * Raised when the user erroneously passes a +NULL+ pointer to a notmuch * function. */ notmuch_rb_eNullPointerError = rb_define_class_under (mod, "NullPointerError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::TagTooLongError * * Raised when a tag value is too long (exceeds Notmuch::TAG_MAX) */ notmuch_rb_eTagTooLongError = rb_define_class_under (mod, "TagTooLongError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::UnbalancedFreezeThawError * * Raised when the notmuch_message_thaw function has been called more times * than notmuch_message_freeze. */ notmuch_rb_eUnbalancedFreezeThawError = rb_define_class_under (mod, "UnbalancedFreezeThawError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::UnbalancedAtomicError * * Raised when notmuch_database_end_atomic has been called more times than * notmuch_database_begin_atomic */ notmuch_rb_eUnbalancedAtomicError = rb_define_class_under (mod, "UnbalancedAtomicError", notmuch_rb_eBaseError); /* * Document-class: Notmuch::Database * * Notmuch database interaction */ notmuch_rb_cDatabase = rb_define_class_under (mod, "Database", rb_cData); rb_define_alloc_func (notmuch_rb_cDatabase, notmuch_rb_database_alloc); rb_define_singleton_method (notmuch_rb_cDatabase, "open", notmuch_rb_database_open, -1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "initialize", notmuch_rb_database_initialize, -1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "close", notmuch_rb_database_close, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "path", notmuch_rb_database_path, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "version", notmuch_rb_database_version, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "needs_upgrade?", notmuch_rb_database_needs_upgrade, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "upgrade!", notmuch_rb_database_upgrade, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "begin_atomic", notmuch_rb_database_begin_atomic, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "end_atomic", notmuch_rb_database_end_atomic, 0); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "get_directory", notmuch_rb_database_get_directory, 1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "add_message", notmuch_rb_database_add_message, 1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "remove_message", notmuch_rb_database_remove_message, 1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "find_message", notmuch_rb_database_find_message, 1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "find_message_by_filename", notmuch_rb_database_find_message_by_filename, 1); /* in database.c */ rb_define_method (notmuch_rb_cDatabase, "query", notmuch_rb_database_query_create, 1); /* in database.c */ /* * Document-class: Notmuch::Directory * * Notmuch directory */ notmuch_rb_cDirectory = rb_define_class_under (mod, "Directory", rb_cData); rb_undef_method (notmuch_rb_cDirectory, "initialize"); rb_define_method (notmuch_rb_cDirectory, "destroy!", notmuch_rb_directory_destroy, 0); /* in directory.c */ rb_define_method (notmuch_rb_cDirectory, "mtime", notmuch_rb_directory_get_mtime, 0); /* in directory.c */ rb_define_method (notmuch_rb_cDirectory, "mtime=", notmuch_rb_directory_set_mtime, 1); /* in directory.c */ rb_define_method (notmuch_rb_cDirectory, "child_files", notmuch_rb_directory_get_child_files, 0); /* in directory.c */ rb_define_method (notmuch_rb_cDirectory, "child_directories", notmuch_rb_directory_get_child_directories, 0); /* in directory.c */ /* * Document-class: Notmuch::FileNames * * Notmuch file names */ notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cData); rb_undef_method (notmuch_rb_cFileNames, "initialize"); rb_define_method (notmuch_rb_cFileNames, "destroy!", notmuch_rb_filenames_destroy, 0); /* in filenames.c */ rb_define_method (notmuch_rb_cFileNames, "each", notmuch_rb_filenames_each, 0); /* in filenames.c */ rb_include_module (notmuch_rb_cFileNames, rb_mEnumerable); /* * Document-class: Notmuch::Query * * Notmuch query */ notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cData); rb_undef_method (notmuch_rb_cQuery, "initialize"); rb_define_method (notmuch_rb_cQuery, "destroy!", notmuch_rb_query_destroy, 0); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "sort", notmuch_rb_query_get_sort, 0); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "sort=", notmuch_rb_query_set_sort, 1); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "to_s", notmuch_rb_query_get_string, 0); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "add_tag_exclude", notmuch_rb_query_add_tag_exclude, 1); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "omit_excluded=", notmuch_rb_query_set_omit_excluded, 1); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "search_threads", notmuch_rb_query_search_threads, 0); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "search_messages", notmuch_rb_query_search_messages, 0); /* in query.c */ rb_define_method (notmuch_rb_cQuery, "count_messages", notmuch_rb_query_count_messages, 0); /* in query.c */ /* * Document-class: Notmuch::Threads * * Notmuch threads */ notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cData); rb_undef_method (notmuch_rb_cThreads, "initialize"); rb_define_method (notmuch_rb_cThreads, "destroy!", notmuch_rb_threads_destroy, 0); /* in threads.c */ rb_define_method (notmuch_rb_cThreads, "each", notmuch_rb_threads_each, 0); /* in threads.c */ rb_include_module (notmuch_rb_cThreads, rb_mEnumerable); /* * Document-class: Notmuch::Messages * * Notmuch messages */ notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cData); rb_undef_method (notmuch_rb_cMessages, "initialize"); rb_define_method (notmuch_rb_cMessages, "destroy!", notmuch_rb_messages_destroy, 0); /* in messages.c */ rb_define_method (notmuch_rb_cMessages, "each", notmuch_rb_messages_each, 0); /* in messages.c */ rb_define_method (notmuch_rb_cMessages, "tags", notmuch_rb_messages_collect_tags, 0); /* in messages.c */ rb_include_module (notmuch_rb_cMessages, rb_mEnumerable); /* * Document-class: Notmuch::Thread * * Notmuch thread */ notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cData); rb_undef_method (notmuch_rb_cThread, "initialize"); rb_define_method (notmuch_rb_cThread, "destroy!", notmuch_rb_thread_destroy, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "total_messages", notmuch_rb_thread_get_total_messages, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "toplevel_messages", notmuch_rb_thread_get_toplevel_messages, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "messages", notmuch_rb_thread_get_messages, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "matched_messages", notmuch_rb_thread_get_matched_messages, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "authors", notmuch_rb_thread_get_authors, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "subject", notmuch_rb_thread_get_subject, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "oldest_date", notmuch_rb_thread_get_oldest_date, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "newest_date", notmuch_rb_thread_get_newest_date, 0); /* in thread.c */ rb_define_method (notmuch_rb_cThread, "tags", notmuch_rb_thread_get_tags, 0); /* in thread.c */ /* * Document-class: Notmuch::Message * * Notmuch message */ notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cData); rb_undef_method (notmuch_rb_cMessage, "initialize"); rb_define_method (notmuch_rb_cMessage, "destroy!", notmuch_rb_message_destroy, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "message_id", notmuch_rb_message_get_message_id, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "thread_id", notmuch_rb_message_get_thread_id, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "replies", notmuch_rb_message_get_replies, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "filename", notmuch_rb_message_get_filename, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "filenames", notmuch_rb_message_get_filenames, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "get_flag", notmuch_rb_message_get_flag, 1); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "set_flag", notmuch_rb_message_set_flag, 2); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "date", notmuch_rb_message_get_date, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "header", notmuch_rb_message_get_header, 1); /* in message.c */ rb_define_alias (notmuch_rb_cMessage, "[]", "header"); rb_define_method (notmuch_rb_cMessage, "tags", notmuch_rb_message_get_tags, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "add_tag", notmuch_rb_message_add_tag, 1); /* in message.c */ rb_define_alias (notmuch_rb_cMessage, "<<", "add_tag"); rb_define_method (notmuch_rb_cMessage, "remove_tag", notmuch_rb_message_remove_tag, 1); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "remove_all_tags", notmuch_rb_message_remove_all_tags, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "maildir_flags_to_tags", notmuch_rb_message_maildir_flags_to_tags, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "tags_to_maildir_flags", notmuch_rb_message_tags_to_maildir_flags, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "freeze", notmuch_rb_message_freeze, 0); /* in message.c */ rb_define_method (notmuch_rb_cMessage, "thaw", notmuch_rb_message_thaw, 0); /* in message.c */ /* * Document-class: Notmuch::Tags * * Notmuch tags */ notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cData); rb_undef_method (notmuch_rb_cTags, "initialize"); rb_define_method (notmuch_rb_cTags, "destroy!", notmuch_rb_tags_destroy, 0); /* in tags.c */ rb_define_method (notmuch_rb_cTags, "each", notmuch_rb_tags_each, 0); /* in tags.c */ rb_include_module (notmuch_rb_cTags, rb_mEnumerable); } notmuch-0.17/bindings/ruby/message.c000066400000000000000000000172221226041107500174660ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: MESSAGE.destroy! => nil * * Destroys the message, freeing all resources allocated for it. */ VALUE notmuch_rb_message_destroy (VALUE self) { notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); notmuch_message_destroy (message); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: MESSAGE.message_id => String * * Get the message ID of 'message'. */ VALUE notmuch_rb_message_get_message_id (VALUE self) { const char *msgid; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); msgid = notmuch_message_get_message_id (message); return rb_str_new2 (msgid); } /* * call-seq: MESSAGE.thread_id => String * * Get the thread ID of 'message'. */ VALUE notmuch_rb_message_get_thread_id (VALUE self) { const char *tid; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); tid = notmuch_message_get_thread_id (message); return rb_str_new2 (tid); } /* * call-seq: MESSAGE.replies => MESSAGES * * Get a Notmuch::Messages enumerable for all of the replies to 'message'. */ VALUE notmuch_rb_message_get_replies (VALUE self) { notmuch_messages_t *messages; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); messages = notmuch_message_get_replies (message); return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages); } /* * call-seq: MESSAGE.filename => String * * Get a filename for the email corresponding to 'message' */ VALUE notmuch_rb_message_get_filename (VALUE self) { const char *fname; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); fname = notmuch_message_get_filename (message); return rb_str_new2 (fname); } /* * call-seq: MESSAGE.filanames => FILENAMES * * Get all filenames for the email corresponding to MESSAGE. */ VALUE notmuch_rb_message_get_filenames (VALUE self) { notmuch_filenames_t *fnames; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); fnames = notmuch_message_get_filenames (message); return Data_Wrap_Struct (notmuch_rb_cFileNames, NULL, NULL, fnames); } /* * call-seq: MESSAGE.get_flag (flag) => true or false * * Get a value of a flag for the email corresponding to 'message' */ VALUE notmuch_rb_message_get_flag (VALUE self, VALUE flagv) { notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); if (!FIXNUM_P (flagv)) rb_raise (rb_eTypeError, "Flag not a Fixnum"); return notmuch_message_get_flag (message, FIX2INT (flagv)) ? Qtrue : Qfalse; } /* * call-seq: MESSAGE.set_flag (flag, value) => nil * * Set a value of a flag for the email corresponding to 'message' */ VALUE notmuch_rb_message_set_flag (VALUE self, VALUE flagv, VALUE valuev) { notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); if (!FIXNUM_P (flagv)) rb_raise (rb_eTypeError, "Flag not a Fixnum"); notmuch_message_set_flag (message, FIX2INT (flagv), RTEST (valuev)); return Qnil; } /* * call-seq: MESSAGE.date => Fixnum * * Get the date of 'message' */ VALUE notmuch_rb_message_get_date (VALUE self) { notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); return UINT2NUM (notmuch_message_get_date (message)); } /* * call-seq: MESSAGE.header (name) => String * * Get the value of the specified header from 'message' */ VALUE notmuch_rb_message_get_header (VALUE self, VALUE headerv) { const char *header, *value; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); SafeStringValue (headerv); header = RSTRING_PTR (headerv); value = notmuch_message_get_header (message, header); if (!value) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return rb_str_new2 (value); } /* * call-seq: MESSAGE.tags => TAGS * * Get a Notmuch::Tags enumerable for all of the tags of 'message'. */ VALUE notmuch_rb_message_get_tags (VALUE self) { notmuch_message_t *message; notmuch_tags_t *tags; Data_Get_Notmuch_Message (self, message); tags = notmuch_message_get_tags (message); if (!tags) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags); } /* * call-seq: MESSAGE.add_tag (tag) => true * * Add a tag to the 'message' */ VALUE notmuch_rb_message_add_tag (VALUE self, VALUE tagv) { const char *tag; notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); SafeStringValue (tagv); tag = RSTRING_PTR (tagv); ret = notmuch_message_add_tag (message, tag); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.remove_tag (tag) => true * * Remove a tag from the 'message' */ VALUE notmuch_rb_message_remove_tag (VALUE self, VALUE tagv) { const char *tag; notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); SafeStringValue (tagv); tag = RSTRING_PTR (tagv); ret = notmuch_message_remove_tag (message, tag); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.remove_all_tags => true * * Remove all tags of the 'message' */ VALUE notmuch_rb_message_remove_all_tags (VALUE self) { notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); ret = notmuch_message_remove_all_tags (message); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.maildir_flags_to_tags => true * * Add/remove tags according to maildir flags in the message filename (s) */ VALUE notmuch_rb_message_maildir_flags_to_tags (VALUE self) { notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); ret = notmuch_message_maildir_flags_to_tags (message); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.tags_to_maildir_flags => true * * Rename message filename (s) to encode tags as maildir flags */ VALUE notmuch_rb_message_tags_to_maildir_flags (VALUE self) { notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); ret = notmuch_message_tags_to_maildir_flags (message); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.freeze => true * * Freeze the 'message' */ VALUE notmuch_rb_message_freeze (VALUE self) { notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); ret = notmuch_message_freeze (message); notmuch_rb_status_raise (ret); return Qtrue; } /* * call-seq: MESSAGE.thaw => true * * Thaw a 'message' */ VALUE notmuch_rb_message_thaw (VALUE self) { notmuch_status_t ret; notmuch_message_t *message; Data_Get_Notmuch_Message (self, message); ret = notmuch_message_thaw (message); notmuch_rb_status_raise (ret); return Qtrue; } notmuch-0.17/bindings/ruby/messages.c000066400000000000000000000041201226041107500176420ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: MESSAGES.destroy! => nil * * Destroys the messages, freeing all resources allocated for it. */ VALUE notmuch_rb_messages_destroy (VALUE self) { notmuch_messages_t *messages; Data_Get_Notmuch_Messages (self, messages); notmuch_messages_destroy (messages); DATA_PTR (self) = NULL; return Qnil; } /* call-seq: MESSAGES.each {|item| block } => MESSAGES * * Calls +block+ once for each message in +self+, passing that element as a * parameter. */ VALUE notmuch_rb_messages_each (VALUE self) { notmuch_message_t *message; notmuch_messages_t *messages; Data_Get_Notmuch_Messages (self, messages); for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); rb_yield (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message)); } return self; } /* * call-seq: MESSAGES.tags => TAGS * * Collect tags from the messages */ VALUE notmuch_rb_messages_collect_tags (VALUE self) { notmuch_tags_t *tags; notmuch_messages_t *messages; Data_Get_Notmuch_Messages (self, messages); tags = notmuch_messages_collect_tags (messages); if (!tags) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags); } notmuch-0.17/bindings/ruby/query.c000066400000000000000000000100251226041107500172010ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011, 2012 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: QUERY.destroy! => nil * * Destroys the query, freeing all resources allocated for it. */ VALUE notmuch_rb_query_destroy (VALUE self) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); notmuch_query_destroy (query); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: QUERY.sort => fixnum * * Get sort type of the +QUERY+ */ VALUE notmuch_rb_query_get_sort (VALUE self) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); return FIX2INT (notmuch_query_get_sort (query)); } /* * call-seq: QUERY.sort=(fixnum) => nil * * Set sort type of the +QUERY+ */ VALUE notmuch_rb_query_set_sort (VALUE self, VALUE sortv) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); if (!FIXNUM_P (sortv)) rb_raise (rb_eTypeError, "Not a Fixnum"); notmuch_query_set_sort (query, FIX2UINT (sortv)); return Qnil; } /* * call-seq: QUERY.to_s => string * * Get query string of the +QUERY+ */ VALUE notmuch_rb_query_get_string (VALUE self) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); return rb_str_new2 (notmuch_query_get_query_string (query)); } /* * call-seq: QUERY.add_tag_exclude(tag) => nil * * Add a tag that will be excluded from the query results by default. */ VALUE notmuch_rb_query_add_tag_exclude (VALUE self, VALUE tagv) { notmuch_query_t *query; const char *tag; Data_Get_Notmuch_Query (self, query); tag = RSTRING_PTR(tagv); notmuch_query_add_tag_exclude(query, tag); return Qnil; } /* * call-seq: QUERY.omit_excluded=(boolean) => nil * * Specify whether to omit excluded results or simply flag them. * By default, this is set to +true+. */ VALUE notmuch_rb_query_set_omit_excluded (VALUE self, VALUE omitv) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); notmuch_query_set_omit_excluded (query, RTEST (omitv)); return Qnil; } /* * call-seq: QUERY.search_threads => THREADS * * Search for threads */ VALUE notmuch_rb_query_search_threads (VALUE self) { notmuch_query_t *query; notmuch_threads_t *threads; Data_Get_Notmuch_Query (self, query); threads = notmuch_query_search_threads (query); if (!threads) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cThreads, NULL, NULL, threads); } /* * call-seq: QUERY.search_messages => MESSAGES * * Search for messages */ VALUE notmuch_rb_query_search_messages (VALUE self) { notmuch_query_t *query; notmuch_messages_t *messages; Data_Get_Notmuch_Query (self, query); messages = notmuch_query_search_messages (query); if (!messages) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages); } /* * call-seq: QUERY.count_messages => Fixnum * * Return an estimate of the number of messages matching a search */ VALUE notmuch_rb_query_count_messages (VALUE self) { notmuch_query_t *query; Data_Get_Notmuch_Query (self, query); /* Xapian exceptions are not handled properly. * (function may return 0 after printing a message) * Thus there is nothing we can do here... */ return UINT2NUM(notmuch_query_count_messages(query)); } notmuch-0.17/bindings/ruby/rdoc.sh000077500000000000000000000005131226041107500171570ustar00rootroot00000000000000#!/bin/sh if test -z "$RDOC"; then RDOC=rdoc if which rdoc19 >/dev/null 2>&1; then RDOC=rdoc19 fi fi set -e set -x $RDOC --main 'Notmuch' --title 'Notmuch Ruby API' --op ruby *.c if test "$1" = "--upload"; then rsync -avze ssh --delete --partial --progress ruby bach.exherbo.org:public_html/notmuch/ fi notmuch-0.17/bindings/ruby/status.c000066400000000000000000000036751226041107500173740ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" void notmuch_rb_status_raise (notmuch_status_t status) { switch (status) { case NOTMUCH_STATUS_SUCCESS: case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: break; case NOTMUCH_STATUS_OUT_OF_MEMORY: rb_raise (notmuch_rb_eMemoryError, "out of memory"); case NOTMUCH_STATUS_READ_ONLY_DATABASE: rb_raise (notmuch_rb_eReadOnlyError, "read-only database"); case NOTMUCH_STATUS_XAPIAN_EXCEPTION: rb_raise (notmuch_rb_eXapianError, "xapian exception"); case NOTMUCH_STATUS_FILE_ERROR: rb_raise (notmuch_rb_eFileError, "failed to read/write file"); case NOTMUCH_STATUS_FILE_NOT_EMAIL: rb_raise (notmuch_rb_eFileNotEmailError, "file not email"); case NOTMUCH_STATUS_NULL_POINTER: rb_raise (notmuch_rb_eNullPointerError, "null pointer"); case NOTMUCH_STATUS_TAG_TOO_LONG: rb_raise (notmuch_rb_eTagTooLongError, "tag too long"); case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: rb_raise (notmuch_rb_eUnbalancedFreezeThawError, "unbalanced freeze/thaw"); case NOTMUCH_STATUS_UNBALANCED_ATOMIC: rb_raise (notmuch_rb_eUnbalancedAtomicError, "unbalanced atomic"); default: rb_raise (notmuch_rb_eBaseError, "unknown notmuch error"); } } notmuch-0.17/bindings/ruby/tags.c000066400000000000000000000030171226041107500167750ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: TAGS.destroy! => nil * * Destroys the tags, freeing all resources allocated for it. */ VALUE notmuch_rb_tags_destroy (VALUE self) { notmuch_tags_t *tags; Data_Get_Notmuch_Tags (self, tags); notmuch_tags_destroy (tags); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: TAGS.each {|item| block } => TAGS * * Calls +block+ once for each element in +self+, passing that element as a * parameter. */ VALUE notmuch_rb_tags_each (VALUE self) { const char *tag; notmuch_tags_t *tags; Data_Get_Notmuch_Tags (self, tags); for (; notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) { tag = notmuch_tags_get (tags); rb_yield (rb_str_new2 (tag)); } return self; } notmuch-0.17/bindings/ruby/thread.c000066400000000000000000000112401226041107500173030ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: THREAD.destroy! => nil * * Destroys the thread, freeing all resources allocated for it. */ VALUE notmuch_rb_thread_destroy (VALUE self) { notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); notmuch_thread_destroy (thread); DATA_PTR (self) = NULL; return Qnil; } /* * call-seq: THREAD.thread_id => String * * Returns the thread id */ VALUE notmuch_rb_thread_get_thread_id (VALUE self) { const char *tid; notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); tid = notmuch_thread_get_thread_id (thread); return rb_str_new2 (tid); } /* * call-seq: THREAD.total_messages => fixnum * * Returns the number of total messages */ VALUE notmuch_rb_thread_get_total_messages (VALUE self) { notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); return INT2FIX (notmuch_thread_get_total_messages (thread)); } /* * call-seq: THREAD.toplevel_messages => MESSAGES * * Get a Notmuch::Messages iterator for the top level messages in thread. */ VALUE notmuch_rb_thread_get_toplevel_messages (VALUE self) { notmuch_messages_t *messages; notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); messages = notmuch_thread_get_toplevel_messages (thread); if (!messages) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages); } /* * call-seq: THREAD.messages => MESSAGES * * Get a Notmuch::Messages iterator for the all messages in thread. */ VALUE notmuch_rb_thread_get_messages (VALUE self) { notmuch_messages_t *messages; notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); messages = notmuch_thread_get_messages (thread); if (!messages) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cMessages, NULL, NULL, messages); } /* * call-seq: THREAD.matched_messages => fixnum * * Get the number of messages in thread that matched the search */ VALUE notmuch_rb_thread_get_matched_messages (VALUE self) { notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); return INT2FIX (notmuch_thread_get_matched_messages (thread)); } /* * call-seq: THREAD.authors => String * * Get a comma-separated list of the names of the authors. */ VALUE notmuch_rb_thread_get_authors (VALUE self) { const char *authors; notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); authors = notmuch_thread_get_authors (thread); return rb_str_new2 (authors); } /* * call-seq: THREAD.subject => String * * Returns the subject of the thread */ VALUE notmuch_rb_thread_get_subject (VALUE self) { const char *subject; notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); subject = notmuch_thread_get_subject (thread); return rb_str_new2 (subject); } /* * call-seq: THREAD.oldest_date => Fixnum * * Get the date of the oldest message in thread. */ VALUE notmuch_rb_thread_get_oldest_date (VALUE self) { notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); return UINT2NUM (notmuch_thread_get_oldest_date (thread)); } /* * call-seq: THREAD.newest_date => fixnum * * Get the date of the newest message in thread. */ VALUE notmuch_rb_thread_get_newest_date (VALUE self) { notmuch_thread_t *thread; Data_Get_Notmuch_Thread (self, thread); return UINT2NUM (notmuch_thread_get_newest_date (thread)); } /* * call-seq: THREAD.tags => TAGS * * Get a Notmuch::Tags iterator for the tags of the thread */ VALUE notmuch_rb_thread_get_tags (VALUE self) { notmuch_thread_t *thread; notmuch_tags_t *tags; Data_Get_Notmuch_Thread (self, thread); tags = notmuch_thread_get_tags (thread); if (!tags) rb_raise (notmuch_rb_eMemoryError, "Out of memory"); return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags); } notmuch-0.17/bindings/ruby/threads.c000066400000000000000000000032131226041107500174670ustar00rootroot00000000000000/* The Ruby interface to the notmuch mail library * * Copyright © 2010, 2011 Ali Polatel * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #include "defs.h" /* * call-seq: THREADS.destroy! => nil * * Destroys the threads, freeing all resources allocated for it. */ VALUE notmuch_rb_threads_destroy (VALUE self) { notmuch_threads_t *threads; Data_Get_Struct (self, notmuch_threads_t, threads); notmuch_threads_destroy (threads); DATA_PTR (self) = NULL; return Qnil; } /* call-seq: THREADS.each {|item| block } => THREADS * * Calls +block+ once for each thread in +self+, passing that element as a * parameter. */ VALUE notmuch_rb_threads_each (VALUE self) { notmuch_thread_t *thread; notmuch_threads_t *threads; Data_Get_Notmuch_Threads (self, threads); for (; notmuch_threads_valid (threads); notmuch_threads_move_to_next (threads)) { thread = notmuch_threads_get (threads); rb_yield (Data_Wrap_Struct (notmuch_rb_cThread, NULL, NULL, thread)); } return self; } notmuch-0.17/command-line-arguments.c000066400000000000000000000123571226041107500176360ustar00rootroot00000000000000#include #include #include #include "error_util.h" #include "command-line-arguments.h" /* Search the array of keywords for a given argument, assigning the output variable to the corresponding value. Return FALSE if nothing matches. */ static notmuch_bool_t _process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { const notmuch_keyword_t *keywords = arg_desc->keywords; if (next == '\0') { /* No keyword given */ arg_str = ""; } while (keywords->name) { if (strcmp (arg_str, keywords->name) == 0) { if (arg_desc->output_var) { *((int *)arg_desc->output_var) = keywords->value; } return TRUE; } keywords++; } if (next != '\0') fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name); else fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name); return FALSE; } static notmuch_bool_t _process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { if (next == '\0') { *((notmuch_bool_t *)arg_desc->output_var) = TRUE; return TRUE; } if (strcmp (arg_str, "false") == 0) { *((notmuch_bool_t *)arg_desc->output_var) = FALSE; return TRUE; } if (strcmp (arg_str, "true") == 0) { *((notmuch_bool_t *)arg_desc->output_var) = TRUE; return TRUE; } fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name); return FALSE; } static notmuch_bool_t _process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { char *endptr; if (next == '\0' || arg_str[0] == '\0') { fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name); return FALSE; } *((int *)arg_desc->output_var) = strtol (arg_str, &endptr, 10); if (*endptr == '\0') return TRUE; fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n", arg_str, arg_desc->name); return FALSE; } static notmuch_bool_t _process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) { if (next == '\0') { fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name); return FALSE; } if (arg_str[0] == '\0') { fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name); return FALSE; } *((const char **)arg_desc->output_var) = arg_str; return TRUE; } /* Search for the {pos_arg_index}th position argument, return FALSE if that does not exist. */ notmuch_bool_t parse_position_arg (const char *arg_str, int pos_arg_index, const notmuch_opt_desc_t *arg_desc) { int pos_arg_counter = 0; while (arg_desc->opt_type != NOTMUCH_OPT_END){ if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) { if (pos_arg_counter == pos_arg_index) { if (arg_desc->output_var) { *((const char **)arg_desc->output_var) = arg_str; } return TRUE; } pos_arg_counter++; } arg_desc++; } return FALSE; } /* * Search for a non-positional (i.e. starting with --) argument matching arg, * parse a possible value, and assign to *output_var */ notmuch_bool_t parse_option (const char *arg, const notmuch_opt_desc_t *options) { assert(arg); assert(options); arg += 2; const notmuch_opt_desc_t *try; for (try = options; try->opt_type != NOTMUCH_OPT_END; try++) { if (try->name && strncmp (arg, try->name, strlen (try->name)) == 0) { char next = arg[strlen (try->name)]; const char *value= arg+strlen(try->name)+1; /* If we have not reached the end of the argument (i.e. the next character is not a space or delimiter) then the argument could still match a longer option name later in the option table. */ if (next != '=' && next != ':' && next != '\0') continue; if (try->output_var == NULL) INTERNAL_ERROR ("output pointer NULL for option %s", try->name); switch (try->opt_type) { case NOTMUCH_OPT_KEYWORD: return _process_keyword_arg (try, next, value); break; case NOTMUCH_OPT_BOOLEAN: return _process_boolean_arg (try, next, value); break; case NOTMUCH_OPT_INT: return _process_int_arg (try, next, value); break; case NOTMUCH_OPT_STRING: return _process_string_arg (try, next, value); break; case NOTMUCH_OPT_POSITION: case NOTMUCH_OPT_END: default: INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type); /*UNREACHED*/ } } } fprintf (stderr, "Unrecognized option: --%s\n", arg); return FALSE; } /* See command-line-arguments.h for description */ int parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index) { int pos_arg_index = 0; notmuch_bool_t more_args = TRUE; while (more_args && opt_index < argc) { if (strncmp (argv[opt_index],"--",2) != 0) { more_args = parse_position_arg (argv[opt_index], pos_arg_index, options); if (more_args) { pos_arg_index++; opt_index++; } } else { if (strlen (argv[opt_index]) == 2) return opt_index+1; more_args = parse_option (argv[opt_index], options); if (more_args) { opt_index++; } else { opt_index = -1; } } } return opt_index; } notmuch-0.17/command-line-arguments.h000066400000000000000000000041321226041107500176330ustar00rootroot00000000000000#ifndef NOTMUCH_OPTS_H #define NOTMUCH_OPTS_H #include "notmuch.h" enum notmuch_opt_type { NOTMUCH_OPT_END = 0, NOTMUCH_OPT_BOOLEAN, /* --verbose */ NOTMUCH_OPT_INT, /* --frob=8 */ NOTMUCH_OPT_KEYWORD, /* --format=raw|json|text */ NOTMUCH_OPT_STRING, /* --file=/tmp/gnarf.txt */ NOTMUCH_OPT_POSITION /* notmuch dump pos_arg */ }; /* * Describe one of the possibilities for a keyword option * 'value' will be copied to the output variable */ typedef struct notmuch_keyword { const char *name; int value; } notmuch_keyword_t; /* * Describe one option. * * First two parameters are mandatory. * * name is mandatory _except_ for positional arguments. * * arg_id is currently unused, but could define short arguments. * * keywords is a (possibly NULL) pointer to an array of keywords */ typedef struct notmuch_opt_desc { enum notmuch_opt_type opt_type; void *output_var; const char *name; int arg_id; const struct notmuch_keyword *keywords; } notmuch_opt_desc_t; /* This is the main entry point for command line argument parsing. Parse command line arguments according to structure options, starting at position opt_index. All output of parsed values is via pointers in options. Parsing stops at -- (consumed) or at the (k+1)st argument not starting with -- (a "positional argument") if options contains k positional argument descriptors. Returns the index of first non-parsed argument, or -1 in case of error. */ int parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index); /* * If the argument parsing loop provided by parse_arguments is not * flexible enough, then the user might be interested in the following * routines, but note that the API to parse_option might have to * change. See command-line-arguments.c for descriptions of these * functions. */ notmuch_bool_t parse_option (const char *arg, const notmuch_opt_desc_t* options); notmuch_bool_t parse_position_arg (const char *arg, int position_arg_index, const notmuch_opt_desc_t* options); #endif notmuch-0.17/compat/000077500000000000000000000000001226041107500143775ustar00rootroot00000000000000notmuch-0.17/compat/Makefile000066400000000000000000000000651226041107500160400ustar00rootroot00000000000000all: $(MAKE) -C .. all .DEFAULT: $(MAKE) -C .. $@ notmuch-0.17/compat/Makefile.local000066400000000000000000000006751226041107500171400ustar00rootroot00000000000000# -*- makefile -*- dir := compat extra_cflags += -I$(srcdir)/$(dir) notmuch_compat_srcs := ifneq ($(HAVE_GETLINE),1) notmuch_compat_srcs += $(dir)/getline.c $(dir)/getdelim.c endif ifneq ($(HAVE_STRCASESTR),1) notmuch_compat_srcs += $(dir)/strcasestr.c endif ifneq ($(HAVE_STRSEP),1) notmuch_compat_srcs += $(dir)/strsep.c endif ifneq ($(HAVE_TIMEGM),1) notmuch_compat_srcs += $(dir)/timegm.c endif SRCS := $(SRCS) $(notmuch_compat_srcs) notmuch-0.17/compat/README000066400000000000000000000011301226041107500152520ustar00rootroot00000000000000notmuch/compat This directory consists of three things: 1. Small programs used by the notmuch configure script to test for the availability of certain system features, (library functions, etc.). For example: have_getline.c 2. Compatibility implementations of those system features for systems that don't provide their own versions. For example: getline.c The compilation of these files is made conditional on the output of the test programs from [1]. 3. Macro definitions abstracting compiler differences (e.g. function attributes). For example: function-attributes.h notmuch-0.17/compat/check_asctime.c000066400000000000000000000001731226041107500173260ustar00rootroot00000000000000#include #include int main() { struct tm tm; (void) asctime_r (&tm, NULL); return (0); } notmuch-0.17/compat/check_getpwuid.c000066400000000000000000000002411226041107500175250ustar00rootroot00000000000000#include #include int main() { struct passwd passwd, *ignored; (void) getpwuid_r (0, &passwd, NULL, 0, &ignored); return (0); } notmuch-0.17/compat/compat.h000066400000000000000000000040121226041107500160300ustar00rootroot00000000000000/* notmuch - Not much of an email library, (just index and search) * * Copyright © 2009 Carl Worth * * 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 http://www.gnu.org/licenses/ . * * Author: Carl Worth */ /* This header file defines functions that will only be conditionally * compiled for compatibility on systems that don't provide their own * implementations of the functions. */ #ifndef NOTMUCH_COMPAT_H #define NOTMUCH_COMPAT_H #ifdef __cplusplus extern "C" { #endif #if !STD_GETPWUID #define _POSIX_PTHREAD_SEMANTICS 1 #endif #if !STD_ASCTIME #define _POSIX_PTHREAD_SEMANTICS 1 #endif #if !HAVE_GETLINE #include #include ssize_t getline (char **lineptr, size_t *n, FILE *stream); ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp); #endif /* !HAVE_GETLINE */ #if !HAVE_STRCASESTR char* strcasestr(const char *haystack, const char *needle); #endif /* !HAVE_STRCASESTR */ #if !HAVE_STRSEP char *strsep(char **stringp, const char *delim); #endif /* !HAVE_STRSEP */ #if !HAVE_TIMEGM #include time_t timegm (struct tm *tm); #endif /* !HAVE_TIMEGM */ /* Silence gcc warnings about unused results. These warnings exist * for a reason; any use of this needs to be justified. */ #ifdef __GNUC__ #define IGNORE_RESULT(x) ({ __typeof__(x) __z = (x); (void)(__z = __z); }) #else /* !__GNUC__ */ #define IGNORE_RESULT(x) x #endif /* __GNUC__ */ #ifdef __cplusplus } #endif #endif /* NOTMUCH_COMPAT_H */ notmuch-0.17/compat/function-attributes.h000066400000000000000000000030501226041107500205570ustar00rootroot00000000000000/* function-attributes.h - Provides compiler abstractions for * function attributes * * Copyright (c) 2012 Justus Winter <4winter@informatik.uni-hamburg.de> * * 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 http://www.gnu.org/licenses/ . */ #ifndef FUNCTION_ATTRIBUTES_H #define FUNCTION_ATTRIBUTES_H /* clang provides this macro to test for support for function * attributes. If it isn't defined, this provides a compatibility * macro for other compilers. */ #ifndef __has_attribute #define __has_attribute(x) 0 #endif /* Provide a NORETURN_ATTRIBUTE macro similar to PRINTF_ATTRIBUTE from * talloc. * * This attribute is understood by gcc since version 2.5. clang * provides support for testing for function attributes. */ #ifndef NORETURN_ATTRIBUTE #if (__GNUC__ >= 3 || \ (__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || \ __has_attribute (noreturn)) #define NORETURN_ATTRIBUTE __attribute__ ((noreturn)) #else #define NORETURN_ATTRIBUTE #endif #endif #endif notmuch-0.17/compat/getdelim.c000066400000000000000000000063501226041107500163410ustar00rootroot00000000000000/* getdelim.c --- Implementation of replacement getdelim function. Copyright (C) 1994, 1996, 1997, 1998, 2001, 2003, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. 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, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Ported from glibc by Simon Josefsson. */ #include "compat.h" #include #include #include #include #include #ifndef SSIZE_MAX # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2)) #endif #if USE_UNLOCKED_IO # include "unlocked-io.h" # define getc_maybe_unlocked(fp) getc(fp) #elif !HAVE_FLOCKFILE || !HAVE_FUNLOCKFILE || !HAVE_DECL_GETC_UNLOCKED # undef flockfile # undef funlockfile # define flockfile(x) ((void) 0) # define funlockfile(x) ((void) 0) # define getc_maybe_unlocked(fp) getc(fp) #else # define getc_maybe_unlocked(fp) getc_unlocked(fp) #endif /* Read up to (and including) a DELIMITER from FP into *LINEPTR (and NUL-terminate it). *LINEPTR is a pointer returned from malloc (or NULL), pointing to *N characters of space. It is realloc'ed as necessary. Returns the number of characters read (not including the null terminator), or -1 on error or EOF. */ ssize_t getdelim (char **lineptr, size_t *n, int delimiter, FILE *fp) { ssize_t result = -1; size_t cur_len = 0; if (lineptr == NULL || n == NULL || fp == NULL) { errno = EINVAL; return -1; } flockfile (fp); if (*lineptr == NULL || *n == 0) { char *new_lineptr; *n = 120; new_lineptr = (char *) realloc (*lineptr, *n); if (new_lineptr == NULL) { result = -1; goto unlock_return; } *lineptr = new_lineptr; } for (;;) { int i; i = getc_maybe_unlocked (fp); if (i == EOF) { result = -1; break; } /* Make enough space for len+1 (for final NUL) bytes. */ if (cur_len + 1 >= *n) { size_t needed_max = SSIZE_MAX < SIZE_MAX ? (size_t) SSIZE_MAX + 1 : SIZE_MAX; size_t needed = 2 * *n + 1; /* Be generous. */ char *new_lineptr; if (needed_max < needed) needed = needed_max; if (cur_len + 1 >= needed) { result = -1; errno = EOVERFLOW; goto unlock_return; } new_lineptr = (char *) realloc (*lineptr, needed); if (new_lineptr == NULL) { result = -1; goto unlock_return; } *lineptr = new_lineptr; *n = needed; } (*lineptr)[cur_len] = i; cur_len++; if (i == delimiter) break; } (*lineptr)[cur_len] = '\0'; result = cur_len ? (ssize_t) cur_len : result; unlock_return: funlockfile (fp); /* doesn't set errno */ return result; } notmuch-0.17/compat/getline.c000066400000000000000000000017761226041107500162050ustar00rootroot00000000000000/* getline.c --- Implementation of replacement getline function. Copyright (C) 2005, 2006, 2007 Free Software Foundation, Inc. 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, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Written by Simon Josefsson. */ #include "compat.h" #include ssize_t getline (char **lineptr, size_t *n, FILE *stream) { return getdelim (lineptr, n, '\n', stream); } notmuch-0.17/compat/have_getline.c000066400000000000000000000003261226041107500171760ustar00rootroot00000000000000#define _GNU_SOURCE #include #include int main() { ssize_t count = 0; size_t n = 0; char **lineptr = NULL; FILE *stream = NULL; count = getline(lineptr, &n, stream); } notmuch-0.17/compat/have_strcasestr.c000066400000000000000000000002301226041107500177360ustar00rootroot00000000000000#define _GNU_SOURCE #include int main() { char *found; const char *haystack, *needle; found = strcasestr(haystack, needle); } notmuch-0.17/compat/have_strsep.c000066400000000000000000000002311226041107500170620ustar00rootroot00000000000000#define _GNU_SOURCE #include int main() { char *found; char **stringp; const char *delim; found = strsep(stringp, delim); } notmuch-0.17/compat/have_timegm.c000066400000000000000000000001371226041107500170310ustar00rootroot00000000000000#include #include "compat.h" int main() { return (int) timegm((struct tm *)0); } notmuch-0.17/compat/strcasestr.c000066400000000000000000000023371226041107500167450ustar00rootroot00000000000000/* * slow simplistic reimplementation of strcasestr for systems that * don't include it in their library * * based on a GPL implementation in OpenTTD found under GPL v2 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, version 2. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Imported into notmuch by Dirk Hohndel - original author unknown. */ #include #include "compat.h" char *strcasestr(const char *haystack, const char *needle) { size_t hay_len = strlen(haystack); size_t needle_len = strlen(needle); while (hay_len >= needle_len) { if (strncasecmp(haystack, needle, needle_len) == 0) return (char *) haystack; haystack++; hay_len--; } return NULL; } notmuch-0.17/compat/strsep.c000066400000000000000000000034431226041107500160670ustar00rootroot00000000000000/* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include /* Taken from glibc 2.6.1 */ char *strsep (char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; /* A frequent case is when the delimiter string contains only one character. Here we don't need to call the expensive `strpbrk' function and instead work using `strchr'. */ if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') end = NULL; else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr (begin + 1, ch); } } else /* Find the end of the token. */ end = strpbrk (begin, delim); if (end) { /* Terminate the token and set *STRINGP past NUL character. */ *end++ = '\0'; *stringp = end; } else /* No more delimiters; this is the last token. */ *stringp = NULL; return begin; } notmuch-0.17/compat/timegm.c000066400000000000000000000033461226041107500160330ustar00rootroot00000000000000/* timegm.c --- Implementation of replacement timegm function. 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, 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Copyright 2013 Blake Jones. */ #include #include "compat.h" static int leapyear (int year) { return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)); } /* * This is a simple implementation of timegm() which does what is needed * by create_output() -- just turns the "struct tm" into a GMT time_t. * It does not normalize any of the fields of the "struct tm", nor does * it set tm_wday or tm_yday. */ time_t timegm (struct tm *tm) { int monthlen[2][12] = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, }; int year, month, days; days = 365 * (tm->tm_year - 70); for (year = 70; year < tm->tm_year; year++) { if (leapyear(1900 + year)) { days++; } } for (month = 0; month < tm->tm_mon; month++) { days += monthlen[leapyear(1900 + year)][month]; } days += tm->tm_mday - 1; return ((((days * 24) + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec); } notmuch-0.17/completion/000077500000000000000000000000001226041107500152655ustar00rootroot00000000000000notmuch-0.17/completion/Makefile000066400000000000000000000002041226041107500167210ustar00rootroot00000000000000# See Makefile.local for the list of files to be compiled in this # directory. all: $(MAKE) -C .. all .DEFAULT: $(MAKE) -C .. $@ notmuch-0.17/completion/Makefile.local000066400000000000000000000012531226041107500200170ustar00rootroot00000000000000# -*- makefile -*- dir := completion # The dir variable will be re-assigned to later, so we can't use it # directly in any shell commands. Instead we save its value in other, # private variables that we can use in the commands. bash_script := $(srcdir)/$(dir)/notmuch-completion.bash zsh_script := $(srcdir)/$(dir)/notmuch-completion.zsh install: install-$(dir) install-$(dir): @echo $@ ifeq ($(WITH_BASH),1) mkdir -p "$(DESTDIR)$(bash_completion_dir)" install -m0644 $(bash_script) "$(DESTDIR)$(bash_completion_dir)/notmuch" endif ifeq ($(WITH_ZSH),1) mkdir -p "$(DESTDIR)$(zsh_completion_dir)" install -m0644 $(zsh_script) "$(DESTDIR)$(zsh_completion_dir)/_notmuch" endif notmuch-0.17/completion/README000066400000000000000000000007721226041107500161530ustar00rootroot00000000000000notmuch completion This directory contains support for various shells to automatically complete partially entered notmuch command lines. notmuch-completion.bash Command-line completion for the bash shell. This depends on bash-completion package [1] version 2.0, which depends on bash version 3.2 or later. [1] http://bash-completion.alioth.debian.org/ notmuch-completion.tcsh Command-line completion for the tcsh shell. notmuch-completion.zsh Command-line completion for the zsh shell. notmuch-0.17/completion/notmuch-completion.bash000066400000000000000000000220001226041107500217420ustar00rootroot00000000000000# bash completion for notmuch -*- shell-script -*- # # Copyright © 2013 Jani Nikula # # Based on the bash-completion package: # http://bash-completion.alioth.debian.org/ # # 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 2 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 http://www.gnu.org/licenses/ . # # Author: Jani Nikula # # # BUGS: # # Add space after an --option without parameter (e.g. reply --decrypt) # on completion. # _notmuch_user_emails() { notmuch config get user.primary_email notmuch config get user.other_email } _notmuch_search_terms() { local cur prev words cword split # handle search prefixes and tags with colons and equal signs _init_completion -n := || return case "${cur}" in tag:*) COMPREPLY=( $(compgen -P "tag:" -W "`notmuch search --output=tags \*`" -- ${cur##tag:}) ) ;; to:*) COMPREPLY=( $(compgen -P "to:" -W "`_notmuch_user_emails`" -- ${cur##to:}) ) ;; from:*) COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_user_emails`" -- ${cur##from:}) ) ;; *) local search_terms="from: to: subject: attachment: tag: id: thread: folder: date:" compopt -o nospace COMPREPLY=( $(compgen -W "${search_terms}" -- ${cur}) ) ;; esac # handle search prefixes and tags with colons __ltrim_colon_completions "${cur}" } _notmuch_compact() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --backup) _filedir return ;; esac ! $split && case "${cur}" in -*) local options="--backup= --quiet" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; esac } _notmuch_config() { local cur prev words cword split _init_completion || return case "${prev}" in config) COMPREPLY=( $(compgen -W "get set list" -- ${cur}) ) ;; get|set) COMPREPLY=( $(compgen -W "`notmuch config list | sed 's/=.*\$//'`" -- ${cur}) ) ;; # these will also complete on config get, but we don't care database.path) _filedir ;; maildir.synchronize_flags) COMPREPLY=( $(compgen -W "true false" -- ${cur}) ) ;; esac } _notmuch_count() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --output) COMPREPLY=( $( compgen -W "messages threads files" -- "${cur}" ) ) return ;; --exclude) COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) ) return ;; --input) _filedir return ;; esac ! $split && case "${cur}" in -*) local options="--output= --exclude= --batch --input=" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; *) _notmuch_search_terms ;; esac } _notmuch_dump() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --format) COMPREPLY=( $( compgen -W "sup batch-tag" -- "${cur}" ) ) return ;; --output) _filedir return ;; esac ! $split && case "${cur}" in -*) local options="--format= --output=" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; *) _notmuch_search_terms ;; esac } _notmuch_insert() { local cur prev words cword split # handle tags with colons and equal signs _init_completion -s -n := || return $split && case "${prev}" in --folder) _filedir return ;; esac ! $split && case "${cur}" in --*) local options="--create-folder --folder=" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) return ;; +*) COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) ) ;; -*) COMPREPLY=( $(compgen -P "-" -W "`notmuch search --output=tags \*`" -- ${cur##-}) ) ;; esac # handle tags with colons __ltrim_colon_completions "${cur}" } _notmuch_new() { local cur prev words cword split _init_completion || return case "${cur}" in -*) local options="--no-hooks" COMPREPLY=( $(compgen -W "${options}" -- ${cur}) ) ;; esac } _notmuch_reply() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --format) COMPREPLY=( $( compgen -W "default json sexp headers-only" -- "${cur}" ) ) return ;; --reply-to) COMPREPLY=( $( compgen -W "all sender" -- "${cur}" ) ) return ;; esac ! $split && case "${cur}" in -*) local options="--format= --format-version= --reply-to= --decrypt" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; *) _notmuch_search_terms ;; esac } _notmuch_restore() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --format) COMPREPLY=( $( compgen -W "sup batch-tag auto" -- "${cur}" ) ) return ;; --input) _filedir return ;; esac ! $split && case "${cur}" in -*) local options="--format= --accumulate --input=" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; esac } _notmuch_search() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --format) COMPREPLY=( $( compgen -W "json sexp text text0" -- "${cur}" ) ) return ;; --output) COMPREPLY=( $( compgen -W "summary threads messages files tags" -- "${cur}" ) ) return ;; --sort) COMPREPLY=( $( compgen -W "newest-first oldest-first" -- "${cur}" ) ) return ;; --exclude) COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) ) return ;; esac ! $split && case "${cur}" in -*) local options="--format= --output= --sort= --offset= --limit= --exclude= --duplicate=" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; *) _notmuch_search_terms ;; esac } _notmuch_show() { local cur prev words cword split _init_completion -s || return $split && case "${prev}" in --entire-thread) COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) ) return ;; --format) COMPREPLY=( $( compgen -W "text json sexp mbox raw" -- "${cur}" ) ) return ;; --exclude|--body) COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) ) return ;; esac ! $split && case "${cur}" in -*) local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt --include-html" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) ;; *) _notmuch_search_terms ;; esac } _notmuch_tag() { local cur prev words cword split # handle tags with colons and equal signs _init_completion -s -n := || return $split && case "${prev}" in --input) _filedir return ;; esac ! $split && case "${cur}" in --*) local options="--batch --input= --remove-all" compopt -o nospace COMPREPLY=( $(compgen -W "$options" -- ${cur}) ) return ;; +*) COMPREPLY=( $(compgen -P "+" -W "`notmuch search --output=tags \*`" -- ${cur##+}) ) ;; -*) COMPREPLY=( $(compgen -P "-" -W "`notmuch search --output=tags \*`" -- ${cur##-}) ) ;; *) _notmuch_search_terms return ;; esac # handle tags with colons __ltrim_colon_completions "${cur}" } _notmuch() { local _notmuch_commands="compact config count dump help insert new reply restore search setup show tag" local arg cur prev words cword split _init_completion || return COMPREPLY=() # subcommand _get_first_arg # complete --help option like the subcommand if [ -z "${arg}" -a "${prev}" = "--help" ]; then arg="help" fi if [ -z "${arg}" ]; then # top level completion local top_options="--help --version" case "${cur}" in -*) COMPREPLY=( $(compgen -W "${top_options}" -- ${cur}) ) ;; *) COMPREPLY=( $(compgen -W "${_notmuch_commands}" -- ${cur}) ) ;; esac elif [ "${arg}" = "help" ]; then # handle help command specially due to _notmuch_commands usage local help_topics="$_notmuch_commands hooks search-terms" COMPREPLY=( $(compgen -W "${help_topics}" -- ${cur}) ) else # complete using _notmuch_subcommand if one exist local completion_func="_notmuch_${arg//-/_}" declare -f $completion_func >/dev/null && $completion_func fi } && complete -F _notmuch notmuch notmuch-0.17/completion/notmuch-completion.tcsh000066400000000000000000000001511226041107500217710ustar00rootroot00000000000000set NOTMUCH_CMD=`notmuch help | awk '/\t/' | cut -f2 |grep -v '^$'` complete notmuch 'p/1/$NOTMUCH_CMD/' notmuch-0.17/completion/notmuch-completion.zsh000066400000000000000000000034141226041107500216410ustar00rootroot00000000000000#compdef notmuch # ZSH completion for `notmuch` # Copyright © 2009 Ingmar Vanhassel _notmuch_commands() { local -a notmuch_commands notmuch_commands=( 'setup:interactively set up notmuch for first use' 'new:find and import any new message to the database' 'search:search for messages matching the search terms, display matching threads as results' 'reply:constructs a reply template for a set of messages' 'show:show all messages matching the search terms' 'tag:add or remove tags for all messages matching the search terms' 'dump:creates a plain-text dump of the tags of each message' 'restore:restores the tags from the given file' 'help:show details on a command' ) _describe -t command 'command' notmuch_commands } _notmuch_dump() { _files } _notmuch_help_topics() { local -a notmuch_help_topics notmuch_help_topics=( 'search-terms:show common search-terms syntax' ) _describe -t notmuch-help-topics 'topic' notmuch_help_topics } _notmuch_help() { _alternative \ _notmuch_commands \ _notmuch_help_topics } _notmuch_restore() { _files } _notmuch_search() { _arguments -s : \ '--max-threads=[display only the first x threads from the search results]:number of threads to show: ' \ '--first=[omit the first x threads from the search results]:number of threads to omit: ' \ '--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' } _notmuch() { if (( CURRENT > 2 )) ; then local cmd=${words[2]} curcontext="${curcontext%:*:*}:notmuch-$cmd" (( CURRENT-- )) shift words _call_function ret _notmuch_$cmd return ret else _notmuch_commands fi } _notmuch "$@" # vim: set sw=2 sts=2 ts=2 et ft=zsh : notmuch-0.17/configure000077500000000000000000000632151226041107500150320ustar00rootroot00000000000000#! /bin/sh # Test whether this shell is capable of parameter substring processing. ( option='a/b'; : ${option#*/} ) 2>/dev/null || { echo " The shell interpreting '$0' is lacking some required features. To work around this problem you may try to execute: ksh $0 $* or bash $0 $* " exit 1 } # Store original IFS value so it can be changed (and restored) in many places. readonly DEFAULT_IFS="$IFS" srcdir=$(dirname "$0") # For a non-srcdir configure invocation (such as ../configure), create # the directory structure and copy Makefiles. if [ "$srcdir" != "." ]; then for dir in . $(grep "^subdirs *=" "$srcdir"/Makefile | sed -e "s/subdirs *= *//"); do mkdir -p "$dir" cp "$srcdir"/"$dir"/Makefile.local "$dir" cp "$srcdir"/"$dir"/Makefile "$dir" done # Easiest way to get the test suite to work is to just copy the # whole thing into the build directory. cp -a "$srcdir"/test/* test # Emacs only likes to generate compiled files next to the .el files # by default so copy these as well (which is not ideal0. cp -a "$srcdir"/emacs/*.el emacs fi # Set several defaults (optionally specified by the user in # environment variables) CC=${CC:-gcc} CXX=${CXX:-g++} CFLAGS=${CFLAGS:--O2} CPPFLAGS=${CPPFLAGS:-} CXXFLAGS=${CXXFLAGS:-\$(CFLAGS)} LDFLAGS=${LDFLAGS:-} XAPIAN_CONFIG=${XAPIAN_CONFIG:-xapian-config} # We don't allow the EMACS or GZIP Makefile variables inherit values # from the environment as we do with CC and CXX above. The reason is # that these names as environment variables have existing uses other # than the program name that we want. (EMACS is set to 't' when a # shell is running within emacs and GZIP specifies arguments to pass # on the gzip command line). # Set the defaults for values the user can specify with command-line # options. PREFIX=/usr/local LIBDIR= WITH_EMACS=1 WITH_BASH=1 WITH_ZSH=1 # Compatible GMime versions (with constraints). # If using GMime 2.6, we need to have a version >= 2.6.5 to avoid a # crypto bug. We need 2.6.7 for permissive "From " header handling. GMIME_24_VERSION_CTR='' GMIME_24_VERSION="gmime-2.4 $GMIME_24_VERSION_CTR" GMIME_26_VERSION_CTR='>= 2.6.7' GMIME_26_VERSION="gmime-2.6 $GMIME_26_VERSION_CTR" WITH_GMIME_VERSIONS="$GMIME_26_VERSION;$GMIME_24_VERSION" usage () { cat <-- Currently ignored --host=-- Currently ignored --infodir=DIR Currently ignored --datadir=DIR Currently ignored --localstatedir=DIR Currently ignored --libexecdir=DIR Currently ignored --disable-maintainer-mode Currently ignored --disable-dependency-tracking Currently ignored EOF } # Parse command-line options for option; do if [ "${option}" = '--help' ] ; then usage exit 0 elif [ "${option%%=*}" = '--prefix' ] ; then PREFIX="${option#*=}" elif [ "${option%%=*}" = '--libdir' ] ; then LIBDIR="${option#*=}" elif [ "${option%%=*}" = '--includedir' ] ; then INCLUDEDIR="${option#*=}" elif [ "${option%%=*}" = '--mandir' ] ; then MANDIR="${option#*=}" elif [ "${option%%=*}" = '--sysconfdir' ] ; then SYSCONFDIR="${option#*=}" elif [ "${option%%=*}" = '--emacslispdir' ] ; then EMACSLISPDIR="${option#*=}" elif [ "${option%%=*}" = '--emacsetcdir' ] ; then EMACSETCDIR="${option#*=}" elif [ "${option%%=*}" = '--bashcompletiondir' ] ; then BASHCOMPLETIONDIR="${option#*=}" elif [ "${option%%=*}" = '--zshcompletiondir' ] ; then ZSHCOMLETIONDIR="${option#*=}" elif [ "${option%%=*}" = '--with-emacs' ]; then if [ "${option#*=}" = 'no' ]; then WITH_EMACS=0 else WITH_EMACS=1 fi elif [ "${option}" = '--without-emacs' ] ; then WITH_EMACS=0 elif [ "${option%%=*}" = '--with-bash-completion' ]; then if [ "${option#*=}" = 'no' ]; then WITH_BASH=0 else WITH_BASH=1 fi elif [ "${option}" = '--without-bash-completion' ] ; then WITH_BASH=0 elif [ "${option%%=*}" = '--with-zsh-completion' ]; then if [ "${option#*=}" = 'no' ]; then WITH_ZSH=0 else WITH_ZSH=1 fi elif [ "${option}" = '--without-zsh-completion' ] ; then WITH_ZSH=0 elif [ "${option%%=*}" = '--with-gmime-version' ] ; then if [ "${option#*=}" = '2.4' ]; then WITH_GMIME_VERSIONS=$GMIME_24_VERSION elif [ "${option#*=}" = '2.6' ]; then WITH_GMIME_VERSIONS=$GMIME_26_VERSION fi elif [ "${option%%=*}" = '--build' ] ; then true elif [ "${option%%=*}" = '--host' ] ; then true elif [ "${option%%=*}" = '--infodir' ] ; then true elif [ "${option%%=*}" = '--datadir' ] ; then true elif [ "${option%%=*}" = '--localstatedir' ] ; then true elif [ "${option%%=*}" = '--libexecdir' ] ; then true elif [ "${option}" = '--disable-maintainer-mode' ] ; then true elif [ "${option}" = '--disable-dependency-tracking' ] ; then true else echo "Unrecognized option: ${option}" echo "See:" echo " $0 --help" echo "" exit 1 fi done # We set this value early, (rather than just while printing the # Makefile.config file later like most values), because we need to # actually investigate this value compared to the ldconfig_paths value # below. if [ -z "$LIBDIR" ] ; then libdir_expanded="${PREFIX}/lib" else # very non-general variable expansion libdir_expanded=`echo "$LIBDIR" | sed "s|\\${prefix}|${PREFIX}|g; s|\\$prefix/|${PREFIX}/|; s|//*|/|g"` fi cat < /dev/null 2>&1; then have_pkg_config=1 else have_pkg_config=0 fi printf "Checking for Xapian development files... " have_xapian=0 for xapian_config in ${XAPIAN_CONFIG}; do if ${xapian_config} --version > /dev/null 2>&1; then xapian_version=$(${xapian_config} --version | sed -e 's/.* //') printf "Yes (%s).\n" ${xapian_version} have_xapian=1 xapian_cxxflags=$(${xapian_config} --cxxflags) xapian_ldflags=$(${xapian_config} --libs) break fi done if [ ${have_xapian} = "0" ]; then printf "No.\n" errors=$((errors + 1)) fi # Compaction is only supported on Xapian > 1.2.6 have_xapian_compact=0 if [ ${have_xapian} = "1" ]; then printf "Checking for Xapian compaction support... " case "${xapian_version}" in 0.*|1.[01].*|1.2.[0-5]) printf "No (only available with Xapian > 1.2.6).\n" ;; [1-9]*.[0-9]*.[0-9]*) have_xapian_compact=1 printf "Yes.\n" ;; *) printf "Unknown version.\n" ;; esac fi printf "Checking for GMime development files... " have_gmime=0 IFS=';' for gmimepc in $WITH_GMIME_VERSIONS; do if pkg-config --exists $gmimepc; then printf "Yes ($gmimepc).\n" have_gmime=1 gmime_cflags=$(pkg-config --cflags $gmimepc) gmime_ldflags=$(pkg-config --libs $gmimepc) break fi done IFS=$DEFAULT_IFS if [ "$have_gmime" = "0" ]; then printf "No.\n" errors=$((errors + 1)) fi # GMime already depends on Glib >= 2.12, but we use at least one Glib # function that only exists as of 2.22, (g_array_unref) printf "Checking for Glib development files (>= 2.22)... " have_glib=0 if pkg-config --exists 'glib-2.0 >= 2.22'; then printf "Yes.\n" have_glib=1 glib_cflags=$(pkg-config --cflags glib-2.0) glib_ldflags=$(pkg-config --libs glib-2.0) else printf "No.\n" errors=$((errors + 1)) fi printf "Checking for talloc development files... " if pkg-config --exists talloc; then printf "Yes.\n" have_talloc=1 talloc_cflags=$(pkg-config --cflags talloc) talloc_ldflags=$(pkg-config --libs talloc) else printf "No.\n" have_talloc=0 talloc_cflags= errors=$((errors + 1)) fi printf "Checking for valgrind development files... " if pkg-config --exists valgrind; then printf "Yes.\n" have_valgrind=1 valgrind_cflags=$(pkg-config --cflags valgrind) else printf "No (but that's fine).\n" have_valgrind=0 fi if [ -z "${EMACSLISPDIR}" ]; then if pkg-config --exists emacs; then EMACSLISPDIR=$(pkg-config emacs --variable sitepkglispdir) else EMACSLISPDIR='$(prefix)/share/emacs/site-lisp' fi fi if [ -z "${EMACSETCDIR}" ]; then if pkg-config --exists emacs; then EMACSETCDIR=$(pkg-config emacs --variable sitepkglispdir) else EMACSETCDIR='$(prefix)/share/emacs/site-lisp' fi fi printf "Checking if emacs is available... " if emacs --quick --batch > /dev/null 2>&1; then printf "Yes.\n" have_emacs=1 else printf "No (so will not byte-compile emacs code)\n" have_emacs=0 fi libdir_in_ldconfig=0 printf "Checking which platform we are on... " uname=`uname` if [ $uname = "Darwin" ] ; then printf "Mac OS X.\n" platform=MACOSX linker_resolves_library_dependencies=0 elif [ $uname = "SunOS" ] ; then printf "Solaris.\n" platform=SOLARIS linker_resolves_library_dependencies=0 elif [ $uname = "FreeBSD" ] ; then printf "FreeBSD.\n" platform=FREEBSD linker_resolves_library_dependencies=0 elif [ $uname = "OpenBSD" ] ; then printf "OpenBSD.\n" platform=OPENBSD linker_resolves_library_dependencies=0 elif [ $uname = "Linux" ] || [ $uname = "GNU" ] ; then printf "$uname\n" platform="$uname" linker_resolves_library_dependencies=1 printf "Checking for $libdir_expanded in ldconfig... " ldconfig_paths=$(/sbin/ldconfig -N -X -v 2>/dev/null | sed -n -e 's,^\(/.*\):\( (.*)\)\?$,\1,p') # Separate ldconfig_paths only on newline (not on any potential # embedded space characters in any filenames). Note, we use a # literal newline in the source here rather than something like: # # IFS=$(printf '\n') # # because the shell's command substitution deletes any trailing newlines. IFS=" " for path in $ldconfig_paths; do if [ "$path" = "$libdir_expanded" ]; then libdir_in_ldconfig=1 fi done IFS=$DEFAULT_IFS if [ "$libdir_in_ldconfig" = '0' ]; then printf "No (will set RPATH)\n" else printf "Yes\n" fi else printf "Unknown.\n" cat < _byteorder.c < #include uint32_t test = 0x34333231; int main() { printf("%.4s\n", (const char*)&test); return 0; } EOF ${CC} ${CFLAGS} _byteorder.c -o _byteorder > /dev/null 2>&1 util_byte_order=$(./_byteorder) echo $util_byte_order rm -f _byteorder _byteorder.c if [ $errors -gt 0 ]; then cat <= 2.22 (including development files such as headers)" echo " http://ftp.gnome.org/pub/gnome/sources/glib/" echo fi if [ $have_talloc -eq 0 ]; then echo " The talloc library (including development files such as headers)" echo " http://talloc.samba.org/" echo fi cat < /dev/null 2>&1 then printf "Yes.\n" have_getline=1 else printf "No (will use our own instead).\n" have_getline=0 fi rm -f compat/have_getline printf "Checking for strcasestr... " if ${CC} -o compat/have_strcasestr "$srcdir"/compat/have_strcasestr.c > /dev/null 2>&1 then printf "Yes.\n" have_strcasestr=1 else printf "No (will use our own instead).\n" have_strcasestr=0 fi rm -f compat/have_strcasestr printf "Checking for strsep... " if ${CC} -o compat/have_strsep "$srcdir"/compat/have_strsep.c > /dev/null 2>&1 then printf "Yes.\n" have_strsep="1" else printf "No (will use our own instead).\n" have_strsep="0" fi rm -f compat/have_strsep printf "Checking for timegm... " if ${CC} -o compat/have_timegm "$srcdir"/compat/have_timegm.c > /dev/null 2>&1 then printf "Yes.\n" have_timegm="1" else printf "No (will use our own instead).\n" have_timegm="0" fi rm -f compat/have_timegm printf "Checking for standard version of getpwuid_r... " if ${CC} -o compat/check_getpwuid "$srcdir"/compat/check_getpwuid.c > /dev/null 2>&1 then printf "Yes.\n" std_getpwuid=1 else printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n" std_getpwuid=0 fi rm -f compat/check_getpwuid printf "Checking for standard version of asctime_r... " if ${CC} -o compat/check_asctime "$srcdir"/compat/check_asctime.c > /dev/null 2>&1 then printf "Yes.\n" std_asctime=1 else printf "No (will define _POSIX_PTHREAD_SEMANTICS to get it).\n" std_asctime=0 fi rm -f compat/check_asctime printf "int main(void){return 0;}\n" > minimal.c printf "Checking for rpath support... " if ${CC} -Wl,--enable-new-dtags -Wl,-rpath,/tmp/ -o minimal minimal.c >/dev/null 2>&1 then printf "Yes.\n" rpath_ldflags="-Wl,--enable-new-dtags -Wl,-rpath,\$(libdir)" else printf "No (nothing to worry about).\n" rpath_ldflags="" fi printf "Checking for -Wl,--as-needed... " if ${CC} -Wl,--as-needed -o minimal minimal.c >/dev/null 2>&1 then printf "Yes.\n" as_needed_ldflags="-Wl,--as-needed" else printf "No (nothing to worry about).\n" as_needed_ldflags="" fi WARN_CXXFLAGS="" printf "Checking for available C++ compiler warning flags... " for flag in -Wall -Wextra -Wwrite-strings; do if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1 then WARN_CXXFLAGS="${WARN_CXXFLAGS}${WARN_CXXFLAGS:+ }${flag}" fi done printf "\n\t${WARN_CXXFLAGS}\n" WARN_CFLAGS="${WARN_CXXFLAGS}" printf "Checking for available C compiler warning flags... " for flag in -Wmissing-declarations; do if ${CC} $flag -o minimal minimal.c > /dev/null 2>&1 then WARN_CFLAGS="${WARN_CFLAGS}${WARN_CFLAGS:+ }${flag}" fi done printf "\n\t${WARN_CFLAGS}\n" rm -f minimal minimal.c cat < Makefile.config < notmuch-0.17/contrib/notmuch-deliver/Makefile.am000066400000000000000000000003671226041107500217230ustar00rootroot00000000000000CLEANFILES= *~ MAINTAINERCLEANFILES= Makefile.in configure aclocal.m4 \ config.h config.h.in INSTALL ACLOCAL_AMFLAGS= -I m4 AUTOMAKE_OPTIONS= dist-bzip2 no-dist-gzip std-options foreign SUBDIRS= maildrop/numlib src . doc_DATA= README.mkd notmuch-0.17/contrib/notmuch-deliver/README.mkd000066400000000000000000000030741226041107500213170ustar00rootroot00000000000000## About `notmuch-deliver` is a [maildir](http://cr.yp.to/proto/maildir.html) delivery tool. ## Overview `notmuch-deliver` is a [maildir](http://cr.yp.to/proto/maildir.html) delivery tool for [notmuch](http://notmuchmail.org) mail indexer. It reads from standard input, delivers the mail to the specified maildir and adds it to the notmuch database. This is meant as a convenient alternative to running `notmuch new` after mail delivery. ## Usage Here's a simple example for [maildrop](http://www.courier-mta.org/maildrop/): # Deliver local mail to $MAILDIR/.Local and add local tag. if (/^From: root/:h) { to "|notmuch-deliver -f -t local Local" } # Deliver lkml mail to $MAILDIR/.Lkml, add lkml tag and remove inbox tag. if (/^List-Id: linux-kernel@vger.kernel.org/:h) { to "|notmuch-deliver -f -t lkml -r inbox Lkml" } # Deliver the rest to $MAILDIR, adding personal tag to "|notmuch-deliver -t personal" ## Requirements - [notmuch](http://notmuchmail.org) shared library - [GLib](http://library.gnome.org/devel/glib/)-2.16 or newer ## Contribute Clone [git://github.com/alip/notmuch-deliver.git](git://github.com/alip/notmuch-deliver.git). Format patches are preferred. Either send a mail to me or poke me on IRC. My personal e-mail address is [alip@exherbo.org](mailto:alip@exherbo.org). I'm available on IRC as `alip` on [Freenode](http://freenode.net) and [OFTC](http://www.oftc.net). ## License You may redistribute this under the same terms as notmuch itself. notmuch-0.17/contrib/notmuch-deliver/autogen.sh000077500000000000000000000010321226041107500216560ustar00rootroot00000000000000#!/bin/sh # vim: set sw=4 et sts=4 tw=80 : die() { echo "$@" >&2 exit 1 } echo ">>> libtoolize --copy --force --automake" libtoolize --copy --force --automake || die "libtoolize failed" echo ">>> rm -f config.cache" rm -f config.cache echo ">>> aclocal -I m4" aclocal -I m4 || die "aclocal failed" echo ">>> autoheader" autoheader || die "autoheader failed" echo ">>> autoconf" autoconf || die "autoconf failed" echo ">>> automake --foreign --add-missing --copy" automake --foreign --add-missing --copy || die "automake failed" notmuch-0.17/contrib/notmuch-deliver/configure.ac000066400000000000000000000105031226041107500221460ustar00rootroot00000000000000dnl vim: set sw=4 sts=4 ts=4 noet ft=config foldmethod=marker foldmarker={{{,}}} : dnl {{{ Program, version AC_PREREQ(2.59) AC_INIT([src/main.c]) AC_CANONICAL_SYSTEM VERSION_MAJOR=0 VERSION_MINOR=1 VERSION_FULL="$VERSION_MAJOR.$VERSION_MINOR" VERSION="$VERSION_FULL" AC_SUBST([VERSION_MAJOR]) AC_SUBST([VERSION_MINOR]) AC_SUBST([VERSION_FULL]) AM_INIT_AUTOMAKE(notmuch-deliver, [$VERSION_FULL]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) dnl {{{ Git revision AC_MSG_CHECKING([for git head]) if test -d "${GIT_DIR:-${ac_top_srcdir:-./}/.git}" ; then GITHEAD=`git describe 2>/dev/null` if test -z ${GITHEAD} ; then GITHEAD=`git rev-parse HEAD` fi if test -n "`git diff-index -m --name-only HEAD`" ; then GITHEAD=${GITHEAD}-dirty fi if test -n "${GITHEAD}" ; then GITHEAD="-${GITHEAD}" fi fi AC_MSG_RESULT([$GITHEAD]) AC_SUBST([GITHEAD]) dnl }}} dnl }}} dnl {{{ Toolchain checks AC_USE_SYSTEM_EXTENSIONS AC_PROG_CC AC_PROG_CC_C99 if test x"$ac_cv_prog_cc_c99" = x"no"; then AC_MSG_ERROR([notmuch-deliver requires a C compiler that supports ISO C99!]) fi AC_PROG_LIBTOOL AC_PROG_LN_S AC_PROG_INSTALL AC_PROG_MAKE_SET dnl }}} dnl {{{ Check for headers AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_TIME AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([sys/stat.h unistd.h sysexits.h utime.h]) dnl }}} dnl {{{ Check for typedefs, structures and compiler characteristics AC_C_CONST AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_TYPE_UID_T AC_TYPE_PID_T AC_STRUCT_TM dnl }}} dnl {{{ Check for library functions AC_CHECK_FUNCS([setgroups initgroups symlink readlink strcasecmp utime utimes splice]) dnl }}} dnl {{{ gethostname() AC_CACHE_CHECK([for missing gethostname prototype], maildir_cv_SYS_GETHOSTNAME, AC_TRY_COMPILE([ #ifdef HAVE_UNISTD_H #include #endif int gethostname(int,int); ],,[maildir_cv_SYS_GETHOSTNAME=yes], [maildir_cv_SYS_GETHOSTNAME=no])) if test x"$maildir_cv_SYS_GETHOSTNAME" = x"no" ; then AC_DEFINE_UNQUOTED(HAS_GETHOSTNAME, 1, [Whether gethostname() is prototyped]) fi dnl }}} dnl {{{ Check for maildir target separator if test "$target_os" = "cygwin" ; then AC_DEFINE_UNQUOTED(MDIRSEP, "!", [Maildir target separator]) else AC_DEFINE_UNQUOTED(MDIRSEP, ":", [Maildir target separator]) fi dnl }}} dnl {{{ Make pkg-config work PKG_PROG_PKG_CONFIG([0.9.0]) dnl }}} dnl {{{ Check for libraries GLIB_REQUIRED=2.16 PKG_CHECK_MODULES([glib], [glib-2.0 >= $GLIB_REQUIRED],, [AC_MSG_ERROR([notmuch-deliver requires glib-$GLIB_REQUIRED or newer])]) AC_CHECK_LIB(notmuch, notmuch_database_create,, [AC_MSG_ERROR([notmuch-deliver requires notmuch mail indexing library])]) dnl }}} dnl {{{ Extra CFLAGS NOTMUCH_DELIVER_CFLAGS= WANTED_CFLAGS="-Wall -W -Wextra -Wvla -Wformat=2 -Wformat-security -Wformat-nonliteral -Winit-self -Wfloat-equal -Wno-deprecated-declarations -Wmissing-declarations -Wmissing-noreturn -Wmissing-prototypes -Wredundant-decls -Wshadow -Wpointer-arith -Wstrict-prototypes -Wcast-qual -Wwrite-strings -pedantic" for flag in $WANTED_CFLAGS ; do AX_CHECK_COMPILER_FLAGS([$flag], [NOTMUCH_DELIVER_CFLAGS="$NOTMUCH_DELIVER_CFLAGS $flag"],) done dnl }}} dnl {{{ Profiling AC_MSG_CHECKING([whether gprof symbols are wanted]) AC_ARG_ENABLE([gprof], [AS_HELP_STRING([--enable-gprof], [Add gprof symbols (-pg) (for debugging)])], PROFILE="$enableval", PROFILE="no") AC_MSG_RESULT([$PROFILE]) if test x"$PROFILE" = x"yes" ; then AX_CHECK_COMPILER_FLAGS([-pg],, AC_MSG_ERROR([Your compiler doesn't support -pg flag])) NOTMUCH_DELIVER_CFLAGS="$NOTMUCH_DELIVER_CFLAGS -pg" LDFLAGS="$LDFLAGS -pg" fi AC_MSG_CHECKING([whether coverage testing should be enabled]) AC_ARG_ENABLE([gcov], [AS_HELP_STRING([--enable-gcov], [add gcov support (for debugging)])], COVERAGE="$enableval", COVERAGE="no") AC_MSG_RESULT([$COVERAGE]) if test x"$COVERAGE" = x"yes" ; then AX_CHECK_COMPILER_FLAGS([-fprofile-arcs],, AC_MSG_ERROR([Your compiler doesn't support -fprofile-arcs flag])) AX_CHECK_COMPILER_FLAGS([-ftest-coverage],, AC_MSG_ERROR([Your compiler doesn't support -ftest-coverage flag])) NOTMUCH_DELIVER_CFLAGS="$NOTMUCH_DELIVER_CFLAGS -fprofile-arcs -ftest-coverage" fi AC_SUBST([NOTMUCH_DELIVER_CFLAGS]) dnl }}} dnl {{{ Output AC_CONFIG_HEADERS([config.h]) AC_CONFIG_LINKS([maildrop/numlib/config.h:config.h]) AC_OUTPUT( Makefile maildrop/numlib/Makefile src/Makefile ) dnl }}} notmuch-0.17/contrib/notmuch-deliver/m4/000077500000000000000000000000001226041107500202015ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-deliver/m4/ax_check_compiler_flags.m4000066400000000000000000000063231226041107500252620ustar00rootroot00000000000000# =========================================================================== # http://www.nongnu.org/autoconf-archive/ax_check_compiler_flags.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILER_FLAGS(FLAGS, [ACTION-SUCCESS], [ACTION-FAILURE]) # # DESCRIPTION # # Check whether the given compiler FLAGS work with the current language's # compiler, or whether they give an error. (Warnings, however, are # ignored.) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # LICENSE # # Copyright (c) 2009 Steven G. Johnson # Copyright (c) 2009 Matteo Frigo # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. AC_DEFUN([AX_CHECK_COMPILER_FLAGS], [AC_PREREQ(2.59) dnl for _AC_LANG_PREFIX AC_MSG_CHECKING([whether _AC_LANG compiler accepts $1]) dnl Some hackery here since AC_CACHE_VAL can't handle a non-literal varname: AS_LITERAL_IF([$1], [AC_CACHE_VAL(AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1]), [ ax_save_FLAGS=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$1" AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=yes, AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=no) _AC_LANG_PREFIX[]FLAGS=$ax_save_FLAGS])], [ax_save_FLAGS=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$1" AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], eval AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=yes, eval AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=no) _AC_LANG_PREFIX[]FLAGS=$ax_save_FLAGS]) eval ax_check_compiler_flags=$AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1]) AC_MSG_RESULT($ax_check_compiler_flags) if test "x$ax_check_compiler_flags" = xyes; then m4_default([$2], :) else m4_default([$3], :) fi ])dnl AX_CHECK_COMPILER_FLAGS notmuch-0.17/contrib/notmuch-deliver/maildrop/000077500000000000000000000000001226041107500214705ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/000077500000000000000000000000001226041107500231115ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/Makefile.am000066400000000000000000000152451226041107500251540ustar00rootroot00000000000000# # Copyright 1998 - 2005 Double Precision, Inc. See COPYING for # distribution information. noinst_LTLIBRARIES=libmaildir.la DOCS= deliverquota.html.in deliverquota.8.in \ maildiracl.1.in \ maildir.html maildir.5 \ maildiracl.html.in \ maildirmake.html.in maildirmake.1.in maildirquota.html maildirquota.7 \ maildirkw.html maildirkw.1 if HAVE_SGML BUILT_SOURCES=maildirsharedrc.h maildirfilterconfig.h quotawarnmsg.h \ mailbot.h autoresponsequota.h $(noinst_DATA) $(DOCS) else BUILT_SOURCES=maildirsharedrc.h maildirfilterconfig.h quotawarnmsg.h \ mailbot.h autoresponsequota.h $(noinst_DATA) endif noinst_DATA=deliverquota.html maildirmake.html deliverquota.8 maildirmake.1 \ maildiracl.html maildiracl.1 maildir.libdeps libmaildir_la_SOURCES=autoresponse.c autoresponse.h \ maildiraclt.c maildiraclt.h \ maildircache.c maildircache.h \ maildircreate.c maildircreate.h \ maildirfilename.c maildirgetnew.c \ maildirfilter.c maildirfilter2.c \ maildirfilter.h maildirfiltertypelist.h\ maildirflags.c maildirmkdir.c \ maildirgetquota.c maildirgetquota.h \ maildirinfo.c maildirinfo.h \ maildirkeywords.c maildirkeywords2.c maildirkeywords3.c \ maildirkeywords4.cpp \ maildirkeywords.h maildirlist.c maildirlock.c \ maildirmake2.c \ maildirnewshared.c maildirnewshared.h maildirnewshared2.c \ maildiropen.c maildirparsequota.c \ maildirpath.c maildirpurgetmp.c maildirmisc.h \ maildirrename.c \ maildirsearch.c maildirsearchC.cpp maildirsearch.h \ maildirshared.c maildirshared2.c maildirdelfolder.c\ maildirquota.c maildirquota.h maildirrequota.c maildirrequota.h \ maildirwatch.c maildirwatch.h loginexec.c loginexec.h noinst_PROGRAMS=deliverquota maildirmake testmaildirfilter maildirkwtest \ maildirkw maildiracl maildiraclttest deliverquota_SOURCES=deliverquota.c deliverquota_DEPENDENCIES=libmaildir.la ../rfc822/librfc822.la \ ../numlib/libnumlib.la deliverquota_LDADD=libmaildir.la ../rfc822/librfc822.la ../numlib/libnumlib.la deliverquota_LDFLAGS=-static maildirmake_SOURCES=maildirmake.c maildirmake_DEPENDENCIES=libmaildir.la ../numlib/libnumlib.la \ ../rfc822/librfc822.la maildirmake_LDADD=libmaildir.la ../numlib/libnumlib.la \ ../rfc822/librfc822.la maildirmake_LDFLAGS=-static testmaildirfilter_SOURCES=maildirfiltertypelist.h testmaildirfilter.c testmaildirfilter_DEPENDENCIES=libmaildir.la ../numlib/libnumlib.la testmaildirfilter_LDADD=libmaildir.la ../numlib/libnumlib.la @LIBPCRE@ testmaildirfilter_LDFLAGS=-static maildirkwtest_SOURCES=maildirkwtest.c maildirkwtest_LDADD=libmaildir.la maildirkwtest_DEPENDENCIES=libmaildir.la maildirkwtest_LDFLAGS=-static maildirkw_SOURCES=maildirkw.c maildirkw_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la \ `cat maildir.libdeps` maildirkw_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ ../numlib/libnumlib.la maildir.libdeps maildirkw_LDFLAGS=-static maildiracl=maildiracl.c maildiracl_LDADD=libmaildir.la ../liblock/liblock.la ../numlib/libnumlib.la \ `cat maildir.libdeps` maildiracl_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ ../numlib/libnumlib.la maildir.libdeps maildiracl_LDFLAGS=-static maildiraclttest_SOURCES=testmaildiraclt.c maildiraclttest_LDADD=libmaildir.la ../liblock/liblock.la \ ../numlib/libnumlib.la `cat maildir.libdeps` maildiraclttest_DEPENDENCIES=libmaildir.la ../liblock/liblock.la \ ../numlib/libnumlib.la maildir.libdeps maildiraclttest_LDFLAGS=-static EXTRA_DIST=README.maildirquota.html README.maildirquota.txt \ README.imapkeywords.html \ README.maildirfilter.html \ README.sharedfolders.html README.sharedfolders.txt \ maildirkwtest.txt \ testsuite testsuite.txt testsuite2 testsuite2.txt quotawarnmsg $(DOCS) HTML2TXT=links -dump -no-numbering README.maildirquota.txt: README.maildirquota.html $(HTML2TXT) README.maildirquota.html >README.maildirquota.txt README.sharedfolders.txt: README.sharedfolders.html $(HTML2TXT) README.sharedfolders.html >README.sharedfolders.txt mailbot.h: config.status echo '#define MAILBOT "@MAILBOT@"' >mailbot.h maildirsharedrc.h: config.status echo '#define MAILDIRSHAREDRC "$(sysconfdir)/maildirshared"' >maildirsharedrc.h maildirfilterconfig.h: config.status echo '#define MAILDIRFILTERCONFIG "$(sysconfdir)/maildirfilterconfig"' >maildirfilterconfig.h autoresponsequota.h: config.status echo '#define AUTORESPONSEQUOTA "$(sysconfdir)/autoresponsesquota"' >autoresponsequota.h quotawarnmsg.h: config.status echo '#define QUOTAWARNMSG "$(sysconfdir)/quotawarnmsg"' >quotawarnmsg.h maildir.libdeps: config.status echo @LIBFAM@ >maildir.libdeps clean-local: rm -rf maildir.libdeps testmd check-am: @SHELL@ $(srcdir)/testsuite 2>&1 | cmp - $(srcdir)/testsuite.txt test "@LIBPCRE@" != "" || exit 0 ; @SHELL@ $(srcdir)/testsuite2 2>&1 | cmp - $(srcdir)/testsuite2.txt LC_ALL=C; export LC_ALL; ./maildirkwtest | cmp -s - $(srcdir)/maildirkwtest.txt LC_ALL=C; export LC_ALL; ./maildiraclttest if HAVE_SGML deliverquota.html.in: deliverquota.sgml ../docbook/sgml2html ../docbook/sgml2html deliverquota.sgml deliverquota.html.in deliverquota.8.in: deliverquota.sgml ../docbook/sgml2html ../docbook/sgml2man deliverquota.sgml deliverquota.8.in mv deliverquota.8 deliverquota.8.in maildirmake.html.in: maildirmake.sgml ../docbook/sgml2html ../docbook/sgml2html maildirmake.sgml maildirmake.html.in maildirmake.1.in: maildirmake.sgml ../docbook/sgml2html ../docbook/sgml2man maildirmake.sgml maildirmake.1.in mv maildirmake.1 maildirmake.1.in maildiracl.html.in: maildiracl.sgml ../docbook/sgml2html ../docbook/sgml2html maildiracl.sgml maildiracl.html.in maildiracl.1.in: maildiracl.sgml ../docbook/sgml2html ../docbook/sgml2man maildiracl.sgml maildiracl.1.in mv maildiracl.1 maildiracl.1.in maildirquota.html: maildirquota.sgml ../docbook/sgml2html ../docbook/sgml2html maildirquota.sgml maildirquota.html maildirquota.7: maildirquota.sgml ../docbook/sgml2man ../docbook/sgml2man maildirquota.sgml maildirquota.7 maildir.html: maildir.sgml ../docbook/sgml2html ../docbook/sgml2html maildir.sgml maildir.html maildir.5: maildir.sgml ../docbook/sgml2man ../docbook/sgml2man maildir.sgml maildir.5 maildirkw.html: maildirkw.sgml ../docbook/sgml2html ../docbook/sgml2html maildirkw.sgml maildirkw.html maildirkw.1: maildirkw.sgml ../docbook/sgml2man ../docbook/sgml2man maildirkw.sgml maildirkw.1 endif deliverquota.html: deliverquota.html.in ./config.status --file=$@ deliverquota.8: deliverquota.8.in ./config.status --file=$@ maildirmake.html: maildirmake.html.in ./config.status --file=$@ maildirmake.1: maildirmake.1.in ./config.status --file=$@ maildiracl.html: maildiracl.html.in ./config.status --file=$@ maildiracl.1: maildiracl.1.in ./config.status --file=$@ # autoresponse.c: autoresponsequota.h DISTCLEANFILES=$(BUILT_SOURCES) notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/configure.in000066400000000000000000000072061226041107500254270ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. dnl dnl Copyright 1998 - 2001 Double Precision, Inc. See COPYING for dnl distribution information. AC_INIT(maildir, 0.11, [courier-maildrop@lists.sourceforge.net]) >confdefs.h # Kill PACKAGE_ macros AC_CONFIG_SRCDIR(maildirquota.c) LPATH="$PATH:/usr/local/bin" AC_CANONICAL_SYSTEM AM_INIT_AUTOMAKE([foreign no-define]) AM_CONFIG_HEADER(config.h) dnl Checks for programs. AC_PROG_AWK AC_USE_SYSTEM_EXTENSIONS AC_PROG_CC AC_PROG_INSTALL AC_PROG_LN_S AC_LIBTOOL_DLOPEN AM_PROG_LIBTOOL AC_PROG_CXX AC_PATH_PROGS(PERL, perl5 perl, perl, $LPATH) if test "$GCC" = "yes" then CFLAGS="$CFLAGS -Wall" fi if test "$GXX" = "yes" then CXXFLAGS="$CXXFLAGS -Wall" fi CFLAGS="$CFLAGS -I${srcdir}/.. -I.." dnl Checks for libraries. dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_TIME AC_CHECK_HEADERS(sys/stat.h sys/wait.h fcntl.h unistd.h sysexits.h utime.h pcre.h pcre/pcre.h) AC_CHECK_HEADER([pcre.h], [LIBPCRE=-lpcre]) AC_CHECK_HEADER([pcre/pcre.h], [LIBPCRE=-lpcre]) AC_SUBST(LIBPCRE) AC_HEADER_SYS_WAIT AC_LANG_CPLUSPLUS AC_CHECK_HEADERS(vector vector.h) AC_LANG_C dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_TYPE_UID_T AC_STRUCT_TM AC_SYS_LARGEFILE dnl Checks for library functions. AC_CHECK_HEADER(fam.h, :, :) AC_CHECK_FUNCS(symlink readlink strcasecmp utime utimes) AC_CHECK_LIB(fam, FAMOpen, [ LIBFAM=-lfam AC_DEFINE_UNQUOTED(HAVE_FAM,1, [ Whether libfam.a is available ]) AC_CHECK_HEADER(fam.h, : , [ AC_MSG_WARN([[The development header files and libraries for fam,]]) AC_MSG_WARN([[the File Alteration Monitor, are not installed.]]) AC_MSG_WARN([[You appear to have the FAM runtime libraries installed,]]) AC_MSG_WARN([[so you need to simply install the additional development]]) AC_MSG_WARN([[package for your operating system.]]) AC_MSG_ERROR([[FAM development libraries not found.]]) ] ) ]) AC_SUBST(LIBFAM) echo "$LIBFAM" >maildir.libdeps AC_CACHE_CHECK([for missing gethostname prototype],maildir_cv_SYS_GETHOSTNAME, AC_TRY_COMPILE([ #if HAVE_UNISTD_H #include #endif extern "C" int gethostname(int,int); ],[ ],maildir_cv_SYS_GETHOSTNAME=yes,maildir_cv_SYS_GETHOSTNAME=no ) ) if test $maildir_cv_SYS_GETHOSTNAME = "no" then AC_DEFINE_UNQUOTED(HAS_GETHOSTNAME,1, [ Whether gethostname() is prototyped ]) fi AC_ARG_WITH(db, [ --with-db=gdbm Use the GDBM library. --with-db=db Use the libdb.a library.], db="$withval", db="") if test "$db" = "no" then db="" fi if test "$db" != "" then AC_DEFINE_UNQUOTED(HAVE_DBOBJ,1, [ Whether the top-level configure script defined dbobj ]) fi AC_ARG_WITH(trashquota, [ --with-trashquota Count deleted messages as part of the quota], trashquota="$withval", trashquota="no") if test "$trashquota" = "yes" then AC_DEFINE_UNQUOTED(TRASHQUOTA,1, [ Whether to count deleted messages towards the maildir quota ]) fi test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' eval "prefix=$prefix" eval "exec_prefix=$exec_prefix" eval "sysconfdir=$sysconfdir" AC_PATH_PROGS(MAILBOT, mailbot, mailbot, $LPATH) if test -d $srcdir/../courier then MAILBOT="$bindir/mailbot" AC_DEFINE_UNQUOTED(HAVE_COURIER,1, [ Whether building the full Courier suite. ]) fi AC_SUBST(MAILBOT) AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../docbook) if test "$target_os" = "cygwin" then AC_DEFINE_UNQUOTED(MDIRSEP, "!", [ Maildir target separator ]) else AC_DEFINE_UNQUOTED(MDIRSEP, ":", [ Maildir target separator ]) fi AC_OUTPUT(Makefile sharedindexinstall sharedindexsplit) notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/maildircreate.c000066400000000000000000000107461226041107500260720ustar00rootroot00000000000000/* ** Copyright 1998 - 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #include "maildircreate.h" #include "maildirmisc.h" #include #if HAVE_SYS_STAT_H #include #endif #if TIME_WITH_SYS_TIME #include #include #else #if HAVE_SYS_TIME_H #include #else #include #endif #endif #if HAVE_SYS_STAT_H #include #endif #if HAVE_UNISTD_H #include #endif #include #include #include #include #include #include "numlib/numlib.h" FILE *maildir_tmpcreate_fp(struct maildir_tmpcreate_info *info) { int fd=maildir_tmpcreate_fd(info); FILE *fp; if (fd < 0) return NULL; fp=fdopen(fd, "w+"); if (fp == NULL) { close(fd); return NULL; } return fp; } static int maildir_tmpcreate_fd_do(struct maildir_tmpcreate_info *info); #define KEEPTRYING (60 * 60) #define SLEEPFOR 3 int maildir_tmpcreate_fd(struct maildir_tmpcreate_info *info) { int i; if (!info->doordie) return (maildir_tmpcreate_fd_do(info)); for (i=0; i= 0 || errno != EAGAIN) return fd; sleep(SLEEPFOR); } return -1; } static int maildir_tmpcreate_fd_do(struct maildir_tmpcreate_info *info) { const char *maildir=info->maildir; const char *uniq=info->uniq; const char *hostname=info->hostname; char hostname_buf[256]; char time_buf[NUMBUFSIZE]; char usec_buf[NUMBUFSIZE]; char pid_buf[NUMBUFSIZE]; char len_buf[NUMBUFSIZE+3]; char dev_buf[NUMBUFSIZE]; char ino_buf[NUMBUFSIZE]; struct timeval tv; struct stat stat_buf; int fd; if (!maildir) maildir="."; if (!uniq) uniq=""; if (!hostname || !*hostname) { hostname_buf[sizeof(hostname_buf)-1]=0; if (gethostname(hostname_buf, sizeof(hostname_buf)-1) < 0) strcpy(hostname_buf, "localhost"); hostname=hostname_buf; } gettimeofday(&tv, NULL); libmail_str_time_t(tv.tv_sec, time_buf); libmail_str_time_t(tv.tv_usec, usec_buf); libmail_str_pid_t(getpid(), pid_buf); len_buf[0]=0; if (info->msgsize > 0) { strcpy(len_buf, ",S="); libmail_str_size_t(info->msgsize, len_buf+3); } if (info->tmpname) free(info->tmpname); info->tmpname=malloc(strlen(maildir)+strlen(uniq)+ strlen(hostname)+strlen(time_buf)+ strlen(usec_buf)+ strlen(pid_buf)+strlen(len_buf)+100); if (!info->tmpname) { maildir_tmpcreate_free(info); return -1; } strcpy(info->tmpname, maildir); strcat(info->tmpname, "/tmp/"); strcat(info->tmpname, time_buf); strcat(info->tmpname, ".M"); strcat(info->tmpname, usec_buf); strcat(info->tmpname, "P"); strcat(info->tmpname, pid_buf); if (*uniq) strcat(strcat(info->tmpname, "_"), uniq); strcat(info->tmpname, "."); strcat(info->tmpname, hostname); strcat(info->tmpname, len_buf); if (stat( info->tmpname, &stat_buf) == 0) { maildir_tmpcreate_free(info); errno=EAGAIN; return -1; } if (errno != ENOENT) { maildir_tmpcreate_free(info); if (errno == EAGAIN) errno=EIO; return -1; } if ((fd=maildir_safeopen_stat(info->tmpname, O_CREAT|O_RDWR|O_TRUNC, info->openmode, &stat_buf)) < 0) { maildir_tmpcreate_free(info); return -1; } libmail_strh_dev_t(stat_buf.st_dev, dev_buf); libmail_strh_ino_t(stat_buf.st_ino, ino_buf); if (info->newname) free(info->newname); info->newname=malloc(strlen(info->tmpname)+strlen(ino_buf)+ strlen(dev_buf)+3); if (!info->newname) { maildir_tmpcreate_free(info); unlink(info->tmpname); close(fd); if (errno == EAGAIN) errno=EIO; return -1; } strcpy(info->newname, maildir); strcat(info->newname, "/new/"); strcat(info->newname, time_buf); strcat(info->newname, ".M"); strcat(info->newname, usec_buf); strcat(info->newname, "P"); strcat(info->newname, pid_buf); strcat(info->newname, "V"); strcat(info->newname, dev_buf); strcat(info->newname, "I"); strcat(info->newname, ino_buf); if (*uniq) strcat(strcat(info->newname, "_"), uniq); strcat(info->newname, "."); strcat(info->newname, hostname); strcat(info->newname, len_buf); return fd; } void maildir_tmpcreate_free(struct maildir_tmpcreate_info *info) { if (info->tmpname) free(info->tmpname); info->tmpname=NULL; if (info->newname) free(info->newname); info->newname=NULL; } int maildir_movetmpnew(const char *tmpname, const char *newname) { if (link(tmpname, newname) == 0) { unlink(tmpname); return 0; } if (errno != EXDEV) return -1; /* AFS? */ return rename(tmpname, newname); } notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/maildircreate.h000066400000000000000000000022311226041107500260650ustar00rootroot00000000000000#ifndef maildircreate_h #define maildircreate_h /* ** Copyright 1998 - 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #ifdef __cplusplus extern "C" { #endif /* Create messages in maildirs */ struct maildir_tmpcreate_info { const char *maildir; unsigned long msgsize; /* If known, 0 otherwise (must use requota later)*/ const char *uniq; /* You need when creating multiple msgs */ const char *hostname; /* If known, NULL otherwise */ int openmode; /* Default open mode */ int doordie; /* Loop until we get it right. */ char *tmpname; /* On exit, filename in tmp */ char *newname; /* On exit, filename in new */ }; #define maildir_tmpcreate_init(i) \ do \ { \ memset( (i), 0, sizeof(*(i))); \ (i)->openmode=0644; \ } while(0) int maildir_tmpcreate_fd(struct maildir_tmpcreate_info *); FILE *maildir_tmpcreate_fp(struct maildir_tmpcreate_info *); void maildir_tmpcreate_free(struct maildir_tmpcreate_info *); /* Move created message from tmp to new */ int maildir_movetmpnew(const char *tmpname, const char *newname); #ifdef __cplusplus } #endif #endif notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/maildirmisc.h000066400000000000000000000125061226041107500255630ustar00rootroot00000000000000#ifndef maildirmisc_h #define maildirmisc_h /* ** Copyright 2000-2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #if HAVE_SYS_STAT_H #include #endif #ifdef __cplusplus extern "C" { #endif /* ** ** Miscellaneous maildir-related code ** */ /* Some special folders */ #define INBOX "INBOX" #define DRAFTS "Drafts" #define SENT "Sent" #define TRASH "Trash" #define SHARED "shared" #define SHAREDSUBDIR "shared-folders" #define NEWSHAREDSP "#shared" #define NEWSHARED "#shared." #define PUBLIC "public" /* SMAP */ int maildir_make(const char *maildir, int perm, int subdirperm, int folder); int maildir_del(const char *maildir); int maildir_del_content(const char *maildir); char *maildir_name2dir(const char *maildir, /* DIR location */ const char *foldername); /* INBOX.name */ char *maildir_location(const char *homedir, const char *maildir); /* ** Homedir is the account's home directory, "maildir" is where the account's ** default Maildir is configured to be (usually "./Maildir"). Combine the ** two to produce an absolute pathname. */ char *maildir_folderdir(const char *, /* maildir */ const char *); /* folder name */ /* Returns the directory corresponding to foldername (foldername is ** checked to make sure that it's a valid name, else we set errno ** to EINVAL, and return (0). */ char *maildir_filename(const char *, /* maildir */ const char *, /* folder */ const char *); /* filename */ /* ** Builds the filename to this message, suitable for opening. ** If the file doesn't appear to be there, search the maildir to ** see if someone changed the flags, and return the current filename. */ int maildir_safeopen(const char *, /* filename */ int, /* mode */ int); /* perm */ /* ** Same arguments as open(). When we're accessing a shared maildir, ** prevent someone from playing cute and dumping a bunch of symlinks ** in there. This function will open the indicate file only if the ** last component is not a symlink. ** This is implemented by opening the file with O_NONBLOCK (to prevent ** a DOS attack of someone pointing the symlink to a pipe, causing ** the open to hang), clearing O_NONBLOCK, then stat-int the file ** descriptor, lstating the filename, and making sure that dev/ino ** match. */ int maildir_semisafeopen(const char *, /* filename */ int, /* mode */ int); /* perm */ /* ** Same thing, except that we allow ONE level of soft link indirection, ** because we're reading from our own maildir, which points to the ** message in the sharable maildir. */ int maildir_safeopen_stat(const char *path, int mode, int perm, struct stat *stat1); /* Sane as maildir_safeopen(), except that we also initialize a ** struct stat, saving an extra syscall to the caller. */ int maildir_mkdir(const char *); /* directory */ /* ** Create maildir including all subdirectories in the path (like mkdir -p) */ void maildir_purgetmp(const char *); /* maildir */ /* purges old stuff out of tmp */ void maildir_purge(const char *, /* directory */ unsigned); /* time_t to purge */ void maildir_getnew(const char *, /* maildir */ const char *, /* folder */ void (*)(const char *, void *), /* Callback function for ** every moved msg. */ void *arg); /* Passthrough callback arg */ /* move messages from new to cur */ int maildir_deletefolder(const char *, /* maildir */ const char *); /* folder */ /* deletes a folder */ void maildir_list(const char *maildir, void (*func)(const char *, void *), void *voidp); void maildir_list_sharable(const char *, /* maildir */ void (*)(const char *, void *), /* callback function */ void *); /* 2nd arg to callback func */ /* list sharable folders */ int maildir_shared_subscribe(const char *, /* maildir */ const char *); /* folder */ /* subscribe to a shared folder */ void maildir_list_shared(const char *, /* maildir */ void (*)(const char *, void *), /* callback function */ void *); /* 2nd arg to the callback func */ /* list subscribed folders */ int maildir_shared_unsubscribe(const char *, /* maildir */ const char *); /* folder */ /* unsubscribe from a shared folder */ char *maildir_shareddir(const char *, /* maildir */ const char *); /* folder */ /* ** Validate and return a path to a shared folder. folderdir must be ** a name of a valid shared folder. */ void maildir_shared_sync(const char *); /* maildir */ /* "sync" the shared folder */ int maildir_sharedisro(const char *); /* maildir */ /* maildir is a shared read-only folder */ int maildir_unlinksharedmsg(const char *); /* filename */ /* Remove a message from a shared folder */ /* Internal function that reads a symlink */ char *maildir_getlink(const char *); /* Determine whether the maildir filename has a certain flag */ int maildir_hasflag(const char *filename, char); #define MAILDIR_DELETED(f) maildir_hasflag((f), 'T') /* ** Hierarchical maildir rename. */ #define MAILDIR_RENAME_FOLDER 1 #define MAILDIR_RENAME_SUBFOLDERS 2 int maildir_rename(const char *maildir, /* Path to the maildir */ const char *oldname, /* .foldername */ const char *newname, /* .foldername */ int flags, /* See above */ void (*callback_func)(const char *old_path, const char *new_path) ); #ifdef __cplusplus } #endif #endif notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/maildirmkdir.c000066400000000000000000000023551226041107500257320ustar00rootroot00000000000000/* ** Copyright 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #if HAVE_UNISTD_H #include #endif #include #include "maildirmisc.h" int maildir_mkdir(const char *dir) { char *buf, *p; size_t l; if (dir == 0 || dir[0] == 0) { errno = EINVAL; return (-1); } l = strlen(dir); if ((buf = malloc(l + sizeof("/tmp"))) == 0) { errno = ENOMEM; return (-1); } strcpy(buf, dir); strcpy(buf+l, "/cur"); /* We do mkdir -p here */ p = buf+1; while ((p = strchr(p, '/')) != 0) { *p = '\0'; if (mkdir(buf, 0700) < 0 && errno != EEXIST) { free(buf); return (-1); } *p++ = '/'; } if (mkdir(buf, 0700) < 0 && errno != EEXIST) { free(buf); return (-1); } strcpy(buf+l, "/new"); if (mkdir(buf, 0700) < 0 && errno != EEXIST) { free(buf); return (-1); } /* * make /tmp last because this is the one we open first - * the existence of this directory implies the whole * Maildir structure is complete */ strcpy(buf+l, "/tmp"); if (mkdir(buf, 0700) < 0 && errno != EEXIST) { free(buf); return (-1); } free(buf); return (0); } notmuch-0.17/contrib/notmuch-deliver/maildrop/maildir/maildiropen.c000066400000000000000000000036701226041107500255660ustar00rootroot00000000000000/* ** Copyright 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #if HAVE_UNISTD_H #include #endif #include #include #include #include #include "maildirmisc.h" char *maildir_getlink(const char *filename) { #if HAVE_READLINK size_t bufsiz; char *buf; bufsiz=0; buf=0; for (;;) { int n; if (buf) free(buf); bufsiz += 256; if ((buf=malloc(bufsiz)) == 0) { perror("malloc"); return (0); } if ((n=readlink(filename, buf, bufsiz)) < 0) { free(buf); return (0); } if (n < bufsiz) { buf[n]=0; break; } } return (buf); #else return (0); #endif } int maildir_semisafeopen(const char *path, int mode, int perm) { #if HAVE_READLINK char *l=maildir_getlink(path); if (l) { int f; if (*l != '/') { char *q=malloc(strlen(path)+strlen(l)+2); char *s; if (!q) { free(l); return (-1); } strcpy(q, path); if ((s=strchr(q, '/')) != 0) s[1]=0; else *q=0; strcat(q, l); free(l); l=q; } f=maildir_safeopen(l, mode, perm); free(l); return (f); } #endif return (maildir_safeopen(path, mode, perm)); } int maildir_safeopen(const char *path, int mode, int perm) { struct stat stat1; return maildir_safeopen_stat(path, mode, perm, &stat1); } int maildir_safeopen_stat(const char *path, int mode, int perm, struct stat *stat1) { struct stat stat2; int fd=open(path, mode #ifdef O_NONBLOCK | O_NONBLOCK #else | O_NDELAY #endif , perm); if (fd < 0) return (fd); if (fcntl(fd, F_SETFL, (mode & O_APPEND)) || fstat(fd, stat1) || lstat(path, &stat2)) { close(fd); return (-1); } if (stat1->st_dev != stat2.st_dev || stat1->st_ino != stat2.st_ino) { close(fd); errno=ENOENT; return (-1); } return (fd); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/000077500000000000000000000000001226041107500227565ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/Makefile.am000066400000000000000000000006311226041107500250120ustar00rootroot00000000000000# # Copyright 1998 - 2004 Double Precision, Inc. See COPYING for # distribution information. CLEANFILES=$(noinst_DATA) noinst_LTLIBRARIES=libnumlib.la libnumlib_la_SOURCES=\ atotimet.c \ atouidt.c \ changeuidgid.c \ numlib.h \ strdevt.c \ strgidt.c \ strhdevt.c \ strhinot.c \ strhpidt.c \ strhtimet.c \ strinot.c \ strofft.c \ strpidt.c \ strsize.c \ strsizet.c \ strtimet.c \ struidt.c notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/atotimet.c000066400000000000000000000003641226041107500247530ustar00rootroot00000000000000/* ** Copyright 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include LIBMAIL_STRIMPL(time_t, libmail_strtotime_t, libmail_atotime_t) notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/atouidt.c000066400000000000000000000004561226041107500246000ustar00rootroot00000000000000/* ** Copyright 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include LIBMAIL_STRIMPL(uid_t, libmail_strtouid_t, libmail_atouid_t) LIBMAIL_STRIMPL(gid_t, libmail_strtogid_t, libmail_atogid_t) notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/changeuidgid.c000066400000000000000000000031121226041107500255320ustar00rootroot00000000000000/* ** Copyright 1998 - 2002 Double Precision, Inc. See COPYING for ** distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #if HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include "numlib.h" void libmail_changegroup(gid_t gid) { if ( setgid(gid)) { perror("setgid"); exit(1); } #if HAVE_SETGROUPS if ( getuid() == 0 && setgroups(1, &gid) ) { perror("setgroups"); exit(1); } #endif } void libmail_changeuidgid(uid_t uid, gid_t gid) { libmail_changegroup(gid); if ( setuid(uid)) { perror("setuid"); exit(1); } } void libmail_changeusername(const char *uname, const gid_t *forcegrp) { struct passwd *pw; uid_t changeuid; gid_t changegid; /* uname might be a pointer returned from a previous called to getpw(), ** and libc has a problem getting it back. */ char *p=malloc(strlen(uname)+1); if (!p) { perror("malloc"); exit(1); } strcpy(p, uname); errno=ENOENT; if ((pw=getpwnam(p)) == 0) { free(p); perror("getpwnam"); exit(1); } free(p); changeuid=pw->pw_uid; if ( !forcegrp ) forcegrp= &pw->pw_gid; changegid= *forcegrp; if ( setgid( changegid )) { perror("setgid"); exit(1); } #if HAVE_INITGROUPS if ( getuid() == 0 && initgroups(pw->pw_name, changegid) ) { perror("initgroups"); exit(1); } #else #if HAVE_SETGROUPS if ( getuid() == 0 && setgroups(1, &changegid) ) { perror("setgroups"); exit(1); } #endif #endif if (setuid(changeuid)) { perror("setuid"); exit(1); } } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/configure.in000066400000000000000000000017031226041107500252700ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. dnl dnl Copyright 1998 - 2010 Double Precision, Inc. See COPYING for dnl distribution information. AC_PREREQ(2.59) AC_INIT(numlib, 0.10, courier-users@lists.sourceforge.net) >confdefs.h # Kill PACKAGE_ macros AC_CONFIG_SRCDIR([atotimet.c]) AM_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE([foreign no-define]) dnl Checks for programs. AC_PROG_AWK AC_PROG_CC AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_LIBTOOL if test "$GCC" = "yes" then CFLAGS="$CFLAGS -Wall" fi dnl Checks for libraries. dnl Checks for header files. AC_CHECK_HEADERS(unistd.h stdint.h) AC_CHECK_TYPE(int64_t, [ : ], [ AC_DEFINE_UNQUOTED(int64_t,long long,[default definition of int64_t]) ]) dnl Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T AC_TYPE_PID_T AC_SYS_LARGEFILE dnl Checks for library functions. AC_CHECK_FUNCS(setgroups initgroups) AC_OUTPUT(Makefile) notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/numlib.h000066400000000000000000000045471226041107500244270ustar00rootroot00000000000000#ifndef numlib_h #define numlib_h /* ** Copyright 1998 - 2010 Double Precision, Inc. ** See COPYING for distribution information. */ #ifdef __cplusplus extern "C" { #endif #if HAVE_CONFIG_H #include "../numlib/config.h" /* VPATH build */ #endif #if HAVE_STDINT_H #include #endif #include #include #define NUMBUFSIZE 60 /* Convert various system types to decimal */ char *libmail_str_time_t(time_t, char *); char *libmail_str_off_t(off_t, char *); char *libmail_str_int64_t(int64_t, char *); char *libmail_str_pid_t(pid_t, char *); char *libmail_str_dev_t(dev_t, char *); char *libmail_str_ino_t(ino_t, char *); char *libmail_str_uid_t(uid_t, char *); char *libmail_str_gid_t(gid_t, char *); char *libmail_str_size_t(size_t, char *); char *libmail_str_sizekb(unsigned long, char *); /* X Kb or X Mb */ /* Convert selected system types to hex */ char *libmail_strh_time_t(time_t, char *); char *libmail_strh_pid_t(pid_t, char *); char *libmail_strh_ino_t(ino_t, char *); char *libmail_strh_dev_t(dev_t, char *); /* And, now let's do the reverse */ time_t libmail_strtotime_t(const char **); time_t libmail_atotime_t(const char *); uid_t libmail_strtouid_t(const char **); uid_t libmail_atouid_t(const char *); gid_t libmail_strtogid_t(const char **); gid_t libmail_atogid_t(const char *); /* Common macros: */ #define LIBMAIL_STRIMPL(type, f1, f2) \ \ type f1(const char **p)\ {\ type n=0;\ while ( **p >= '0' && **p <= '9') n=n*10 + (char)(*(*p)++ - '0');\ return n;\ }\ \ type f2(const char *p)\ {\ return f1(&p);\ } /* ** The following functions are used by root to reset its user and group id ** to the authenticated user's. Various functions are provided to handle ** various situations. */ void libmail_changegroup(gid_t); /* Set the group id only. Also clear any ** auxiliary group ids */ void libmail_changeuidgid(uid_t, gid_t); /* Set both user id and group id. Also clear ** aux group ids */ void libmail_changeusername(const char *, const gid_t *); /* ** Set the userid to the indicate user's. If second argument is ** not null, it points to the groupid to set. If it's null, the ** group id is taken from the passwd file. Auxiliary IDs are set ** to any aux IDs set for the user in the group file. If there are ** no aux group IDs for the user, any AUX ids are cleared. */ #ifdef __cplusplus } #endif #endif notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strdevt.c000066400000000000000000000005611226041107500246170ustar00rootroot00000000000000/* ** Copyright 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_dev_t(dev_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strgidt.c000066400000000000000000000005661226041107500246110ustar00rootroot00000000000000/* ** Copyright 1998 - 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_gid_t(gid_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strhdevt.c000066400000000000000000000007071226041107500247710ustar00rootroot00000000000000/* ** Copyright 1998 - 2003 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include static const char xdigit[]="0123456789ABCDEF"; char *libmail_strh_dev_t(dev_t t, char *arg) { char buf[sizeof(t)*2+1]; char *p=buf+sizeof(buf)-1; unsigned i; *p=0; for (i=0; i static const char xdigit[]="0123456789ABCDEF"; char *libmail_strh_ino_t(ino_t t, char *arg) { char buf[sizeof(t)*2+1]; char *p=buf+sizeof(buf)-1; unsigned i; *p=0; for (i=0; i static const char xdigit[]="0123456789ABCDEF"; char *libmail_strh_pid_t(pid_t t, char *arg) { char buf[sizeof(t)*2+1]; char *p=buf+sizeof(buf)-1; unsigned i; *p=0; for (i=0; i static const char xdigit[]="0123456789ABCDEF"; char *libmail_strh_time_t(time_t t, char *arg) { char buf[sizeof(t)*2+1]; char *p=buf+sizeof(buf)-1; unsigned i; *p=0; for (i=0; i char *libmail_str_ino_t(ino_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strofft.c000066400000000000000000000013331226041107500246110ustar00rootroot00000000000000/* ** Copyright 1998 - 2010 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_off_t(off_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; int isneg=0; if (t < 0) { t= -t; isneg=1; } *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); if (isneg) *--p='-'; return (strcpy(arg, p)); } char *libmail_str_int64_t(int64_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; int isneg=0; if (t < 0) { t= -t; isneg=1; } *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); if (isneg) *--p='-'; return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strpidt.c000066400000000000000000000005661226041107500246220ustar00rootroot00000000000000/* ** Copyright 1998 - 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_pid_t(pid_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strsize.c000066400000000000000000000024601226041107500246270ustar00rootroot00000000000000/* ** Copyright 2001 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include static void cat_n(char *buf, unsigned long n) { char bb[NUMBUFSIZE+1]; char *p=bb+sizeof(bb)-1; *p=0; do { *--p = "0123456789"[n % 10]; n=n/10; } while (n); strcat(buf, p); } char *libmail_str_sizekb(unsigned long n, char *sizebuf) { /* If size is less than 1K bytes, display it as 0.xK */ if (n < 1024) { strcpy(sizebuf, "0."); cat_n(sizebuf, (int)(10 * n / 1024 )); strcat(sizebuf, "K"); } /* If size is less than 1 meg, display is as xK */ else if (n < 1024 * 1024) { *sizebuf=0; cat_n(sizebuf, (unsigned long)(n+512)/1024); strcat(sizebuf, "K"); } /* Otherwise, display in megabytes */ else { unsigned long nm=(double)n / (1024.0 * 1024.0) * 10; *sizebuf=0; cat_n( sizebuf, nm / 10); strcat(sizebuf, "."); cat_n( sizebuf, nm % 10); strcat(sizebuf, "M"); } return (sizebuf); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strsizet.c000066400000000000000000000005701226041107500250130ustar00rootroot00000000000000/* ** Copyright 1998 - 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_size_t(size_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/strtimet.c000066400000000000000000000005701226041107500247770ustar00rootroot00000000000000/* ** Copyright 1998 - 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_time_t(time_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/maildrop/numlib/struidt.c000066400000000000000000000005661226041107500246270ustar00rootroot00000000000000/* ** Copyright 1998 - 2000 Double Precision, Inc. ** See COPYING for distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "numlib.h" #include char *libmail_str_uid_t(uid_t t, char *arg) { char buf[NUMBUFSIZE]; char *p=buf+sizeof(buf)-1; *p=0; do { *--p= '0' + (t % 10); t=t / 10; } while(t); return (strcpy(arg, p)); } notmuch-0.17/contrib/notmuch-deliver/src/000077500000000000000000000000001226041107500204505ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-deliver/src/Makefile.am000066400000000000000000000012201226041107500224770ustar00rootroot00000000000000DEFS+= -DGITHEAD=\"$(GITHEAD)\" AM_CPPFLAGS= \ -I$(top_builddir)/maildrop/numlib \ -I$(top_srcdir)/maildrop \ -I$(top_srcdir)/maildrop/maildir AM_CFLAGS= @NOTMUCH_DELIVER_CFLAGS@ $(glib_CFLAGS) noinst_HEADERS=\ $(top_srcdir)/maildrop/maildir/maildircreate.h \ $(top_srcdir)/maildrop/maildir/maildirmisc.h bin_PROGRAMS=\ notmuch-deliver notmuch_deliver_SOURCES=\ $(top_srcdir)/maildrop/maildir/maildircreate.c \ $(top_srcdir)/maildrop/maildir/maildiropen.c \ $(top_srcdir)/maildrop/maildir/maildirmkdir.c \ main.c notmuch_deliver_LDADD=\ $(top_builddir)/maildrop/numlib/libnumlib.la \ $(glib_LIBS) notmuch-0.17/contrib/notmuch-deliver/src/main.c000066400000000000000000000260731226041107500215500ustar00rootroot00000000000000/* notmuch-deliver - If you make the user a promise... make sure you deliver it! * * Copyright © 2010 Ali Polatel * Based in part upon deliverquota of maildrop which is: * Copyright 1998 - 2009 Double Precision, Inc. * * 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 http://www.gnu.org/licenses/ . * * Author: Ali Polatel */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SPLICE #include #endif #ifdef HAVE_SYSEXITS_H #include #endif #include #include #include "maildircreate.h" #include "maildirmisc.h" #ifndef EX_USAGE #define EX_USAGE 64 #endif #ifndef EX_SOFTWARE #define EX_SOFTWARE 70 #endif #ifndef EX_OSERR #define EX_OSERR 71 #endif #ifndef EX_IOERR #define EX_IOERR 74 #endif #ifndef EX_TEMPFAIL #define EX_TEMPFAIL 75 #endif #ifndef EX_NOPERM #define EX_NOPERM 77 #endif #ifndef EX_CONFIG #define EX_CONFIG 78 #endif static gboolean opt_create, opt_fatal, opt_folder, opt_version; static gboolean opt_verbose = FALSE; static gchar **opt_tags = NULL; static gchar **opt_rtags = NULL; static GOptionEntry options[] = { {"version", 'V', 0, G_OPTION_ARG_NONE, &opt_version, "Display version", NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Be verbose (useful for debugging)", NULL}, {"create", 'c', 0, G_OPTION_ARG_NONE, &opt_create, "Create the maildir if it doesn't exist", NULL}, {"folder", 'f', 0, G_OPTION_ARG_NONE, &opt_folder, "Add a dot before FOLDER, e.g: Local => $MAILDIR/.Local", NULL}, {"tag", 't', 0, G_OPTION_ARG_STRING_ARRAY, &opt_tags, "Add a tag to the message, may be specified multiple times", "TAG"}, {"remove-tag", 'r', 0, G_OPTION_ARG_STRING_ARRAY, &opt_rtags, "Remove a tag from the message, may be specified multiple times", "TAG"}, {"fatal-add", 0, 0, G_OPTION_ARG_NONE, &opt_fatal, "If adding the mail to the database fails, unlink it and return non-zero", NULL}, {NULL, 0, 0, 0, NULL, NULL, NULL}, }; static void about(void) { printf(PACKAGE"-"VERSION GITHEAD "\n"); } static void log_handler(G_GNUC_UNUSED const gchar *domain, GLogLevelFlags level, const gchar *message, G_GNUC_UNUSED gpointer user_data) { g_return_if_fail(message != NULL && message[0] != '\0'); if (!opt_verbose && (level & G_LOG_LEVEL_DEBUG)) return; g_printerr(PACKAGE": %s\n", message); } static gboolean load_keyfile(const gchar *path, gchar **db_path, gchar ***tags) { GKeyFile *fd; GError *error; fd = g_key_file_new(); error = NULL; if (!g_key_file_load_from_file(fd, path, G_KEY_FILE_NONE, &error)) { g_printerr("Failed to parse `%s': %s", path, error->message); g_error_free(error); g_key_file_free(fd); return FALSE; } *db_path = g_key_file_get_string(fd, "database", "path", &error); if (*db_path == NULL) { g_critical("Failed to parse database.path from `%s': %s", path, error->message); g_error_free(error); g_key_file_free(fd); return FALSE; } *tags = g_key_file_get_string_list(fd, "new", "tags", NULL, NULL); g_key_file_free(fd); return TRUE; } #ifdef HAVE_SPLICE static int save_splice(int fdin, int fdout) { int ret, written, pfd[2]; if (pipe(pfd) < 0) { g_critical("Failed to create pipe: %s", g_strerror(errno)); return EX_IOERR; } for (;;) { ret = splice(fdin, NULL, pfd[1], NULL, 4096, 0); if (!ret) break; if (ret < 0) { g_critical("Splicing data from standard input failed: %s", g_strerror(errno)); close(pfd[0]); close(pfd[1]); return EX_IOERR; } do { written = splice(pfd[0], NULL, fdout, NULL, ret, 0); if (!written) { g_critical("Splicing data to temporary file failed: internal error"); close(pfd[0]); close(pfd[1]); return EX_IOERR; } if (written < 0) { g_critical("Splicing data to temporary file failed: %s", g_strerror(errno)); close(pfd[0]); close(pfd[1]); return EX_IOERR; } ret -= written; } while (ret); } close(pfd[0]); close(pfd[1]); return 0; } #endif /* HAVE_SPLICE */ static int save_readwrite(int fdin, int fdout) { int ret, written; char buf[4096], *p; for (;;) { ret = read(fdin, buf, 4096); if (!ret) break; if (ret < 0) { if (errno == EINTR) continue; g_critical("Reading from standard input failed: %s", g_strerror(errno)); return EX_IOERR; } p = buf; do { written = write(fdout, p, ret); if (!written) return EX_IOERR; if (written < 0) { if (errno == EINTR) continue; g_critical("Writing to temporary file failed: %s", g_strerror(errno)); return EX_IOERR; } p += written; ret -= written; } while (ret); } return 0; } static int save_maildir(int fdin, const char *dir, int auto_create, char **path) { int fdout, ret; struct maildir_tmpcreate_info info; maildir_tmpcreate_init(&info); info.openmode = 0666; info.maildir = dir; info.doordie = 1; while ((fdout = maildir_tmpcreate_fd(&info)) < 0) { if (errno == ENOENT && auto_create && maildir_mkdir(dir) == 0) { auto_create = 0; continue; } g_critical("Failed to create temporary file `%s': %s", info.tmpname, g_strerror(errno)); return EX_TEMPFAIL; } g_debug("Reading from standard input and writing to `%s'", info.tmpname); #ifdef HAVE_SPLICE ret = g_getenv("NOTMUCH_DELIVER_NO_SPLICE") ? save_readwrite(fdin, fdout) : save_splice(fdin, fdout); #else ret = save_readwrite(fdin, fdout); #endif /* HAVE_SPLICE */ if (ret) goto fail; close(fdout); g_debug("Moving `%s' to `%s'", info.tmpname, info.newname); if (maildir_movetmpnew(info.tmpname, info.newname)) { g_critical("Moving `%s' to `%s' failed: %s", info.tmpname, info.newname, g_strerror(errno)); unlink(info.tmpname); return EX_IOERR; } if (path) *path = g_strdup(info.newname); maildir_tmpcreate_free(&info); return 0; fail: g_debug("Unlinking `%s'", info.tmpname); unlink(info.tmpname); return EX_IOERR; } static int add_tags(notmuch_message_t *message, char **tags) { unsigned i; notmuch_status_t ret; if (!tags) return 0; for (i = 0; tags[i]; i++) { ret = notmuch_message_add_tag(message, tags[i]); if (ret != NOTMUCH_STATUS_SUCCESS) g_warning("Failed to add tag `%s': %s", tags[i], notmuch_status_to_string(ret)); } return i; } static int rm_tags(notmuch_message_t *message, char **tags) { unsigned i; notmuch_status_t ret; if (!tags) return 0; for (i = 0; tags[i]; i++) { ret = notmuch_message_remove_tag(message, tags[i]); if (ret != NOTMUCH_STATUS_SUCCESS) g_warning("Failed to remove tag `%s': %s", tags[i], notmuch_status_to_string(ret)); } return i; } static int save_database(notmuch_database_t *db, const char *path, char **default_tags) { notmuch_status_t ret; notmuch_message_t *message; g_debug("Adding `%s' to notmuch database", path); ret = notmuch_database_add_message(db, path, &message); switch (ret) { case NOTMUCH_STATUS_SUCCESS: break; case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: g_debug("Message is a duplicate, not adding tags"); return 0; default: g_warning("Failed to add `%s' to notmuch database: %s", path, notmuch_status_to_string(ret)); return EX_SOFTWARE; } g_debug("Message isn't a duplicate, adding tags"); add_tags(message, default_tags); add_tags(message, opt_tags); rm_tags(message, opt_rtags); return 0; } int main(int argc, char **argv) { int ret; gchar *conf_path, *db_path, *folder, *maildir, *mail; gchar **conf_tags; GOptionContext *ctx; GError *error = NULL; notmuch_database_t *db; notmuch_status_t status; ctx = g_option_context_new("[FOLDER]"); g_option_context_add_main_entries(ctx, options, PACKAGE); g_option_context_set_summary(ctx, PACKAGE"-"VERSION GITHEAD" - notmuch delivery tool"); g_option_context_set_description(ctx, "\nConfiguration:\n" " "PACKAGE" uses notmuch's configuration file to determine database path and\n" " initial tags to add to new messages. You may set NOTMUCH_CONFIG environment\n" " variable to specify an alternative configuration file.\n" "\nEnvironment:\n" " NOTMUCH_CONFIG: Path to notmuch configuration file\n" " NOTMUCH_DELIVER_NO_SPLICE: Don't use splice() even if it's available\n" "\nExit codes:\n" " 0 => Successful run\n" " 64 => Usage error\n" " 70 => Failed to open the database\n" " (or to add to the database if --fatal-add is specified)\n" " 71 => Input output errors\n" " (failed to read from standard input)\n" " (failed to write to temporary file)\n" " 76 => Failed to open/create maildir\n" " 78 => Configuration error (wrt .notmuch-config)\n"); g_log_set_default_handler(log_handler, NULL); if (!g_option_context_parse(ctx, &argc, &argv, &error)) { g_critical("Option parsing failed: %s", error->message); g_option_context_free(ctx); g_error_free(error); return EX_USAGE; } g_option_context_free(ctx); if (opt_version) { about(); return 0; } if (g_getenv("NOTMUCH_CONFIG")) conf_path = g_strdup(g_getenv("NOTMUCH_CONFIG")); else if (g_getenv("HOME")) conf_path = g_build_filename(g_getenv("HOME"), ".notmuch-config", NULL); else { g_critical("Neither NOTMUCH_CONFIG nor HOME set"); return EX_USAGE; } db_path = NULL; conf_tags = NULL; g_debug("Parsing configuration from `%s'", conf_path); if (!load_keyfile(conf_path, &db_path, &conf_tags)) { g_free(conf_path); return EX_CONFIG; } g_free(conf_path); if ((argc - 1) > 1) { g_critical("Won't deliver to %d folders", argc - 1); return EX_USAGE; } if (argc > 1) { folder = g_strdup_printf("%s%s", opt_folder ? "." : "", argv[1]); maildir = g_build_filename(db_path, folder, NULL); g_free(folder); } else maildir = g_strdup(db_path); g_debug("Opening notmuch database `%s'", db_path); status = notmuch_database_open(db_path, NOTMUCH_DATABASE_MODE_READ_WRITE, &db); if (status) { g_critical("Failed to open database `%s': %s", db_path, notmuch_status_to_string(status)); g_free(maildir); return EX_SOFTWARE; } g_free(db_path); if (db == NULL) return EX_SOFTWARE; if (notmuch_database_needs_upgrade(db)) { g_message("Upgrading database"); notmuch_database_upgrade(db, NULL, NULL); } g_debug("Opening maildir `%s'", maildir); if ((ret = save_maildir(STDIN_FILENO, maildir, opt_create, &mail)) != 0) { g_free(maildir); return ret; } g_free(maildir); if ((ret = save_database(db, mail, conf_tags)) != 0 && opt_fatal) { g_warning("Unlinking `%s'", mail); unlink(mail); return ret; } g_strfreev(conf_tags); g_strfreev(opt_tags); g_strfreev(opt_rtags); g_free(mail); notmuch_database_destroy(db); return 0; } notmuch-0.17/contrib/notmuch-mutt/000077500000000000000000000000001226041107500172205ustar00rootroot00000000000000notmuch-0.17/contrib/notmuch-mutt/.gitignore000066400000000000000000000000331226041107500212040ustar00rootroot00000000000000notmuch-mutt.1 README.html notmuch-0.17/contrib/notmuch-mutt/Makefile000066400000000000000000000002421226041107500206560ustar00rootroot00000000000000NAME = notmuch-mutt all: $(NAME) $(NAME).1 $(NAME).1: $(NAME) pod2man $< > $@ README.html: README markdown $< > $@ clean: rm -f notmuch-mutt.1 README.html notmuch-0.17/contrib/notmuch-mutt/README000066400000000000000000000042531226041107500201040ustar00rootroot00000000000000notmuch-mutt: Notmuch (of a) helper for Mutt ============================================ notmuch-mutt provide integration among the [Mutt] [1] mail user agent and the [Notmuch] [2] mail indexer. notmuch-mutt offer two main integration features. The first one is the ability of stating a **search query interactively** and then jump to a fresh Maildir containing its search results only. The second one is the ability to **reconstruct threads on the fly** starting from the currently highlighted mail, which comes handy when a thread has been split across different maildirs, archived, or the like. notmuch-mutt enables to trigger mail searches via a Mutt macro (usually F8) and reconstruct threads via another (usually F9). Check the manpage for the 2-liner configuration snippet for your Mutt configuration files (~/.muttrc, /etc/Muttrc, or a /etc/Muttrc.d snippet). A [blog style introduction] [3] to notmuch-mutt is available and includes some more rationale for its existence. Arguably, some of the logics of notmuch-mutt could disappear by adding support for a --output=symlinks flag to notmuch. [1]: http://www.mutt.org/ [2]: http://notmuchmail.org/ [3]: http://upsilon.cc/~zack/blog/posts/2011/01/how_to_use_Notmuch_with_Mutt/ Requirements ------------ To *run* notmuch-mutt you will need Perl with the following libraries: - Mail::Box (Debian package: libmail-box-perl) - Mail::Internet (Debian package: libmailtools-perl) - String::ShellQuote (Debian package: libstring-shellquote-perl) - Term::ReadLine (Debian package: libterm-readline-gnu-perl) To *build* notmuch-mutt documentation you will need: - pod2man (coming with Perl) to generate the manpage - markdown to generate README.html out of this file License ------- notmuch-mutt is copyright (C) 2011-2012 Stefano Zacchiroli . notmuch-mutt is released under the terms of the GNU General Public License (GPL), version 3 or above. A copy of the license is available online at . notmuch-0.17/contrib/notmuch-mutt/notmuch-mutt000077500000000000000000000170051226041107500216150ustar00rootroot00000000000000#!/usr/bin/perl -w # # notmuch-mutt - notmuch (of a) helper for Mutt # # Copyright: 2011-2012 Stefano Zacchiroli # License: GNU General Public License (GPL), version 3 or above # # See the bottom of this file for more documentation. # A manpage can be obtained by running "pod2man notmuch-mutt > notmuch-mutt.1" use strict; use warnings; use File::Path; use Getopt::Long qw(:config no_getopt_compat); use Mail::Internet; use Mail::Box::Maildir; use Pod::Usage; use String::ShellQuote; use Term::ReadLine; my $xdg_cache_dir = "$ENV{HOME}/.cache"; $xdg_cache_dir = $ENV{XDG_CACHE_HOME} if $ENV{XDG_CACHE_HOME}; my $cache_dir = "$xdg_cache_dir/notmuch/mutt"; # create an empty maildir (if missing) or empty an existing maildir" sub empty_maildir($) { my ($maildir) = (@_); rmtree($maildir) if (-d $maildir); my $folder = new Mail::Box::Maildir(folder => $maildir, create => 1); $folder->close(); } # search($maildir, $remove_dups, $query) # search mails according to $query with notmuch; store results in $maildir sub search($$$) { my ($maildir, $remove_dups, $query) = @_; my $dup_option = ""; $query = shell_quote($query); if ($remove_dups) { $dup_option = "--duplicate=1"; } empty_maildir($maildir); system("notmuch search --output=files $dup_option $query" . " | sed -e 's: :\\\\ :g'" . " | xargs --no-run-if-empty ln -s -t $maildir/cur/"); } sub prompt($$) { my ($text, $default) = @_; my $query = ""; my $term = Term::ReadLine->new( "notmuch-mutt" ); my $histfile = "$cache_dir/history"; $term->ornaments( 0 ); $term->unbind_key( ord( "\t" ) ); $term->MinLine( 3 ); $histfile = $ENV{MUTT_NOTMUCH_HISTFILE} if $ENV{MUTT_NOTMUCH_HISTFILE}; $term->ReadHistory($histfile) if (-r $histfile); while (1) { chomp($query = $term->readline($text, $default)); if ($query eq "?") { system("man", "notmuch-search-terms"); } else { $term->WriteHistory($histfile); return $query; } } } sub get_message_id() { my $mail = Mail::Internet->new(\*STDIN); my $mid = $mail->head->get("message-id") or return undef; $mid =~ /^<(.*)>$/; # get message-id value return $1; } sub search_action($$$@) { my ($interactive, $results_dir, $remove_dups, @params) = @_; if (! $interactive) { search($results_dir, $remove_dups, join(' ', @params)); } else { my $query = prompt("search ('?' for man): ", join(' ', @params)); if ($query ne "") { search($results_dir, $remove_dups, $query); } } } sub thread_action($$@) { my ($results_dir, $remove_dups, @params) = @_; my $mid = get_message_id(); if (! defined $mid) { empty_maildir($results_dir); die "notmuch-mutt: cannot find Message-Id, abort.\n"; } my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid"); my $tid = `$search_cmd`; # get thread id chomp($tid); search($results_dir, $remove_dups, $tid); } sub tag_action(@) { my $mid = get_message_id(); defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n"; system("notmuch", "tag", @_, "--", "id:$mid"); } sub die_usage() { my %podflags = ( "verbose" => 1, "exitval" => 2 ); pod2usage(%podflags); } sub main() { mkpath($cache_dir) unless (-d $cache_dir); my $results_dir = "$cache_dir/results"; my $interactive = 0; my $help_needed = 0; my $remove_dups = 0; my $getopt = GetOptions( "h|help" => \$help_needed, "o|output-dir=s" => \$results_dir, "p|prompt" => \$interactive, "r|remove-dups" => \$remove_dups); if (! $getopt || $#ARGV < 0) { die_usage() }; my ($action, @params) = ($ARGV[0], @ARGV[1..$#ARGV]); foreach my $param (@params) { $param =~ s/folder:=/folder:/g; } if ($help_needed) { die_usage(); } elsif ($action eq "search" && $#ARGV == 0 && ! $interactive) { print STDERR "Error: no search term provided\n\n"; die_usage(); } elsif ($action eq "search") { search_action($interactive, $results_dir, $remove_dups, @params); } elsif ($action eq "thread") { thread_action($results_dir, $remove_dups, @params); } elsif ($action eq "tag") { tag_action(@params); } else { die_usage(); } } main(); __END__ =head1 NAME notmuch-mutt - notmuch (of a) helper for Mutt =head1 SYNOPSIS =over =item B [I