mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-12-23 01:44:52 +01:00
Import notmuch_0.27.orig.tar.gz
[dgit import orig notmuch_0.27.orig.tar.gz]
This commit is contained in:
commit
045f0e455a
757 changed files with 108867 additions and 0 deletions
25
.dir-locals.el
Normal file
25
.dir-locals.el
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
;; 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))
|
||||||
|
)
|
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/.first-build-message
|
||||||
|
/Makefile.config
|
||||||
|
/sh.config
|
||||||
|
/version.stamp
|
||||||
|
TAGS
|
||||||
|
tags
|
||||||
|
*cscope*
|
||||||
|
/.deps
|
||||||
|
/notmuch
|
||||||
|
/notmuch-shared
|
||||||
|
/lib/libnotmuch.so*
|
||||||
|
/lib/libnotmuch*.dylib
|
||||||
|
*.[ao]
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
/releases
|
||||||
|
/.stamps
|
8
.mailmap
Normal file
8
.mailmap
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Peter Feigl <craven@gmx.net>
|
||||||
|
Nate <nstraz@redhat.com>
|
||||||
|
Ali Polatel <alip@penguen.ev>
|
||||||
|
Stefan <aeuii@posteo.de>
|
||||||
|
Patrick Totzke <patricktotzke@googlemail.com>
|
||||||
|
Patrick Totzke <patricktotzke@gmail.com>
|
||||||
|
Patrick Totzke <p.totzke@ed.ac.uk>
|
||||||
|
Mark Walters <markwalters1009@gmail.com>
|
25
.travis.yml
Normal file
25
.travis.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
language: c
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- dtach
|
||||||
|
- libxapian-dev
|
||||||
|
- libgmime-2.6-dev
|
||||||
|
- libtalloc-dev
|
||||||
|
- python3-sphinx
|
||||||
|
- gpgsm
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./configure
|
||||||
|
- make download-test-databases
|
||||||
|
- make test
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
irc:
|
||||||
|
channels:
|
||||||
|
- "chat.freenode.net#notmuch"
|
||||||
|
on_success: change
|
30
AUTHORS
Normal file
30
AUTHORS
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
Carl Worth <cworth@cworth.org> 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 (https://sup-heliotrope.github.io/).
|
||||||
|
|
||||||
|
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 <dottedmag@dottedmag.net>)
|
||||||
|
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
|
15
COPYING
Normal file
15
COPYING
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 https://www.gnu.org/licenses/
|
676
COPYING-GPL-3
Normal file
676
COPYING-GPL-3
Normal file
|
@ -0,0 +1,676 @@
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|
104
INSTALL
Normal file
104
INSTALL
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
Build 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 four libraries: Xapian, GMime 2.6,
|
||||||
|
Talloc, and zlib 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 https://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
|
||||||
|
-----
|
||||||
|
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 https://github.com/jstedfast/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 https://talloc.samba.org/
|
||||||
|
|
||||||
|
zlib
|
||||||
|
----
|
||||||
|
|
||||||
|
zlib is an extremely popular compression library. It is used
|
||||||
|
by Xapian, so if you installed that you will already have
|
||||||
|
zlib. You may need to install the zlib headers separately.
|
||||||
|
|
||||||
|
Notmuch needs the transparent write feature of zlib introduced
|
||||||
|
in version 1.2.5.2 (Dec. 2011).
|
||||||
|
|
||||||
|
zlib is available from https://zlib.net
|
||||||
|
|
||||||
|
Building Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
To build the documentation for notmuch you need at least version 1.0
|
||||||
|
of sphinx (Jul. 2010).
|
||||||
|
|
||||||
|
Sphinx is available from www.sphinx-doc.org.
|
||||||
|
|
||||||
|
To install the documentation as "info" pages, you will need the
|
||||||
|
additional tools makeinfo and install-info.
|
||||||
|
|
||||||
|
Installing Dependencies from Packages
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
On a modern, package-based operating system you can install all of the
|
||||||
|
dependencies with a single simple command line. For example:
|
||||||
|
|
||||||
|
For Debian and similar:
|
||||||
|
|
||||||
|
sudo apt-get install libxapian-dev libgmime-3.0-dev libtalloc-dev zlib1g-dev python3-sphinx texinfo install-info
|
||||||
|
|
||||||
|
For Fedora and similar:
|
||||||
|
|
||||||
|
sudo yum install xapian-core-devel gmime-devel libtalloc-devel zlib-devel python3-sphinx texinfo info
|
||||||
|
|
||||||
|
On other systems, a similar command can be used, but the details of
|
||||||
|
the package names may be different.
|
||||||
|
|
||||||
|
|
||||||
|
|
71
Makefile
Normal file
71
Makefile
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# We make all targets depend on the Makefiles themselves.
|
||||||
|
global_deps = Makefile Makefile.config Makefile.local \
|
||||||
|
$(subdirs:%=%/Makefile) $(subdirs:%=%/Makefile.local)
|
||||||
|
|
||||||
|
INCLUDE_MORE := yes
|
||||||
|
ifneq ($(filter clean distclean dataclean, $(word 1, $(MAKECMDGOALS))),)
|
||||||
|
CLEAN_GOAL := $(word 1, $(MAKECMDGOALS))
|
||||||
|
|
||||||
|
# If there are more goals following CLEAN_GOAL, run $(MAKE)s in parts.
|
||||||
|
ifneq ($(word 2, $(MAKECMDGOALS)),)
|
||||||
|
INCLUDE_MORE := no
|
||||||
|
FOLLOWING_GOALS := $(wordlist 2, 99, $(MAKECMDGOALS))
|
||||||
|
|
||||||
|
.PHONY: $(FOLLOWING_GOALS) make_in_parts
|
||||||
|
$(FOLLOWING_GOALS):
|
||||||
|
@true
|
||||||
|
$(CLEAN_GOAL): make_in_parts
|
||||||
|
make_in_parts:
|
||||||
|
$(MAKE) $(CLEAN_GOAL)
|
||||||
|
$(MAKE) $(FOLLOWING_GOALS) configure_options="$(configure_options)"
|
||||||
|
endif
|
||||||
|
|
||||||
|
else
|
||||||
|
CLEAN_GOAL :=
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Potentially speedup make clean, distclean and dataclean ; avoid
|
||||||
|
# re-creating Makefile.config if it exists but configure is newer.
|
||||||
|
ifneq ($(CLEAN_GOAL),)
|
||||||
|
Makefile.config: | $(srcdir)/configure
|
||||||
|
else
|
||||||
|
Makefile.config: $(srcdir)/configure
|
||||||
|
endif
|
||||||
|
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)
|
||||||
|
|
||||||
|
ifeq ($(INCLUDE_MORE),yes)
|
||||||
|
# runtime variable definitions available in all subdirs
|
||||||
|
include $(srcdir)/Makefile.global
|
||||||
|
# Finally, include all of the Makefile.local fragments where all the
|
||||||
|
# real work is done.
|
||||||
|
|
||||||
|
include $(subdirs:%=%/Makefile.local) Makefile.local
|
||||||
|
endif
|
65
Makefile.global
Normal file
65
Makefile.global
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# 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:=$(if $(wildcard ${srcdir}/.git),yes,no)
|
||||||
|
|
||||||
|
ifeq ($(IS_GIT),yes)
|
||||||
|
DATE:=$(shell git --git-dir=${srcdir}/.git log --date=short -1 --pretty=format:%cd)
|
||||||
|
else
|
||||||
|
DATE:=$(shell date +%F)
|
||||||
|
endif
|
||||||
|
|
||||||
|
VERSION:=$(shell cat ${srcdir}/version)
|
||||||
|
ELPA_VERSION:=$(subst ~,_,$(VERSION))
|
||||||
|
ifeq ($(filter release release-message pre-release update-versions,$(MAKECMDGOALS)),)
|
||||||
|
ifeq ($(IS_GIT),yes)
|
||||||
|
VERSION:=$(shell git --git-dir=${srcdir}/.git describe --abbrev=7 --match '[0-9.]*'|sed -e s/_/~/ -e s/-/+/ -e s/-/~/)
|
||||||
|
# drop the ~g$sha1 part
|
||||||
|
ELPA_VERSION:=$(word 1,$(subst ~, ,$(VERSION)))
|
||||||
|
# convert git version to package.el friendly form
|
||||||
|
ELPA_VERSION:=$(subst +,snapshot,$(ELPA_VERSION))
|
||||||
|
|
||||||
|
# Write the file 'version.stamp' in case its contents differ from $(VERSION)
|
||||||
|
FILE_VERSION:=$(shell test -f version.stamp && read vs < version.stamp || vs=; echo $$vs)
|
||||||
|
ifneq ($(FILE_VERSION),$(VERSION))
|
||||||
|
$(shell echo "$(VERSION)" > version.stamp)
|
||||||
|
endif
|
||||||
|
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=https://notmuchmail.org/releases
|
||||||
|
TAR_FILE=$(PACKAGE)-$(VERSION).tar.gz
|
||||||
|
ELPA_FILE:=$(PACKAGE)-emacs-$(ELPA_VERSION).tar
|
||||||
|
DEB_TAR_FILE=$(PACKAGE)_$(VERSION).orig.tar.gz
|
||||||
|
SHA256_FILE=$(TAR_FILE).sha256
|
||||||
|
GPG_FILE=$(SHA256_FILE).asc
|
||||||
|
|
||||||
|
PV_FILE=bindings/python/notmuch/version.py
|
||||||
|
|
||||||
|
# Smash together user's values with our extra values
|
||||||
|
STD_CFLAGS := -std=gnu99
|
||||||
|
FINAL_CFLAGS = -DNOTMUCH_VERSION=$(VERSION) $(CPPFLAGS) $(STD_CFLAGS) $(CFLAGS) $(WARN_CFLAGS) $(extra_cflags) $(CONFIGURE_CFLAGS)
|
||||||
|
FINAL_CXXFLAGS = $(CPPFLAGS) $(CXXFLAGS) $(WARN_CXXFLAGS) $(extra_cflags) $(extra_cxxflags) $(CONFIGURE_CXXFLAGS)
|
||||||
|
FINAL_NOTMUCH_LDFLAGS = $(LDFLAGS) -Lutil -lnotmuch_util -Llib -lnotmuch
|
||||||
|
ifeq ($(LIBDIR_IN_LDCONFIG),0)
|
||||||
|
FINAL_NOTMUCH_LDFLAGS += $(RPATH_LDFLAGS)
|
||||||
|
endif
|
||||||
|
FINAL_NOTMUCH_LDFLAGS += $(AS_NEEDED_LDFLAGS) $(GMIME_LDFLAGS) $(TALLOC_LDFLAGS) $(ZLIB_LDFLAGS)
|
||||||
|
FINAL_NOTMUCH_LINKER = CC
|
||||||
|
ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1)
|
||||||
|
FINAL_NOTMUCH_LDFLAGS += $(CONFIGURE_LDFLAGS)
|
||||||
|
FINAL_NOTMUCH_LINKER = CXX
|
||||||
|
endif
|
||||||
|
FINAL_LIBNOTMUCH_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(CONFIGURE_LDFLAGS)
|
309
Makefile.local
Normal file
309
Makefile.local
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: notmuch notmuch-shared build-man build-info ruby-bindings
|
||||||
|
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
|
||||||
|
|
||||||
|
# Depend (also) on the file 'version'. In case of ifeq ($(IS_GIT),yes)
|
||||||
|
# this file may already have been updated.
|
||||||
|
version.stamp: $(srcdir)/version
|
||||||
|
echo $(VERSION) > $@
|
||||||
|
|
||||||
|
$(TAR_FILE):
|
||||||
|
if git tag -v $(UPSTREAM_TAG) >/dev/null 2>&1; then \
|
||||||
|
ref=$(UPSTREAM_TAG); \
|
||||||
|
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
|
||||||
|
ct=`git --no-pager log -1 --pretty=format:%ct $$ref` ; \
|
||||||
|
tar --owner root --group root --append -f $(TAR_FILE).tmp \
|
||||||
|
--transform s_^_$(PACKAGE)-$(VERSION)/_ \
|
||||||
|
--transform 's_.tmp$$__' --mtime=@$$ct version.tmp
|
||||||
|
rm version.tmp
|
||||||
|
gzip -n < $(TAR_FILE).tmp > $(TAR_FILE)
|
||||||
|
@echo "Source is ready for release in $(TAR_FILE)"
|
||||||
|
|
||||||
|
$(SHA256_FILE): $(TAR_FILE)
|
||||||
|
sha256sum $^ > $@
|
||||||
|
|
||||||
|
$(GPG_FILE): $(SHA256_FILE)
|
||||||
|
gpg --armor --sign $^
|
||||||
|
|
||||||
|
.PHONY: dist
|
||||||
|
dist: $(TAR_FILE)
|
||||||
|
|
||||||
|
.PHONY: update-versions
|
||||||
|
|
||||||
|
update-versions:
|
||||||
|
sed -i -e "s/^__VERSION__[[:blank:]]*=.*$$/__VERSION__ = \'${VERSION}\'/" \
|
||||||
|
-e "s/^SOVERSION[[:blank:]]*=.*$$/SOVERSION = \'${LIBNOTMUCH_VERSION_MAJOR}\'/" \
|
||||||
|
${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) $(SHA256_FILE) $(GPG_FILE) releases
|
||||||
|
$(MAKE) VERSION=$(VERSION) release-message > $(PACKAGE)-$(VERSION).announce
|
||||||
|
ifeq ($(REALLY_UPLOAD),yes)
|
||||||
|
git push origin $(VERSION) $(DEB_TAG) release pristine-tar
|
||||||
|
cd releases && scp $(TAR_FILE) $(SHA256_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)/$(SHA256_FILE)"
|
||||||
|
@echo -n " "
|
||||||
|
@cat releases/$(SHA256_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 https://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 --git-dir=${srcdir}/.git ls-files -m),)
|
||||||
|
@echo "No"
|
||||||
|
@echo "The following files have been modified since the most recent git commit:"
|
||||||
|
@echo ""
|
||||||
|
@git --git-dir=${srcdir}/.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 --no-check-certificate -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 occurred"; \
|
||||||
|
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) $(or $(2),$@)\n"; $($(word 1, $(1)))
|
||||||
|
endif
|
||||||
|
# The user has explicitly enabled quiet compilation.
|
||||||
|
ifeq ($(V),0)
|
||||||
|
quiet = @printf "$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
|
||||||
|
endif
|
||||||
|
# Otherwise, print the full command line.
|
||||||
|
quiet ?= $($(word 1, $(1)))
|
||||||
|
|
||||||
|
%.o: %.cc $(global_deps)
|
||||||
|
@mkdir -p $(patsubst %/.,%,.deps/$(@D))
|
||||||
|
$(call quiet,CXX $(CPPFLAGS) $(CXXFLAGS)) -c $(FINAL_CXXFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
|
||||||
|
|
||||||
|
%.o: %.c $(global_deps)
|
||||||
|
@mkdir -p $(patsubst %/.,%,.deps/$(@D))
|
||||||
|
$(call quiet,CC $(CPPFLAGS) $(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
|
||||||
|
|
||||||
|
CPPCHECK=cppcheck
|
||||||
|
.stamps/cppcheck/%: %
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
$(call quiet,CPPCHECK,$<) --template=gcc --error-exitcode=1 --quiet $<
|
||||||
|
@touch $@
|
||||||
|
|
||||||
|
CLEAN := $(CLEAN) .stamps
|
||||||
|
|
||||||
|
.PHONY : clean
|
||||||
|
clean:
|
||||||
|
rm -rf $(CLEAN)
|
||||||
|
|
||||||
|
.PHONY: distclean
|
||||||
|
distclean: clean
|
||||||
|
rm -rf $(DISTCLEAN)
|
||||||
|
|
||||||
|
.PHONY: dataclean
|
||||||
|
dataclean: distclean
|
||||||
|
rm -rf $(DATACLEAN)
|
||||||
|
|
||||||
|
notmuch_client_srcs = \
|
||||||
|
$(notmuch_compat_srcs) \
|
||||||
|
command-line-arguments.c\
|
||||||
|
debugger.c \
|
||||||
|
status.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-reindex.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 \
|
||||||
|
tag-util.c
|
||||||
|
|
||||||
|
notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
|
||||||
|
|
||||||
|
notmuch.o: version.stamp
|
||||||
|
|
||||||
|
notmuch: $(notmuch_client_modules) lib/libnotmuch.a util/libnotmuch_util.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 install-info
|
||||||
|
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
|
||||||
|
|
||||||
|
SRCS := $(SRCS) $(notmuch_client_srcs)
|
||||||
|
CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
|
||||||
|
CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
|
||||||
|
CLEAN := $(CLEAN) .deps
|
||||||
|
|
||||||
|
DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config
|
||||||
|
|
||||||
|
CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%)
|
||||||
|
.PHONY: cppcheck
|
||||||
|
ifeq ($(HAVE_CPPCHECK),1)
|
||||||
|
cppcheck: ${CPPCHECK_STAMPS}
|
||||||
|
else
|
||||||
|
cppcheck:
|
||||||
|
@echo "No cppcheck found during configure; skipping static checking"
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
DEPS := $(SRCS:%.c=.deps/%.d)
|
||||||
|
DEPS := $(DEPS:%.cc=.deps/%.d)
|
||||||
|
-include $(DEPS)
|
||||||
|
|
||||||
|
.SUFFIXES: # Delete the default suffixes. Old-Fashioned Suffix Rules not used.
|
77
README
Normal file
77
README
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
Notmuch - 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:
|
||||||
|
|
||||||
|
(autoload 'notmuch "notmuch" "Notmuch mail" t)
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
https://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
|
11
README.rst
Normal file
11
README.rst
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
If you're reading this on https://github.com/notmuch/notmuch, this is a
|
||||||
|
read-only mirror of the notmuch project.
|
||||||
|
|
||||||
|
For more information about the project, see https://notmuchmail.org.
|
||||||
|
|
||||||
|
Please don't send us pull requests on github. If you have a feature
|
||||||
|
branch that you want us to look at, use ``git send-email`` to send it
|
||||||
|
to notmuch@notmuchmail.org.
|
||||||
|
|
||||||
|
For more information about contributing to the project, see
|
||||||
|
https://notmuchmail.org/contributing/.
|
7
bindings/Makefile
Normal file
7
bindings/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# See Makefile.local for the list of files to be compiled in this
|
||||||
|
# directory.
|
||||||
|
all:
|
||||||
|
$(MAKE) -C .. all
|
||||||
|
|
||||||
|
.DEFAULT:
|
||||||
|
$(MAKE) -C .. $@
|
22
bindings/Makefile.local
Normal file
22
bindings/Makefile.local
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
dir := bindings
|
||||||
|
|
||||||
|
# force the shared library to be built
|
||||||
|
ruby-bindings: lib/$(LINKER_NAME)
|
||||||
|
ifeq ($(HAVE_RUBY_DEV),1)
|
||||||
|
cd $(dir)/ruby && \
|
||||||
|
EXTRA_LDFLAGS="$(NO_UNDEFINED_LDFLAGS)" \
|
||||||
|
LIBNOTMUCH="../../lib/$(LINKER_NAME)" \
|
||||||
|
NOTMUCH_SRCDIR='$(NOTMUCH_SRCDIR)' \
|
||||||
|
$(RUBY) extconf.rb --vendor
|
||||||
|
$(MAKE) -C $(dir)/ruby
|
||||||
|
endif
|
||||||
|
|
||||||
|
CLEAN += $(patsubst %,$(dir)/ruby/%, \
|
||||||
|
.RUBYARCHDIR.time \
|
||||||
|
Makefile database.o directory.o filenames.o\
|
||||||
|
init.o message.o messages.o mkmf.log notmuch.so query.o \
|
||||||
|
status.o tags.o thread.o threads.o)
|
||||||
|
|
||||||
|
CLEAN += bindings/ruby/.vendorarchdir.time
|
4
bindings/python/.gitignore
vendored
Normal file
4
bindings/python/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.py[co]
|
||||||
|
/docs/build
|
||||||
|
/docs/html
|
||||||
|
/build/
|
2
bindings/python/MANIFEST.in
Normal file
2
bindings/python/MANIFEST.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
include notmuch
|
||||||
|
#recursive-include docs/html *
|
17
bindings/python/README
Normal file
17
bindings/python/README
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
notmuch -- The python interface to notmuch
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
This module makes the functionality of the notmuch library
|
||||||
|
(`https://notmuchmail.org`_) available to python. Successful import of
|
||||||
|
this module 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:
|
||||||
|
|
||||||
|
https://notmuch.readthedocs.io/projects/notmuch-python/
|
||||||
|
|
||||||
|
To build the python bindings, do
|
||||||
|
|
||||||
|
python setup.py install --prefix=path/to/your/preferred/location
|
674
bindings/python/docs/COPYING
Normal file
674
bindings/python/docs/COPYING
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
88
bindings/python/docs/Makefile
Normal file
88
bindings/python/docs/Makefile
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# 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 <target>' where <target> 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."
|
218
bindings/python/docs/source/conf.py
Normal file
218
bindings/python/docs/source/conf.py
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
# -*- 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
|
||||||
|
# "<project> v<release> 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 = []
|
||||||
|
|
||||||
|
# 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 <link> 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 = {'https://docs.python.org/': None}
|
54
bindings/python/docs/source/database.rst
Normal file
54
bindings/python/docs/source/database.rst
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
: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:: index_file
|
||||||
|
|
||||||
|
.. automethod:: remove_message
|
||||||
|
|
||||||
|
.. automethod:: find_message
|
||||||
|
|
||||||
|
.. automethod:: find_message_by_filename
|
||||||
|
|
||||||
|
.. automethod:: get_all_tags
|
||||||
|
|
||||||
|
.. automethod:: create_query
|
||||||
|
|
||||||
|
.. automethod:: get_config
|
||||||
|
|
||||||
|
.. automethod:: get_configs
|
||||||
|
|
||||||
|
.. automethod:: set_config
|
||||||
|
|
||||||
|
.. 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
|
32
bindings/python/docs/source/filesystem.rst
Normal file
32
bindings/python/docs/source/filesystem.rst
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
Files and directories
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. currentmodule:: notmuch
|
||||||
|
|
||||||
|
:class:`Filenames` -- An iterator over filenames
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
.. autoclass:: Filenames
|
||||||
|
|
||||||
|
.. method:: Filenames.__len__
|
||||||
|
.. warning::
|
||||||
|
:meth:`__len__` was removed in version 0.22 as it exhausted the
|
||||||
|
iterator and broke list(Filenames()). Use `len(list(names))`
|
||||||
|
instead.
|
||||||
|
|
||||||
|
:class:`Directory` -- 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
|
36
bindings/python/docs/source/index.rst
Normal file
36
bindings/python/docs/source/index.rst
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Welcome to :mod:`notmuch`'s documentation
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
.. currentmodule:: notmuch
|
||||||
|
|
||||||
|
The :mod:`notmuch` module provides an interface to the `notmuch
|
||||||
|
<https://notmuchmail.org>`_ 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 <Sebastian@SSpaeth.de>
|
||||||
|
|
||||||
|
: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`
|
54
bindings/python/docs/source/message.rst
Normal file
54
bindings/python/docs/source/message.rst
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
: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:: get_property
|
||||||
|
|
||||||
|
.. automethod:: get_properties
|
||||||
|
|
||||||
|
.. 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__
|
15
bindings/python/docs/source/messages.rst
Normal file
15
bindings/python/docs/source/messages.rst
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
: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))`.
|
6
bindings/python/docs/source/notes.rst
Normal file
6
bindings/python/docs/source/notes.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Interfacing with notmuch
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. todo:: move the note about talloc out of the code
|
||||||
|
|
||||||
|
.. automodule:: notmuch
|
43
bindings/python/docs/source/query.rst
Normal file
43
bindings/python/docs/source/query.rst
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
: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 explicitly specified via
|
||||||
|
:meth:`set_sort`. By default it is set to `None`.
|
||||||
|
|
||||||
|
.. automethod:: exclude_tag
|
||||||
|
|
||||||
|
.. automethod:: search_threads
|
||||||
|
|
||||||
|
.. automethod:: search_messages
|
||||||
|
|
||||||
|
.. automethod:: count_messages
|
||||||
|
|
||||||
|
.. automethod:: count_threads
|
19
bindings/python/docs/source/quickstart.rst
Normal file
19
bindings/python/docs/source/quickstart.rst
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Quickstart 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)
|
57
bindings/python/docs/source/status_and_errors.rst
Normal file
57
bindings/python/docs/source/status_and_errors.rst
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
.. 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:: UnsupportedOperationError(message=None)
|
||||||
|
:members:
|
||||||
|
.. autoexception:: UpgradeRequiredError(message=None)
|
||||||
|
:members:
|
||||||
|
.. autoexception:: PathError(message=None)
|
||||||
|
:members:
|
||||||
|
.. autoexception:: NotInitializedError(message=None)
|
||||||
|
:members:
|
17
bindings/python/docs/source/tags.rst
Normal file
17
bindings/python/docs/source/tags.rst
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
: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__
|
26
bindings/python/docs/source/thread.rst
Normal file
26
bindings/python/docs/source/thread.rst
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
: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__
|
14
bindings/python/docs/source/threads.rst
Normal file
14
bindings/python/docs/source/threads.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
:class:`Threads` -- Threads iterator
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. currentmodule:: notmuch
|
||||||
|
|
||||||
|
.. autoclass:: Threads
|
||||||
|
|
||||||
|
.. method:: __len__
|
||||||
|
.. warning::
|
||||||
|
:meth:`__len__` was removed in version 0.22 as it exhausted the
|
||||||
|
iterator and broke list(Threads()). Use `len(list(msgs))`
|
||||||
|
instead.
|
||||||
|
|
||||||
|
.. automethod:: __str__
|
84
bindings/python/notmuch/__init__.py
Normal file
84
bindings/python/notmuch/__init__.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
"""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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
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,
|
||||||
|
UnsupportedOperationError,
|
||||||
|
UpgradeRequiredError,
|
||||||
|
PathError,
|
||||||
|
)
|
||||||
|
from .version import __VERSION__
|
||||||
|
__LICENSE__ = "GPL v3+"
|
||||||
|
__AUTHOR__ = 'Sebastian Spaeth <Sebastian@SSpaeth.de>'
|
71
bindings/python/notmuch/compat.py
Normal file
71
bindings/python/notmuch/compat.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
'''
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
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')
|
||||||
|
|
||||||
|
# We import the SafeConfigParser class on behalf of other code to cope
|
||||||
|
# with the differences between Python 2 and 3.
|
||||||
|
SafeConfigParser # avoid warning about unused import
|
786
bindings/python/notmuch/database.py
Normal file
786
bindings/python/notmuch/database.py
Normal file
|
@ -0,0 +1,786 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import warnings
|
||||||
|
from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
|
||||||
|
from .compat import SafeConfigParser
|
||||||
|
from .globals import (
|
||||||
|
nmlib,
|
||||||
|
Enum,
|
||||||
|
_str,
|
||||||
|
NotmuchConfigListP,
|
||||||
|
NotmuchDatabaseP,
|
||||||
|
NotmuchDirectoryP,
|
||||||
|
NotmuchIndexoptsP,
|
||||||
|
NotmuchMessageP,
|
||||||
|
NotmuchTagsP,
|
||||||
|
)
|
||||||
|
from .errors import (
|
||||||
|
STATUS,
|
||||||
|
FileError,
|
||||||
|
NotmuchError,
|
||||||
|
NullPointerError,
|
||||||
|
NotInitializedError,
|
||||||
|
)
|
||||||
|
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"""
|
||||||
|
|
||||||
|
DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH'])
|
||||||
|
"""Constants: policies for decrypting messages during indexing"""
|
||||||
|
|
||||||
|
"""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_get_revision"""
|
||||||
|
_get_revision = nmlib.notmuch_database_get_revision
|
||||||
|
_get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)]
|
||||||
|
_get_revision.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 = c_uint
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._db:
|
||||||
|
status = self._destroy(self._db)
|
||||||
|
if status != STATUS.SUCCESS:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
|
||||||
|
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 = c_uint
|
||||||
|
|
||||||
|
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:
|
||||||
|
status = self._close(self._db)
|
||||||
|
if status != STATUS.SUCCESS:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def get_revision (self):
|
||||||
|
"""Returns the committed database revison and UUID
|
||||||
|
|
||||||
|
:returns: (revison, uuid) The database revision as a positive integer
|
||||||
|
and the UUID of the database.
|
||||||
|
"""
|
||||||
|
self._assert_db_is_initialized()
|
||||||
|
uuid = c_char_p ()
|
||||||
|
revision = Database._get_revision(self._db, byref (uuid))
|
||||||
|
return (revision, uuid.value.decode ('utf-8'))
|
||||||
|
|
||||||
|
_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:`index_file`,
|
||||||
|
: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)
|
||||||
|
|
||||||
|
_get_default_indexopts = nmlib.notmuch_database_get_default_indexopts
|
||||||
|
_get_default_indexopts.argtypes = [NotmuchDatabaseP]
|
||||||
|
_get_default_indexopts.restype = NotmuchIndexoptsP
|
||||||
|
|
||||||
|
_indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy
|
||||||
|
_indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint]
|
||||||
|
_indexopts_set_decrypt_policy.restype = None
|
||||||
|
|
||||||
|
_indexopts_destroy = nmlib.notmuch_indexopts_destroy
|
||||||
|
_indexopts_destroy.argtypes = [NotmuchIndexoptsP]
|
||||||
|
_indexopts_destroy.restype = None
|
||||||
|
|
||||||
|
_index_file = nmlib.notmuch_database_index_file
|
||||||
|
_index_file.argtypes = [NotmuchDatabaseP, c_char_p,
|
||||||
|
c_void_p,
|
||||||
|
POINTER(NotmuchMessageP)]
|
||||||
|
_index_file.restype = c_uint
|
||||||
|
|
||||||
|
def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None):
|
||||||
|
"""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`.
|
||||||
|
|
||||||
|
:param decrypt_policy: If the message contains any encrypted
|
||||||
|
parts, and decrypt_policy is set to
|
||||||
|
:attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to
|
||||||
|
decrypt the message and index the cleartext, stashing any
|
||||||
|
discovered session keys. If it is set to
|
||||||
|
:attr:`DECRYPTION_POLICY`.FALSE, it will never try to
|
||||||
|
decrypt during indexing. If it is set to
|
||||||
|
:attr:`DECRYPTION_POLICY`.AUTO, then it will try to use
|
||||||
|
any stashed session keys it knows about, but will not try
|
||||||
|
to access the user's secret keys.
|
||||||
|
:attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as
|
||||||
|
:attr:`DECRYPTION_POLICY`.TRUE except that no session keys
|
||||||
|
are stashed in the database. If decrypt_policy is set to
|
||||||
|
None (the default), then the database itself will decide
|
||||||
|
whether to decrypt, based on the `index.decrypt`
|
||||||
|
configuration setting (see notmuch-config(1)).
|
||||||
|
|
||||||
|
: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()
|
||||||
|
indexopts = c_void_p(None)
|
||||||
|
if decrypt_policy is not None:
|
||||||
|
indexopts = self._get_default_indexopts(self._db)
|
||||||
|
self._indexopts_set_decrypt_policy(indexopts, decrypt_policy)
|
||||||
|
|
||||||
|
status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p))
|
||||||
|
|
||||||
|
if indexopts:
|
||||||
|
self._indexopts_destroy(indexopts)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def add_message(self, filename, sync_maildir_flags=False):
|
||||||
|
"""Deprecated alias for :meth:`index_file`
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
|
||||||
|
|
||||||
|
return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
|
||||||
|
|
||||||
|
_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()
|
||||||
|
status = self._remove_message(self._db, _str(filename))
|
||||||
|
if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
return status
|
||||||
|
|
||||||
|
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 occurred 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 occurred 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)
|
||||||
|
|
||||||
|
"""notmuch_database_status_string"""
|
||||||
|
_status_string = nmlib.notmuch_database_status_string
|
||||||
|
_status_string.argtypes = [NotmuchDatabaseP]
|
||||||
|
_status_string.restype = c_char_p
|
||||||
|
|
||||||
|
def status_string(self):
|
||||||
|
"""Returns the status string of the database
|
||||||
|
|
||||||
|
This is sometimes used for additional error reporting
|
||||||
|
"""
|
||||||
|
self._assert_db_is_initialized()
|
||||||
|
s = Database._status_string(self._db)
|
||||||
|
if s:
|
||||||
|
return s.decode('utf-8', 'ignore')
|
||||||
|
return s
|
||||||
|
|
||||||
|
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_database_get_config"""
|
||||||
|
_get_config = nmlib.notmuch_database_get_config
|
||||||
|
_get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)]
|
||||||
|
_get_config.restype = c_uint
|
||||||
|
|
||||||
|
def get_config(self, key):
|
||||||
|
"""Return the value of the given config key.
|
||||||
|
|
||||||
|
Note that only config values that are stored in the database are
|
||||||
|
searched and returned. The config file is not read.
|
||||||
|
|
||||||
|
:param key: the config key under which a value should be looked up, it
|
||||||
|
should probably be in the form "section.key"
|
||||||
|
:type key: str
|
||||||
|
:returns: the config value or the empty string if no value is present
|
||||||
|
for that key
|
||||||
|
:rtype: str
|
||||||
|
:raises: :exc:`NotmuchError` in case of failure.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._assert_db_is_initialized()
|
||||||
|
return_string = c_char_p()
|
||||||
|
status = self._get_config(self._db, _str(key), byref(return_string))
|
||||||
|
if status != STATUS.SUCCESS:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
return return_string.value.decode('utf-8')
|
||||||
|
|
||||||
|
"""notmuch_database_get_config_list"""
|
||||||
|
_get_config_list = nmlib.notmuch_database_get_config_list
|
||||||
|
_get_config_list.argtypes = [NotmuchDatabaseP, c_char_p,
|
||||||
|
POINTER(NotmuchConfigListP)]
|
||||||
|
_get_config_list.restype = c_uint
|
||||||
|
|
||||||
|
_config_list_valid = nmlib.notmuch_config_list_valid
|
||||||
|
_config_list_valid.argtypes = [NotmuchConfigListP]
|
||||||
|
_config_list_valid.restype = bool
|
||||||
|
|
||||||
|
_config_list_key = nmlib.notmuch_config_list_key
|
||||||
|
_config_list_key.argtypes = [NotmuchConfigListP]
|
||||||
|
_config_list_key.restype = c_char_p
|
||||||
|
|
||||||
|
_config_list_value = nmlib.notmuch_config_list_value
|
||||||
|
_config_list_value.argtypes = [NotmuchConfigListP]
|
||||||
|
_config_list_value.restype = c_char_p
|
||||||
|
|
||||||
|
_config_list_move_to_next = nmlib.notmuch_config_list_move_to_next
|
||||||
|
_config_list_move_to_next.argtypes = [NotmuchConfigListP]
|
||||||
|
_config_list_move_to_next.restype = None
|
||||||
|
|
||||||
|
_config_list_destroy = nmlib.notmuch_config_list_destroy
|
||||||
|
_config_list_destroy.argtypes = [NotmuchConfigListP]
|
||||||
|
_config_list_destroy.restype = None
|
||||||
|
|
||||||
|
def get_configs(self, prefix=''):
|
||||||
|
"""Return a generator of key, value pairs where the start of key
|
||||||
|
matches the given prefix
|
||||||
|
|
||||||
|
Note that only config values that are stored in the database are
|
||||||
|
searched and returned. The config file is not read. If no `prefix` is
|
||||||
|
given all config values are returned.
|
||||||
|
|
||||||
|
This could be used to get all named queries into a dict for example::
|
||||||
|
|
||||||
|
queries = {k[6:]: v for k, v in db.get_configs('query.')}
|
||||||
|
|
||||||
|
:param prefix: a string by which the keys should be selected
|
||||||
|
:type prefix: str
|
||||||
|
:yields: all key-value pairs where `prefix` matches the beginning
|
||||||
|
of the key
|
||||||
|
:ytype: pairs of str
|
||||||
|
:raises: :exc:`NotmuchError` in case of failure.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._assert_db_is_initialized()
|
||||||
|
config_list_p = NotmuchConfigListP()
|
||||||
|
status = self._get_config_list(self._db, _str(prefix),
|
||||||
|
byref(config_list_p))
|
||||||
|
if status != STATUS.SUCCESS:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
while self._config_list_valid(config_list_p):
|
||||||
|
key = self._config_list_key(config_list_p).decode('utf-8')
|
||||||
|
value = self._config_list_value(config_list_p).decode('utf-8')
|
||||||
|
yield key, value
|
||||||
|
self._config_list_move_to_next(config_list_p)
|
||||||
|
|
||||||
|
"""notmuch_database_set_config"""
|
||||||
|
_set_config = nmlib.notmuch_database_set_config
|
||||||
|
_set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p]
|
||||||
|
_set_config.restype = c_uint
|
||||||
|
|
||||||
|
def set_config(self, key, value):
|
||||||
|
"""Set a config value in the notmuch database.
|
||||||
|
|
||||||
|
If an empty string is provided as `value` the `key` is unset!
|
||||||
|
|
||||||
|
:param key: the key to set
|
||||||
|
:type key: str
|
||||||
|
:param value: the value to store under `key`
|
||||||
|
:type value: str
|
||||||
|
:returns: None
|
||||||
|
:raises: :exc:`NotmuchError` in case of failure.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._assert_db_is_initialized()
|
||||||
|
status = self._set_config(self._db, _str(key), _str(value))
|
||||||
|
if status != STATUS.SUCCESS:
|
||||||
|
raise NotmuchError(status)
|
185
bindings/python/notmuch/directory.py
Normal file
185
bindings/python/notmuch/directory.py
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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.index_file` 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 "<notmuch Directory object '%s'>" % 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)
|
204
bindings/python/notmuch/errors.py
Normal file
204
bindings/python/notmuch/errors.py
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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',
|
||||||
|
'UNSUPPORTED_OPERATION',
|
||||||
|
'UPGRADE_REQUIRED',
|
||||||
|
'PATH_ERROR',
|
||||||
|
'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
|
||||||
|
* UNSUPPORTED_OPERATION
|
||||||
|
* UPGRADE_REQUIRED
|
||||||
|
* PATH_ERROR
|
||||||
|
* 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.UNSUPPORTED_OPERATION: UnsupportedOperationError,
|
||||||
|
STATUS.UPGRADE_REQUIRED: UpgradeRequiredError,
|
||||||
|
STATUS.PATH_ERROR: PathError,
|
||||||
|
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 UnsupportedOperationError(NotmuchError):
|
||||||
|
status = STATUS.UNSUPPORTED_OPERATION
|
||||||
|
|
||||||
|
|
||||||
|
class UpgradeRequiredError(NotmuchError):
|
||||||
|
status = STATUS.UPGRADE_REQUIRED
|
||||||
|
|
||||||
|
|
||||||
|
class PathError(NotmuchError):
|
||||||
|
status = STATUS.PATH_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
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
|
131
bindings/python/notmuch/filenames.py
Normal file
131
bindings/python/notmuch/filenames.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
from ctypes import c_char_p
|
||||||
|
from .globals import (
|
||||||
|
nmlib,
|
||||||
|
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::
|
||||||
|
|
||||||
|
list_of_names = list(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 publicly 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 = [NotmuchFilenamesP]
|
||||||
|
_destroy.restype = None
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Close and free the notmuch filenames"""
|
||||||
|
if self._files_p:
|
||||||
|
self._destroy(self._files_p)
|
105
bindings/python/notmuch/globals.py
Normal file
105
bindings/python/notmuch/globals.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ctypes import CDLL, Structure, POINTER
|
||||||
|
from notmuch.version import SOVERSION
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
#package-global instance of the notmuch library
|
||||||
|
try:
|
||||||
|
from os import uname
|
||||||
|
if uname()[0] == 'Darwin':
|
||||||
|
nmlib = CDLL("libnotmuch.{0:s}.dylib".format(SOVERSION))
|
||||||
|
else:
|
||||||
|
nmlib = CDLL("libnotmuch.so.{0:s}".format(SOVERSION))
|
||||||
|
except:
|
||||||
|
raise ImportError("Could not find shared 'notmuch' library.")
|
||||||
|
|
||||||
|
from .compat import Python3StringMixIn, encode_utf8 as _str
|
||||||
|
|
||||||
|
# We import these on behalf of other modules. Silence warning about
|
||||||
|
# these symbols not being used.
|
||||||
|
Python3StringMixIn
|
||||||
|
_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 NotmuchMessagePropertiesS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchMessagePropertiesP = POINTER(NotmuchMessagePropertiesS)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchTagsS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchTagsP = POINTER(NotmuchTagsS)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchDirectoryS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchFilenamesS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchFilenamesP = POINTER(NotmuchFilenamesS)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchConfigListS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchConfigListP = POINTER(NotmuchConfigListS)
|
||||||
|
|
||||||
|
|
||||||
|
class NotmuchIndexoptsS(Structure):
|
||||||
|
pass
|
||||||
|
NotmuchIndexoptsP = POINTER(NotmuchIndexoptsS)
|
719
bindings/python/notmuch/message.py
Normal file
719
bindings/python/notmuch/message.py
Normal file
|
@ -0,0 +1,719 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
Jesse Rosenthal <jrosenthal@jhu.edu>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from ctypes import c_char_p, c_long, c_uint, c_int, POINTER, byref
|
||||||
|
from datetime import date
|
||||||
|
from .globals import (
|
||||||
|
nmlib,
|
||||||
|
Enum,
|
||||||
|
_str,
|
||||||
|
Python3StringMixIn,
|
||||||
|
NotmuchTagsP,
|
||||||
|
NotmuchMessageP,
|
||||||
|
NotmuchMessagesP,
|
||||||
|
NotmuchMessagePropertiesP,
|
||||||
|
NotmuchFilenamesP,
|
||||||
|
)
|
||||||
|
from .errors import (
|
||||||
|
STATUS,
|
||||||
|
NotmuchError,
|
||||||
|
NullPointerError,
|
||||||
|
NotInitializedError,
|
||||||
|
)
|
||||||
|
from .tag import Tags
|
||||||
|
from .filenames import Filenames
|
||||||
|
|
||||||
|
import email
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
"""notmuch_message_get_property"""
|
||||||
|
_get_property = nmlib.notmuch_message_get_property
|
||||||
|
_get_property.argtypes = [NotmuchMessageP, c_char_p, POINTER(c_char_p)]
|
||||||
|
_get_property.restype = c_int
|
||||||
|
|
||||||
|
"""notmuch_message_get_properties"""
|
||||||
|
_get_properties = nmlib.notmuch_message_get_properties
|
||||||
|
_get_properties.argtypes = [NotmuchMessageP, c_char_p, c_int]
|
||||||
|
_get_properties.restype = NotmuchMessagePropertiesP
|
||||||
|
|
||||||
|
"""notmuch_message_properties_valid"""
|
||||||
|
_properties_valid = nmlib.notmuch_message_properties_valid
|
||||||
|
_properties_valid.argtypes = [NotmuchMessagePropertiesP]
|
||||||
|
_properties_valid.restype = bool
|
||||||
|
|
||||||
|
"""notmuch_message_properties_value"""
|
||||||
|
_properties_value = nmlib.notmuch_message_properties_value
|
||||||
|
_properties_value.argtypes = [NotmuchMessagePropertiesP]
|
||||||
|
_properties_value.restype = c_char_p
|
||||||
|
|
||||||
|
"""notmuch_message_properties_key"""
|
||||||
|
_properties_key = nmlib.notmuch_message_properties_key
|
||||||
|
_properties_key.argtypes = [NotmuchMessagePropertiesP]
|
||||||
|
_properties_key.restype = c_char_p
|
||||||
|
|
||||||
|
"""notmuch_message_properties_move_to_next"""
|
||||||
|
_properties_move_to_next = nmlib.notmuch_message_properties_move_to_next
|
||||||
|
_properties_move_to_next.argtypes = [NotmuchMessagePropertiesP]
|
||||||
|
_properties_move_to_next.restype = None
|
||||||
|
|
||||||
|
#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 occurred
|
||||||
|
"""
|
||||||
|
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 occurred
|
||||||
|
"""
|
||||||
|
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 get_property(self, prop):
|
||||||
|
""" Retrieve the value for a single property key
|
||||||
|
|
||||||
|
:param prop: The name of the property to get.
|
||||||
|
:returns: String with the property value or None if there is no such
|
||||||
|
key. In the case of multiple values for the given key, the
|
||||||
|
first one is retrieved.
|
||||||
|
:raises: :exc:`NotInitializedError` if message has not been
|
||||||
|
initialized
|
||||||
|
"""
|
||||||
|
if not self._msg:
|
||||||
|
raise NotInitializedError()
|
||||||
|
|
||||||
|
value = c_char_p()
|
||||||
|
status = Message._get_property(self._msg, _str(prop), byref(value))
|
||||||
|
if status != 0:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
|
||||||
|
return value.value.decode('utf-8')
|
||||||
|
|
||||||
|
def get_properties(self, prop="", exact=False):
|
||||||
|
""" Get the properties of the message, returning a generator of
|
||||||
|
name, value pairs.
|
||||||
|
|
||||||
|
The generator will yield once per value. There might be more than one
|
||||||
|
value on each name, so the generator might yield the same name several
|
||||||
|
times.
|
||||||
|
|
||||||
|
:param prop: The name of the property to get. Otherwise it will return
|
||||||
|
the full list of properties of the message.
|
||||||
|
:param exact: if True, require exact match with key. Otherwise
|
||||||
|
treat as prefix.
|
||||||
|
:yields: Each property values as a pair of `name, value`
|
||||||
|
:ytype: pairs of str
|
||||||
|
:raises: :exc:`NotInitializedError` if message has not been
|
||||||
|
initialized
|
||||||
|
"""
|
||||||
|
if not self._msg:
|
||||||
|
raise NotInitializedError()
|
||||||
|
|
||||||
|
properties = Message._get_properties(self._msg, _str(prop), exact)
|
||||||
|
while Message._properties_valid(properties):
|
||||||
|
key = Message._properties_key(properties)
|
||||||
|
value = Message._properties_value(properties)
|
||||||
|
yield key.decode("utf-8"), value.decode("utf-8")
|
||||||
|
Message._properties_move_to_next(properties)
|
||||||
|
|
||||||
|
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.index_file` 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._maildir_flags_to_tags(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(), 'rb')
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
email_msg = email.message_from_file(fp)
|
||||||
|
else:
|
||||||
|
email_msg = email.message_from_binary_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)
|
199
bindings/python/notmuch/messages.py
Normal file
199
bindings/python/notmuch/messages.py
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
Jesse Rosenthal <jrosenthal@jhu.edu>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 publicly 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__
|
237
bindings/python/notmuch/query.py
Normal file
237
bindings/python/notmuch/query.py
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ctypes import c_char_p, c_uint, POINTER, byref
|
||||||
|
from .globals import (
|
||||||
|
nmlib,
|
||||||
|
Enum,
|
||||||
|
_str,
|
||||||
|
NotmuchQueryP,
|
||||||
|
NotmuchThreadsP,
|
||||||
|
NotmuchDatabaseP,
|
||||||
|
NotmuchMessagesP,
|
||||||
|
)
|
||||||
|
from .errors import (
|
||||||
|
NotmuchError,
|
||||||
|
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)
|
||||||
|
|
||||||
|
_exclude_tag = nmlib.notmuch_query_add_tag_exclude
|
||||||
|
_exclude_tag.argtypes = [NotmuchQueryP, c_char_p]
|
||||||
|
_exclude_tag.resttype = None
|
||||||
|
|
||||||
|
def exclude_tag(self, tagname):
|
||||||
|
"""Add a tag that will be excluded from the query results by default.
|
||||||
|
|
||||||
|
This exclusion will be overridden if this tag appears explicitly in the
|
||||||
|
query.
|
||||||
|
|
||||||
|
:param tagname: Name of the tag to be excluded
|
||||||
|
"""
|
||||||
|
self._assert_query_is_initialized()
|
||||||
|
self._exclude_tag(self._query, _str(tagname))
|
||||||
|
|
||||||
|
"""notmuch_query_search_threads"""
|
||||||
|
_search_threads = nmlib.notmuch_query_search_threads
|
||||||
|
_search_threads.argtypes = [NotmuchQueryP, POINTER(NotmuchThreadsP)]
|
||||||
|
_search_threads.restype = c_uint
|
||||||
|
|
||||||
|
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 = NotmuchThreadsP() # == NULL
|
||||||
|
status = Query._search_threads(self._query, byref(threads_p))
|
||||||
|
if status != 0:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
|
||||||
|
if not threads_p:
|
||||||
|
raise NullPointerError
|
||||||
|
return Threads(threads_p, self)
|
||||||
|
|
||||||
|
"""notmuch_query_search_messages_st"""
|
||||||
|
_search_messages = nmlib.notmuch_query_search_messages
|
||||||
|
_search_messages.argtypes = [NotmuchQueryP, POINTER(NotmuchMessagesP)]
|
||||||
|
_search_messages.restype = c_uint
|
||||||
|
|
||||||
|
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 = NotmuchMessagesP() # == NULL
|
||||||
|
status = Query._search_messages(self._query, byref(msgs_p))
|
||||||
|
if status != 0:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
|
||||||
|
if not msgs_p:
|
||||||
|
raise NullPointerError
|
||||||
|
return Messages(msgs_p, self)
|
||||||
|
|
||||||
|
_count_messages = nmlib.notmuch_query_count_messages
|
||||||
|
_count_messages.argtypes = [NotmuchQueryP, POINTER(c_uint)]
|
||||||
|
_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()
|
||||||
|
count = c_uint(0)
|
||||||
|
status = Query._count_messages(self._query, byref(count))
|
||||||
|
if status != 0:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
return count.value
|
||||||
|
|
||||||
|
_count_threads = nmlib.notmuch_query_count_threads
|
||||||
|
_count_threads.argtypes = [NotmuchQueryP, POINTER(c_uint)]
|
||||||
|
_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()
|
||||||
|
count = c_uint(0)
|
||||||
|
status = Query._count_threads(self._query, byref(count))
|
||||||
|
if status != 0:
|
||||||
|
raise NotmuchError(status)
|
||||||
|
return count.value
|
||||||
|
|
||||||
|
_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)
|
141
bindings/python/notmuch/tag.py
Normal file
141
bindings/python/notmuch/tag.py
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
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 publicly 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)
|
281
bindings/python/notmuch/thread.py
Normal file
281
bindings/python/notmuch/thread.py
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 publicly 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 explicitly 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)
|
152
bindings/python/notmuch/threads.py
Normal file
152
bindings/python/notmuch/threads.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
"""
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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::
|
||||||
|
|
||||||
|
list_of_threads = list(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.
|
||||||
|
# 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 publicly 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 __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)
|
3
bindings/python/notmuch/version.py
Normal file
3
bindings/python/notmuch/version.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# this file should be kept in sync with ../../../version
|
||||||
|
__VERSION__ = '0.27'
|
||||||
|
SOVERSION = '5'
|
70
bindings/python/setup.py
Normal file
70
bindings/python/setup.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#!/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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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='https://notmuchmail.org/',
|
||||||
|
download_url='https://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
|
||||||
|
<https://notmuchmail.org>`_ 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 <https://notmuch.readthedocs.io/>`_.
|
||||||
|
|
||||||
|
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='https://www.gnu.org/licenses/gpl-3.0.txt',
|
||||||
|
)
|
7
bindings/ruby/.gitignore
vendored
Normal file
7
bindings/ruby/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# .gitignore for bindings/ruby
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
/Makefile
|
||||||
|
/mkmf.log
|
||||||
|
/notmuch.so
|
||||||
|
*.o
|
7
bindings/ruby/README
Normal file
7
bindings/ruby/README
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
To build the the notmuch ruby extension, run the following commands
|
||||||
|
from the *top level* notmuch source directory:
|
||||||
|
|
||||||
|
% ./configure
|
||||||
|
% make ruby-bindings
|
||||||
|
|
||||||
|
The generic documentation about building notmuch also applies.
|
423
bindings/ruby/database.c
Normal file
423
bindings/ruby/database.c
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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_status_t ret;
|
||||||
|
notmuch_database_t *db;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Database (self, db);
|
||||||
|
ret = notmuch_database_destroy (db);
|
||||||
|
DATA_PTR (self) = NULL;
|
||||||
|
notmuch_rb_status_raise (ret);
|
||||||
|
|
||||||
|
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_index_file (db, path, NULL, &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.get_all_tags() => TAGS
|
||||||
|
*
|
||||||
|
* Returns a list of all tags found in the database.
|
||||||
|
*/
|
||||||
|
VALUE
|
||||||
|
notmuch_rb_database_get_all_tags (VALUE self)
|
||||||
|
{
|
||||||
|
notmuch_database_t *db;
|
||||||
|
notmuch_tags_t *tags;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Database (self, db);
|
||||||
|
|
||||||
|
tags = notmuch_database_get_all_tags (db);
|
||||||
|
if (!tags) {
|
||||||
|
const char *msg = notmuch_database_status_string (db);
|
||||||
|
if (!msg)
|
||||||
|
msg = "Unknown notmuch error";
|
||||||
|
|
||||||
|
rb_raise (notmuch_rb_eBaseError, "%s", msg);
|
||||||
|
}
|
||||||
|
return Data_Wrap_Struct (notmuch_rb_cTags, NULL, NULL, tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
357
bindings/ruby/defs.h
Normal file
357
bindings/ruby/defs.h
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DEFS_H
|
||||||
|
#define DEFS_H
|
||||||
|
|
||||||
|
#include <notmuch.h>
|
||||||
|
#include <ruby.h>
|
||||||
|
|
||||||
|
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_get_all_tags (VALUE self);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
VALUE
|
||||||
|
notmuch_rb_query_count_threads (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
|
115
bindings/ruby/directory.c
Normal file
115
bindings/ruby/directory.c
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
25
bindings/ruby/extconf.rb
Normal file
25
bindings/ruby/extconf.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# coding: utf-8
|
||||||
|
# Copyright 2010, 2011, 2012 Ali Polatel <alip@exherbo.org>
|
||||||
|
# Distributed under the terms of the GNU General Public License v3
|
||||||
|
|
||||||
|
require 'mkmf'
|
||||||
|
|
||||||
|
dir = File.join(ENV['NOTMUCH_SRCDIR'], 'lib')
|
||||||
|
|
||||||
|
# includes
|
||||||
|
$INCFLAGS = "-I#{dir} #{$INCFLAGS}"
|
||||||
|
|
||||||
|
if ENV['EXTRA_LDFLAGS']
|
||||||
|
$LDFLAGS += " " + ENV['EXTRA_LDFLAGS']
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ENV['LIBNOTMUCH']
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
$LOCAL_LIBS += ENV['LIBNOTMUCH']
|
||||||
|
|
||||||
|
# Create Makefile
|
||||||
|
dir_config('notmuch')
|
||||||
|
create_makefile('notmuch')
|
58
bindings/ruby/filenames.c
Normal file
58
bindings/ruby/filenames.c
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
357
bindings/ruby/init.c
Normal file
357
bindings/ruby/init.c
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <tt>notmuch</tt> 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, "all_tags", notmuch_rb_database_get_all_tags, 0); /* 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 */
|
||||||
|
rb_define_method (notmuch_rb_cQuery, "count_threads", notmuch_rb_query_count_threads, 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);
|
||||||
|
}
|
366
bindings/ruby/message.c
Normal file
366
bindings/ruby/message.c
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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.filenames => 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;
|
||||||
|
}
|
80
bindings/ruby/messages.c
Normal file
80
bindings/ruby/messages.c
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
209
bindings/ruby/query.c
Normal file
209
bindings/ruby/query.c
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
notmuch_status_t status;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Query (self, query);
|
||||||
|
|
||||||
|
status = notmuch_query_search_threads (query, &threads);
|
||||||
|
if (status)
|
||||||
|
notmuch_rb_status_raise (status);
|
||||||
|
|
||||||
|
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;
|
||||||
|
notmuch_status_t status;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Query (self, query);
|
||||||
|
|
||||||
|
status = notmuch_query_search_messages (query, &messages);
|
||||||
|
if (status)
|
||||||
|
notmuch_rb_status_raise (status);
|
||||||
|
|
||||||
|
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;
|
||||||
|
notmuch_status_t status;
|
||||||
|
unsigned int count;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Query (self, query);
|
||||||
|
|
||||||
|
status = notmuch_query_count_messages (query, &count);
|
||||||
|
if (status)
|
||||||
|
notmuch_rb_status_raise (status);
|
||||||
|
|
||||||
|
return UINT2NUM(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq: QUERY.count_threads => Fixnum
|
||||||
|
*
|
||||||
|
* Return an estimate of the number of threads matching a search
|
||||||
|
*/
|
||||||
|
VALUE
|
||||||
|
notmuch_rb_query_count_threads (VALUE self)
|
||||||
|
{
|
||||||
|
notmuch_query_t *query;
|
||||||
|
notmuch_status_t status;
|
||||||
|
unsigned int count;
|
||||||
|
|
||||||
|
Data_Get_Notmuch_Query (self, query);
|
||||||
|
|
||||||
|
status = notmuch_query_count_threads (query, &count);
|
||||||
|
if (status)
|
||||||
|
notmuch_rb_status_raise (status);
|
||||||
|
|
||||||
|
return UINT2NUM(count);
|
||||||
|
}
|
17
bindings/ruby/rdoc.sh
Executable file
17
bindings/ruby/rdoc.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/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
|
51
bindings/ruby/status.c
Normal file
51
bindings/ruby/status.c
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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");
|
||||||
|
}
|
||||||
|
}
|
61
bindings/ruby/tags.c
Normal file
61
bindings/ruby/tags.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
213
bindings/ruby/thread.c
Normal file
213
bindings/ruby/thread.c
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
60
bindings/ruby/threads.c
Normal file
60
bindings/ruby/threads.c
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Ali Polatel <alip@exherbo.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
304
command-line-arguments.c
Normal file
304
command-line-arguments.c
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "error_util.h"
|
||||||
|
#include "command-line-arguments.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OPT_FAILED, /* false */
|
||||||
|
OPT_OK, /* good */
|
||||||
|
OPT_GIVEBACK, /* pop one of the arguments you thought you were getting off the stack */
|
||||||
|
} opt_handled;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Search the array of keywords for a given argument, assigning the
|
||||||
|
output variable to the corresponding value. Return false if nothing
|
||||||
|
matches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static opt_handled
|
||||||
|
_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next,
|
||||||
|
const char *arg_str, bool negate)
|
||||||
|
{
|
||||||
|
const notmuch_keyword_t *keywords;
|
||||||
|
|
||||||
|
if (next == '\0') {
|
||||||
|
/* No keyword given */
|
||||||
|
arg_str = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (keywords = arg_desc->keywords; keywords->name; keywords++) {
|
||||||
|
if (strcmp (arg_str, keywords->name) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (arg_desc->opt_flags && negate)
|
||||||
|
*arg_desc->opt_flags &= ~keywords->value;
|
||||||
|
else if (arg_desc->opt_flags)
|
||||||
|
*arg_desc->opt_flags |= keywords->value;
|
||||||
|
else
|
||||||
|
*arg_desc->opt_keyword = keywords->value;
|
||||||
|
|
||||||
|
return OPT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg_desc->opt_keyword && arg_desc->keyword_no_arg_value && next != ':' && next != '=') {
|
||||||
|
for (keywords = arg_desc->keywords; keywords->name; keywords++) {
|
||||||
|
if (strcmp (arg_desc->keyword_no_arg_value, keywords->name) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
*arg_desc->opt_keyword = keywords->value;
|
||||||
|
fprintf (stderr, "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
|
||||||
|
" Please specify the argument explicitly!\n", arg_desc->name, arg_desc->keyword_no_arg_value);
|
||||||
|
|
||||||
|
return OPT_GIVEBACK;
|
||||||
|
}
|
||||||
|
fprintf (stderr, "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n", arg_str, arg_desc->name);
|
||||||
|
return OPT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 OPT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static opt_handled
|
||||||
|
_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next,
|
||||||
|
const char *arg_str, bool negate)
|
||||||
|
{
|
||||||
|
bool value;
|
||||||
|
|
||||||
|
if (next == '\0' || strcmp (arg_str, "true") == 0) {
|
||||||
|
value = true;
|
||||||
|
} else if (strcmp (arg_str, "false") == 0) {
|
||||||
|
value = false;
|
||||||
|
} else {
|
||||||
|
fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
|
||||||
|
return OPT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
*arg_desc->opt_bool = negate ? !value : value;
|
||||||
|
|
||||||
|
return OPT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static opt_handled
|
||||||
|
_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 OPT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
*arg_desc->opt_int = strtol (arg_str, &endptr, 10);
|
||||||
|
if (*endptr == '\0')
|
||||||
|
return OPT_OK;
|
||||||
|
|
||||||
|
fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
|
||||||
|
arg_str, arg_desc->name);
|
||||||
|
return OPT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static opt_handled
|
||||||
|
_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 OPT_FAILED;
|
||||||
|
}
|
||||||
|
if (arg_str[0] == '\0' && ! arg_desc->allow_empty) {
|
||||||
|
fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
|
||||||
|
return OPT_FAILED;
|
||||||
|
}
|
||||||
|
*arg_desc->opt_string = arg_str;
|
||||||
|
return OPT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return number of non-NULL opt_* fields in opt_desc. */
|
||||||
|
static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
!!opt_desc->opt_inherit +
|
||||||
|
!!opt_desc->opt_bool +
|
||||||
|
!!opt_desc->opt_int +
|
||||||
|
!!opt_desc->opt_keyword +
|
||||||
|
!!opt_desc->opt_flags +
|
||||||
|
!!opt_desc->opt_string +
|
||||||
|
!!opt_desc->opt_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return true if opt_desc is valid. */
|
||||||
|
static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
|
||||||
|
{
|
||||||
|
int n = _opt_set_count (opt_desc);
|
||||||
|
|
||||||
|
if (n > 1)
|
||||||
|
INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
|
||||||
|
opt_desc->name);
|
||||||
|
|
||||||
|
return n > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Search for the {pos_arg_index}th position argument, return false if
|
||||||
|
that does not exist.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool
|
||||||
|
parse_position_arg (const char *arg_str, int pos_arg_index,
|
||||||
|
const notmuch_opt_desc_t *arg_desc) {
|
||||||
|
|
||||||
|
int pos_arg_counter = 0;
|
||||||
|
while (_opt_valid (arg_desc)) {
|
||||||
|
if (arg_desc->opt_position) {
|
||||||
|
if (pos_arg_counter == pos_arg_index) {
|
||||||
|
*arg_desc->opt_position = arg_str;
|
||||||
|
if (arg_desc->present)
|
||||||
|
*arg_desc->present = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pos_arg_counter++;
|
||||||
|
}
|
||||||
|
arg_desc++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NEGATIVE_PREFIX "no-"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Search for a non-positional (i.e. starting with --) argument matching arg,
|
||||||
|
* parse a possible value, and assign to *output_var
|
||||||
|
*/
|
||||||
|
|
||||||
|
int
|
||||||
|
parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
|
||||||
|
{
|
||||||
|
assert(argv);
|
||||||
|
|
||||||
|
const char *_arg = argv[opt_index];
|
||||||
|
|
||||||
|
assert(_arg);
|
||||||
|
assert(options);
|
||||||
|
|
||||||
|
const char *arg = _arg + 2; /* _arg starts with -- */
|
||||||
|
const char *negative_arg = NULL;
|
||||||
|
|
||||||
|
/* See if this is a --no-argument */
|
||||||
|
if (strlen (arg) > strlen (NEGATIVE_PREFIX) &&
|
||||||
|
strncmp (arg, NEGATIVE_PREFIX, strlen (NEGATIVE_PREFIX)) == 0) {
|
||||||
|
negative_arg = arg + strlen (NEGATIVE_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
const notmuch_opt_desc_t *try;
|
||||||
|
|
||||||
|
const char *next_arg = NULL;
|
||||||
|
if (opt_index < argc - 1 && strncmp (argv[opt_index + 1], "--", 2) != 0)
|
||||||
|
next_arg = argv[opt_index + 1];
|
||||||
|
|
||||||
|
for (try = options; _opt_valid (try); try++) {
|
||||||
|
if (try->opt_inherit) {
|
||||||
|
int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
|
||||||
|
if (new_index >= 0)
|
||||||
|
return new_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! try->name)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char next;
|
||||||
|
const char *value;
|
||||||
|
bool negate = false;
|
||||||
|
|
||||||
|
if (strncmp (arg, try->name, strlen (try->name)) == 0) {
|
||||||
|
next = arg[strlen (try->name)];
|
||||||
|
value = arg + strlen (try->name) + 1;
|
||||||
|
} else if (negative_arg && (try->opt_bool || try->opt_flags) &&
|
||||||
|
strncmp (negative_arg, try->name, strlen (try->name)) == 0) {
|
||||||
|
next = negative_arg[strlen (try->name)];
|
||||||
|
value = negative_arg + strlen (try->name) + 1;
|
||||||
|
/* The argument part of --no-argument matches, negate the result. */
|
||||||
|
negate = true;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
bool lookahead = (next == '\0' && next_arg != NULL && ! try->opt_bool);
|
||||||
|
|
||||||
|
if (lookahead) {
|
||||||
|
next = ' ';
|
||||||
|
value = next_arg;
|
||||||
|
opt_index ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
opt_handled opt_status = OPT_FAILED;
|
||||||
|
if (try->opt_keyword || try->opt_flags)
|
||||||
|
opt_status = _process_keyword_arg (try, next, value, negate);
|
||||||
|
else if (try->opt_bool)
|
||||||
|
opt_status = _process_boolean_arg (try, next, value, negate);
|
||||||
|
else if (try->opt_int)
|
||||||
|
opt_status = _process_int_arg (try, next, value);
|
||||||
|
else if (try->opt_string)
|
||||||
|
opt_status = _process_string_arg (try, next, value);
|
||||||
|
else
|
||||||
|
INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
|
||||||
|
|
||||||
|
if (opt_status == OPT_FAILED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (lookahead && opt_status == OPT_GIVEBACK)
|
||||||
|
opt_index --;
|
||||||
|
|
||||||
|
if (try->present)
|
||||||
|
*try->present = true;
|
||||||
|
|
||||||
|
return opt_index+1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
bool 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 {
|
||||||
|
int prev_opt_index = opt_index;
|
||||||
|
|
||||||
|
if (strlen (argv[opt_index]) == 2)
|
||||||
|
return opt_index+1;
|
||||||
|
|
||||||
|
opt_index = parse_option (argc, argv, options, opt_index);
|
||||||
|
if (opt_index < 0) {
|
||||||
|
fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
|
||||||
|
more_args = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt_index;
|
||||||
|
}
|
82
command-line-arguments.h
Normal file
82
command-line-arguments.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#ifndef NOTMUCH_OPTS_H
|
||||||
|
#define NOTMUCH_OPTS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "notmuch.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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. */
|
||||||
|
typedef struct notmuch_opt_desc {
|
||||||
|
/* One and only one of opt_* must be set. */
|
||||||
|
const struct notmuch_opt_desc *opt_inherit;
|
||||||
|
bool *opt_bool;
|
||||||
|
int *opt_int;
|
||||||
|
int *opt_keyword;
|
||||||
|
int *opt_flags;
|
||||||
|
const char **opt_string;
|
||||||
|
const char **opt_position;
|
||||||
|
|
||||||
|
/* for opt_keyword only: if no matching arguments were found, and
|
||||||
|
* keyword_no_arg_value is set, then use keyword_no_arg_value instead. */
|
||||||
|
const char *keyword_no_arg_value;
|
||||||
|
|
||||||
|
/* Must be set except for opt_inherit and opt_position. */
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/* Optional, if non-NULL, set to true if the option is present. */
|
||||||
|
bool *present;
|
||||||
|
|
||||||
|
/* Optional, allow empty strings for opt_string. */
|
||||||
|
bool allow_empty;
|
||||||
|
|
||||||
|
/* Must be set for opt_keyword and opt_flags. */
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int
|
||||||
|
parse_option (int argc, char **argv, const notmuch_opt_desc_t* options, int opt_index);
|
||||||
|
|
||||||
|
bool
|
||||||
|
parse_position_arg (const char *arg,
|
||||||
|
int position_arg_index,
|
||||||
|
const notmuch_opt_desc_t* options);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
1
compat/.gitignore
vendored
Normal file
1
compat/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/zlib.pc
|
5
compat/Makefile
Normal file
5
compat/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
all:
|
||||||
|
$(MAKE) -C .. all
|
||||||
|
|
||||||
|
.DEFAULT:
|
||||||
|
$(MAKE) -C .. $@
|
28
compat/Makefile.local
Normal file
28
compat/Makefile.local
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
dir := compat
|
||||||
|
extra_cflags += -I$(srcdir)/$(dir)
|
||||||
|
|
||||||
|
notmuch_compat_srcs :=
|
||||||
|
|
||||||
|
ifneq ($(HAVE_CANONICALIZE_FILE_NAME),1)
|
||||||
|
notmuch_compat_srcs += $(dir)/canonicalize_file_name.c
|
||||||
|
endif
|
||||||
|
|
||||||
|
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)
|
21
compat/README
Normal file
21
compat/README
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
notmuch/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
|
18
compat/canonicalize_file_name.c
Normal file
18
compat/canonicalize_file_name.c
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include "compat.h"
|
||||||
|
#include <limits.h>
|
||||||
|
#undef _GNU_SOURCE
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
char *
|
||||||
|
canonicalize_file_name (const char * path)
|
||||||
|
{
|
||||||
|
#ifdef PATH_MAX
|
||||||
|
char *resolved_path = malloc (PATH_MAX+1);
|
||||||
|
if (resolved_path == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return realpath (path, resolved_path);
|
||||||
|
#else
|
||||||
|
#error undefined PATH_MAX _and_ missing canonicalize_file_name not supported
|
||||||
|
#endif
|
||||||
|
}
|
11
compat/check_asctime.c
Normal file
11
compat/check_asctime.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct tm tm;
|
||||||
|
|
||||||
|
(void) asctime_r (&tm, NULL);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
11
compat/check_getpwuid.c
Normal file
11
compat/check_getpwuid.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct passwd passwd, *ignored;
|
||||||
|
|
||||||
|
(void) getpwuid_r (0, &passwd, NULL, 0, &ignored);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
85
compat/compat.h
Normal file
85
compat/compat.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/* 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 https://www.gnu.org/licenses/ .
|
||||||
|
*
|
||||||
|
* Author: Carl Worth <cworth@cworth.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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_CANONICALIZE_FILE_NAME
|
||||||
|
/* we only call this function from C, and this makes testing easier */
|
||||||
|
#ifndef __cplusplus
|
||||||
|
char *
|
||||||
|
canonicalize_file_name (const char *path);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !HAVE_GETLINE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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.h>
|
||||||
|
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 */
|
47
compat/function-attributes.h
Normal file
47
compat/function-attributes.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* 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 https://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
|
18
compat/gen_zlib_pc.c
Normal file
18
compat/gen_zlib_pc.c
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
static const char *template =
|
||||||
|
"prefix=/usr\n"
|
||||||
|
"exec_prefix=${prefix}\n"
|
||||||
|
"libdir=${exec_prefix}/lib\n"
|
||||||
|
"\n"
|
||||||
|
"Name: zlib\n"
|
||||||
|
"Description: zlib compression library\n"
|
||||||
|
"Version: %s\n"
|
||||||
|
"Libs: -lz\n";
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf(template, ZLIB_VERSION);
|
||||||
|
return 0;
|
||||||
|
}
|
133
compat/getdelim.c
Normal file
133
compat/getdelim.c
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/* 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 <stdio.h>
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
29
compat/getline.c
Normal file
29
compat/getline.c
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* 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 <stdio.h>
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
getline (char **lineptr, size_t *n, FILE *stream)
|
||||||
|
{
|
||||||
|
return getdelim (lineptr, n, '\n', stream);
|
||||||
|
}
|
10
compat/have_canonicalize_file_name.c
Normal file
10
compat/have_canonicalize_file_name.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char *found;
|
||||||
|
char *string;
|
||||||
|
|
||||||
|
found = canonicalize_file_name (string);
|
||||||
|
}
|
10
compat/have_d_type.c
Normal file
10
compat/have_d_type.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct dirent ent;
|
||||||
|
|
||||||
|
(void) ent.d_type;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
13
compat/have_getline.c
Normal file
13
compat/have_getline.c
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
ssize_t count = 0;
|
||||||
|
size_t n = 0;
|
||||||
|
char **lineptr = NULL;
|
||||||
|
FILE *stream = NULL;
|
||||||
|
|
||||||
|
count = getline(lineptr, &n, stream);
|
||||||
|
}
|
10
compat/have_strcasestr.c
Normal file
10
compat/have_strcasestr.c
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char *found;
|
||||||
|
const char *haystack, *needle;
|
||||||
|
|
||||||
|
found = strcasestr(haystack, needle);
|
||||||
|
}
|
11
compat/have_strsep.c
Normal file
11
compat/have_strsep.c
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char *found;
|
||||||
|
char **stringp;
|
||||||
|
const char *delim;
|
||||||
|
|
||||||
|
found = strsep(stringp, delim);
|
||||||
|
}
|
6
compat/have_timegm.c
Normal file
6
compat/have_timegm.c
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return (int) timegm((struct tm *)0);
|
||||||
|
}
|
40
compat/strcasestr.c
Normal file
40
compat/strcasestr.c
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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 <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
65
compat/strsep.c
Normal file
65
compat/strsep.c
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/* 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 <string.h>
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
56
compat/timegm.c
Normal file
56
compat/timegm.c
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/* 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 <time.h>
|
||||||
|
#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);
|
||||||
|
}
|
7
completion/Makefile
Normal file
7
completion/Makefile
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# See Makefile.local for the list of files to be compiled in this
|
||||||
|
# directory.
|
||||||
|
all:
|
||||||
|
$(MAKE) -C .. all
|
||||||
|
|
||||||
|
.DEFAULT:
|
||||||
|
$(MAKE) -C .. $@
|
22
completion/Makefile.local
Normal file
22
completion/Makefile.local
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- 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
|
16
completion/README
Normal file
16
completion/README
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
notmuch 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] https://github.com/scop/bash-completion
|
||||||
|
|
||||||
|
notmuch-completion.zsh
|
||||||
|
|
||||||
|
Command-line completion for the zsh shell.
|
622
completion/notmuch-completion.bash
Normal file
622
completion/notmuch-completion.bash
Normal file
|
@ -0,0 +1,622 @@
|
||||||
|
# bash completion for notmuch -*- shell-script -*-
|
||||||
|
#
|
||||||
|
# Copyright © 2013 Jani Nikula
|
||||||
|
#
|
||||||
|
# Based on the bash-completion package:
|
||||||
|
# https://github.com/scop/bash-completion
|
||||||
|
#
|
||||||
|
# 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 https://www.gnu.org/licenses/ .
|
||||||
|
#
|
||||||
|
# Author: Jani Nikula <jani@nikula.org>
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# BUGS:
|
||||||
|
#
|
||||||
|
# Add space after an --option without parameter (e.g. reply --decrypt)
|
||||||
|
# on completion.
|
||||||
|
#
|
||||||
|
|
||||||
|
_notmuch_shared_options="--help --uuid= --version"
|
||||||
|
|
||||||
|
# $1: current input of the form prefix:partialinput, where prefix is
|
||||||
|
# to or from.
|
||||||
|
_notmuch_email()
|
||||||
|
{
|
||||||
|
local output prefix cur
|
||||||
|
|
||||||
|
prefix="${1%%:*}"
|
||||||
|
cur="${1#*:}"
|
||||||
|
|
||||||
|
# Cut the input to be completed at punctuation because
|
||||||
|
# (apparently) Xapian does not support the trailing wildcard '*'
|
||||||
|
# operator for input with punctuation. We let compgen handle the
|
||||||
|
# extra filtering required.
|
||||||
|
cur="${cur%%[^a-zA-Z0-9]*}"
|
||||||
|
|
||||||
|
case "$prefix" in
|
||||||
|
# Note: It would be more accurate and less surprising to have
|
||||||
|
# output=recipients here for to: addresses, but as gathering
|
||||||
|
# the recipient addresses requires disk access for each
|
||||||
|
# matching message, this becomes prohibitively slow.
|
||||||
|
to|from) output=sender;;
|
||||||
|
*) return;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Only emit plain, lower case, unique addresses.
|
||||||
|
notmuch address --output=$output $prefix:"${cur}*" | \
|
||||||
|
sed 's/[^<]*<\([^>]*\)>/\1/' | tr "[:upper:]" "[:lower:]" | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
_notmuch_mimetype()
|
||||||
|
{
|
||||||
|
# use mime types from mime-support package if available, and fall
|
||||||
|
# back to a handful of common ones otherwise
|
||||||
|
if [ -r "/etc/mime.types" ]; then
|
||||||
|
sed -n '/^[[:alpha:]]/{s/[[:space:]].*//;p;}' /etc/mime.types
|
||||||
|
else
|
||||||
|
cat <<EOF
|
||||||
|
application/gzip
|
||||||
|
application/msword
|
||||||
|
application/pdf
|
||||||
|
application/zip
|
||||||
|
audio/mpeg
|
||||||
|
audio/ogg
|
||||||
|
image/gif
|
||||||
|
image/jpeg
|
||||||
|
image/png
|
||||||
|
message/rfc822
|
||||||
|
text/calendar
|
||||||
|
text/html
|
||||||
|
text/plain
|
||||||
|
text/vcard
|
||||||
|
text/x-diff
|
||||||
|
text/x-vcalendar
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_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_email ${cur}`" -- ${cur##to:}) )
|
||||||
|
;;
|
||||||
|
from:*)
|
||||||
|
COMPREPLY=( $(compgen -P "from:" -W "`_notmuch_email ${cur}`" -- ${cur##from:}) )
|
||||||
|
;;
|
||||||
|
path:*)
|
||||||
|
local path=`notmuch config get database.path`
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -d "$path/${cur##path:}" | sed "s|^$path/||" ) )
|
||||||
|
;;
|
||||||
|
folder:*)
|
||||||
|
local path=`notmuch config get database.path`
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -d "$path/${cur##folder:}" | \
|
||||||
|
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
|
||||||
|
;;
|
||||||
|
mimetype:*)
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -P "mimetype:" -W "`_notmuch_mimetype ${cur}`" -- ${cur##mimetype:}) )
|
||||||
|
;;
|
||||||
|
query:*)
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -P "query:" -W "`notmuch config list | sed -n '/^query\./s/^query\.\([^=]*\)=.*/\1/p'`" -- ${cur##query:}) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local search_terms="from: to: subject: attachment: mimetype: tag: id: thread: folder: path: date: lastmod: query: property:"
|
||||||
|
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 -d
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--backup= --quiet ${_notmuch_shared_options}"
|
||||||
|
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 -d
|
||||||
|
;;
|
||||||
|
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= --lastmod ${_notmuch_shared_options}"
|
||||||
|
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="--gzip --format= --output= ${_notmuch_shared_options}"
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_notmuch_search_terms
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_notmuch_emacs_mua()
|
||||||
|
{
|
||||||
|
local cur prev words cword split
|
||||||
|
_init_completion -s || return
|
||||||
|
|
||||||
|
$split &&
|
||||||
|
case "${prev}" in
|
||||||
|
--to|--cc|--bcc)
|
||||||
|
COMPREPLY=( $(compgen -W "`_notmuch_email to:${cur}`" -- ${cur}) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--body)
|
||||||
|
_filedir
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--subject= --to= --cc= --bcc= --body= --no-window-system --client --auto-daemon --create-frame --print --help --hello"
|
||||||
|
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $(compgen -W "`_notmuch_email to:${cur}`" -- ${cur}) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
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)
|
||||||
|
local path=`notmuch config get database.path`
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -d "$path/${cur}" | \
|
||||||
|
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--decrypt)
|
||||||
|
COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
--*)
|
||||||
|
local options="--create-folder --folder= --keep --no-hooks --decrypt= ${_notmuch_shared_options}"
|
||||||
|
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 -s || return
|
||||||
|
|
||||||
|
$split &&
|
||||||
|
case "${prev}" in
|
||||||
|
--decrypt)
|
||||||
|
COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--no-hooks --decrypt= --quiet ${_notmuch_shared_options}"
|
||||||
|
compopt -o nospace
|
||||||
|
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
|
||||||
|
;;
|
||||||
|
--decrypt)
|
||||||
|
COMPREPLY=( $( compgen -W "true auto false" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--format= --format-version= --reply-to= --decrypt= ${_notmuch_shared_options}"
|
||||||
|
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= ${_notmuch_shared_options}"
|
||||||
|
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= ${_notmuch_shared_options}"
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_notmuch_search_terms
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_notmuch_reindex()
|
||||||
|
{
|
||||||
|
local cur prev words cword split
|
||||||
|
_init_completion -s || return
|
||||||
|
|
||||||
|
$split &&
|
||||||
|
case "${prev}" in
|
||||||
|
--decrypt)
|
||||||
|
COMPREPLY=( $( compgen -W "true false auto nostash" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--decrypt= ${_notmuch_shared_options}"
|
||||||
|
compopt -o nospace
|
||||||
|
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
_notmuch_search_terms
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_notmuch_address()
|
||||||
|
{
|
||||||
|
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 "sender recipients count address" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--sort)
|
||||||
|
COMPREPLY=( $( compgen -W "newest-first oldest-first" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--exclude)
|
||||||
|
COMPREPLY=( $( compgen -W "true false flag all" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--deduplicate)
|
||||||
|
COMPREPLY=( $( compgen -W "no mailbox address" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--format= --output= --sort= --exclude= --deduplicate= ${_notmuch_shared_options}"
|
||||||
|
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
|
||||||
|
;;
|
||||||
|
--decrypt)
|
||||||
|
COMPREPLY=( $( compgen -W "true auto false stash" -- "${cur}" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
! $split &&
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
local options="--entire-thread= --format= --exclude= --body= --format-version= --part= --verify --decrypt= --include-html ${_notmuch_shared_options}"
|
||||||
|
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 ${_notmuch_shared_options}"
|
||||||
|
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 reindex search address setup show tag emacs-mua"
|
||||||
|
local arg cur prev words cword split
|
||||||
|
|
||||||
|
# require bash-completion with _init_completion
|
||||||
|
type -t _init_completion >/dev/null 2>&1 || return
|
||||||
|
|
||||||
|
_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
|
||||||
|
case "${cur}" in
|
||||||
|
-*)
|
||||||
|
# XXX: handle ${_notmuch_shared_options} and --config=
|
||||||
|
local options="--help --version"
|
||||||
|
COMPREPLY=( $(compgen -W "${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 properties"
|
||||||
|
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
|
88
completion/notmuch-completion.zsh
Normal file
88
completion/notmuch-completion.zsh
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#compdef notmuch
|
||||||
|
|
||||||
|
# ZSH completion for `notmuch`
|
||||||
|
# Copyright © 2009 Ingmar Vanhassel <ingmar@exherbo.org>
|
||||||
|
|
||||||
|
_notmuch_commands()
|
||||||
|
{
|
||||||
|
local -a notmuch_commands
|
||||||
|
notmuch_commands=(
|
||||||
|
'help:display documentation for a subcommand'
|
||||||
|
'setup:interactively configure notmuch'
|
||||||
|
|
||||||
|
'address:output addresses from matching messages'
|
||||||
|
'compact:compact the notmuch database'
|
||||||
|
'config:access notmuch configuration file'
|
||||||
|
'count:count messages matching the given search terms'
|
||||||
|
'dump:creates a plain-text dump of the tags of each message'
|
||||||
|
'insert:add a message to the maildir and notmuch database'
|
||||||
|
'new:incorporate new mail into the notmuch database'
|
||||||
|
'reply:constructs a reply template for a set of messages'
|
||||||
|
'restore:restores the tags from the given file (see notmuch dump)'
|
||||||
|
'search:search for messages matching the given search terms'
|
||||||
|
'show:show messages matching the given search terms'
|
||||||
|
'tag:add/remove tags for all messages matching the search terms'
|
||||||
|
)
|
||||||
|
|
||||||
|
_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"))' \
|
||||||
|
'--output=[select what to output]:output:((summary threads messages files tags))'
|
||||||
|
}
|
||||||
|
|
||||||
|
_notmuch_address()
|
||||||
|
{
|
||||||
|
_arguments -s : \
|
||||||
|
'--sort=[sort results]:sorting:((newest-first\:"reverse chronological order" oldest-first\:"chronological order"))' \
|
||||||
|
'--output=[select what to output]:output:((sender recipients count))'
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 :
|
3
contrib/go/.gitignore
vendored
Normal file
3
contrib/go/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/src/github.com/
|
||||||
|
/pkg/
|
||||||
|
/bin/
|
502
contrib/go/LICENSE
Normal file
502
contrib/go/LICENSE
Normal file
|
@ -0,0 +1,502 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the library's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1990
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
40
contrib/go/Makefile
Normal file
40
contrib/go/Makefile
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# 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
|
16
contrib/go/README
Normal file
16
contrib/go/README
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
go-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] https://notmuchmail.org/
|
261
contrib/go/src/notmuch-addrlookup/addrlookup.go
Normal file
261
contrib/go/src/notmuch-addrlookup/addrlookup.go
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
package 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*(("(\.|[^"])*"|[^,])*<?(?mail\b\w+([-+.]\w+)*\@\w+[-\.\w]*\.([-\.\w]+)*\w\b)>?)`
|
||||||
|
// pattern := "\\s*((\\\"(\\\\.|[^\\\\\"])*\\\"|[^,])*" +
|
||||||
|
// "<?(?P<mail>\\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)
|
||||||
|
}
|
1404
contrib/go/src/notmuch/notmuch.go
Normal file
1404
contrib/go/src/notmuch/notmuch.go
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue