mirror of https://codeberg.org/cage/tinmop/
- initial commit.
This commit is contained in:
commit
c56a5b86ca
|
@ -0,0 +1,33 @@
|
|||
Makefile
|
||||
Makefile.in
|
||||
|
||||
autom4te.cache/
|
||||
|
||||
po/Makefile
|
||||
po/Makefile.in
|
||||
po/POTFILES
|
||||
po/en@boldquot.gmo
|
||||
po/en@boldquot.insert-header
|
||||
po/en@boldquot.po
|
||||
po/en@quot.gmo
|
||||
po/en@quot.insert-header
|
||||
po/en@quot.po
|
||||
po/remove-potcdate.sed
|
||||
|
||||
po/stamp-po
|
||||
po/it/
|
||||
po/*.gmo
|
||||
|
||||
quick_quicklisp.sh
|
||||
|
||||
src/config.lisp
|
||||
|
||||
**-noshare.*
|
||||
|
||||
**~
|
||||
|
||||
*.diff
|
||||
*.patch
|
||||
*.log
|
||||
|
||||
tinmop
|
|
@ -0,0 +1,44 @@
|
|||
* Contributing
|
||||
|
||||
There is always need for help, you can join the developer, sending
|
||||
patches or translating the UI to your favourite language.
|
||||
|
||||
Also there is need for better documentation.
|
||||
|
||||
And a fancy logo would be great too! :)
|
||||
|
||||
Just point your browser to the
|
||||
[[https://notabug.org/cage/tinmop/][code repository]].
|
||||
|
||||
** Some Notes
|
||||
Please understand that tinmop is a very opinionated client and with a
|
||||
someway radical approach to social networks. The developer tried to
|
||||
offer a "/degamified/" experience to the user.
|
||||
|
||||
This means that, in particular, i will not accept patches or
|
||||
requests for:
|
||||
|
||||
- implementing mentioning or other alerts system;
|
||||
- trending, hashtags/people suggested, etc.;
|
||||
- count of followers, people you are following.
|
||||
|
||||
I think you get the idea ;-)
|
||||
|
||||
Also i try to hide as much as information i can to the server so i
|
||||
won't implement muting or blocking. Ignore the toot for annoying user
|
||||
configuring the client to do so, instead.
|
||||
|
||||
Finally before starting coding like a crazy it is better to discuss
|
||||
the general idea with the developers, this way will be more unlikely
|
||||
that the patch will be rejected and our time wasted.
|
||||
|
||||
And do not forget to have fun!
|
||||
|
||||
* Translations
|
||||
|
||||
translation template can be found in ~po/tinmop.pot~ in gettext format,
|
||||
there are a bunch of free software editor to work with this file or
|
||||
you can just
|
||||
[[https://www.gnu.org/software/gettext/manual/html_node/PO-Mode.html][use emacs]].
|
||||
|
||||
So far the only completed translation is in Italian.
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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
|
||||
<http://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
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -0,0 +1,6 @@
|
|||
2019-12-26 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* Makefile.am (SUBDIRS): Add po.
|
||||
(EXTRA_DIST): Add config.rpath, m4/ChangeLog.
|
||||
* configure.ac (AC_CONFIG_FILES): Add po/Makefile.in.
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
- src/hooks
|
||||
derived from
|
||||
Copyright (c) 2014 Paul M. Rodriguez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
according to: https://github.com/TBRSS/serapeum
|
||||
|
||||
- src/db-utils.lisp
|
||||
src/db.lisp
|
||||
src/text-utils.lisp
|
||||
|
||||
uses code from:
|
||||
niccolo': a chemicals inventory
|
||||
Copyright (C) 2016 Universita' degli Studi di Palermo
|
||||
|
||||
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 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
- src/misc-utils.lisp
|
||||
'defalias' derived from
|
||||
Copyright (c) 2014 Paul M. Rodriguez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
according to: https://github.com/TBRSS/serapeum
|
||||
|
||||
'unsplice' derived from
|
||||
Copyright (c) 2011-2012, James M. Lawrence. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of the project nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
according to: https://github.com/lmj/lparallel
|
||||
|
||||
'intersperse' family of functions derives from serapeum
|
||||
Copyright (c) 2014 Paul M. Rodriguez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
defun-w-lock
|
||||
|
||||
uses code from:
|
||||
niccolo': a chemicals inventory
|
||||
Copyright (C) 2016 Universita' degli Studi di Palermo
|
||||
|
||||
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 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
array-slice
|
||||
derived from alexandria
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- misc.lisp uses code derived from:
|
||||
local-time Copyright (c) 2005-2012 by Daniel Lowe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- src/complete.lisp
|
||||
derived from linedit
|
||||
Copyright (c) 2003, 2004 Nikodemus Siivola, Julian Squires
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- src/windows.lisp
|
||||
uses code from croatoan
|
||||
Copyright (c) 2012-2019,2020 Anton Vidovic <anton.vidovic@gmx.de>
|
||||
|
||||
Portions Copyright (c) 2018 Daniel Vedder <d.vedder@web.de>
|
||||
Portions Copyright (c) 2019 D4ryus <d4ryus@teknik.io>
|
||||
Portions Copyright (c) 2019-2020 cage2 <github.com/cage2>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
- emoji-shortcodes.lisp
|
||||
derived from:
|
||||
https://github.com/milesj/emojibase
|
||||
relased under
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2019 Miles Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,68 @@
|
|||
# tinmop: an humble mastodon client
|
||||
# Copyright (C) 2020 cage
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program.
|
||||
# If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
bin_SCRIPTS = tinmop
|
||||
|
||||
CLEANFILES = $(bin_SCRIPTS) $(CONF_PATH_FILE);
|
||||
|
||||
CONF_PATH_FILE = src/config.lisp
|
||||
|
||||
CONF_PATH_FILE_IN = src/config.lisp.in
|
||||
|
||||
BUILT_SOURCES = $(CONF_PATH_FILE)
|
||||
|
||||
EXTRA_DIST = config.rpath m4/ChangeLog tinmop.asd README.org src LICENSES.org COPYING \
|
||||
etc/shared.conf etc/default-theme.conf etc/init.lisp
|
||||
|
||||
SUBDIRS = po
|
||||
|
||||
dist_doc_DATA = README.org README.txt LICENSES.org CONTRIBUTING.org doc/man.org doc/send-toot.lisp
|
||||
|
||||
nobase_dist_sysconf_DATA = etc/shared.conf etc/init.lisp etc/default-theme.conf
|
||||
|
||||
dist_man1_MANS = doc/tinmop.man
|
||||
|
||||
$(PACKAGE): $(CONF_PATH_FILE)
|
||||
$(LISP_COMPILER) \
|
||||
--eval "(asdf:load-system '$(PACKAGE))" \
|
||||
--eval "(in-package main)" \
|
||||
--eval "(sb-ext:save-lisp-and-die \"$(PACKAGE)\" :toplevel 'main::main :executable t :purify t :save-runtime-options t)"
|
||||
|
||||
$(CONF_PATH_FILE):
|
||||
grep "^;" $(CONF_PATH_FILE_IN) > $(CONF_PATH_FILE)
|
||||
echo -e "(in-package :config)\n" >> $(CONF_PATH_FILE);
|
||||
echo "(alexandria:define-constant +sys-data-dir+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\""$(pkgdatadir)"/data/\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
echo "(alexandria:define-constant +sys-conf-dir+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\"$(sysconfdir)\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
echo "(alexandria:define-constant +catalog-dir+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\""$(localedir)"\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
echo "(alexandria:define-constant +text-domain+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\""$(PACKAGE)"\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
echo "(alexandria:define-constant +program-name+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\""$(PACKAGE)"\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
echo "(alexandria:define-constant +program-version+" >> $(CONF_PATH_FILE);
|
||||
echo -e "\""$(VERSION)"\" :test #'string=)\n" >> $(CONF_PATH_FILE);
|
||||
|
||||
cat $(CONF_PATH_FILE).in | sed "\/^;;.*$\/d" >> $(CONF_PATH_FILE);
|
|
@ -0,0 +1,206 @@
|
|||
#+OPTIONS: html-postamble:nil html-preamble:nil
|
||||
#+AUTHOR:
|
||||
#+TITLE: tinmop
|
||||
|
||||
* Introduction
|
||||
|
||||
Tinmop is an opinionated client for Mastodon (or Pleroma using the
|
||||
mastodon API). It offer a distraction free terminal interface.
|
||||
|
||||
The name is a recursive acronym: "Tinmop Is Not Mutt or Pine". The
|
||||
older of us can remember that, in turn, Pine is an acronym as well:
|
||||
"Pine Is Not Elm" and, finally, Elm means (according to Wikipedia):
|
||||
"Electronic Mail".
|
||||
|
||||
* Peculiar Features
|
||||
|
||||
- tree structure of messages;
|
||||
- subscriptions of hashtag;
|
||||
- encrypted direct message (but see [[FAQ]]);
|
||||
- no mentions notification, no knowlege of when or who favourited
|
||||
your status;
|
||||
- No blocking or muting, the client can be configured to ignore
|
||||
a list of accounts.
|
||||
|
||||
* Dependency
|
||||
|
||||
** Programs
|
||||
|
||||
+ for running the program:
|
||||
- SBCL compiler;
|
||||
- xdg-open;
|
||||
- your favourite editor (default: nano).
|
||||
|
||||
+ to install the package, including running the script to install
|
||||
lisp libraries (~quick_quicklisp.sh~):
|
||||
- GNU AWK (Gawk);
|
||||
- BASH shell.
|
||||
|
||||
** Lisp Libraries
|
||||
|
||||
- alexandria;
|
||||
- cl-ppcre;
|
||||
- tooter;
|
||||
- croatoan;
|
||||
- osicat;
|
||||
- cl-spark;
|
||||
- access;
|
||||
- sqlite;
|
||||
- sxql;
|
||||
- sxql-composer;
|
||||
- marshal;
|
||||
- bordeaux-threads;
|
||||
- log4cl;
|
||||
- local-time;
|
||||
- cl-colors2;
|
||||
- cl-i18n;
|
||||
- clunit2;
|
||||
- esrap;
|
||||
- ieee-floats;
|
||||
- parse-number;
|
||||
- cl-html5-parser;
|
||||
- unix-opts;
|
||||
- crypto-shortcuts;
|
||||
- drakma.
|
||||
|
||||
** Foreign (C language) library
|
||||
|
||||
- libssl
|
||||
|
||||
* Install
|
||||
|
||||
1. optional step needed only if you have not already the configure script,
|
||||
you will need ~autotools~ for that.
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ autoreconf -fiv
|
||||
#+END_SRC
|
||||
|
||||
2. run ~configure~ and resolve the missing dependencies (if any)
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ ./configure
|
||||
#+END_SRC
|
||||
|
||||
3. the script ~quick-quicklisp.sh~ will download and install the library manager and the
|
||||
library on your home dir.
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ ./quick_quicklisp.sh
|
||||
#+END_SRC
|
||||
|
||||
This step is optional if you have already installed quicklisp, in
|
||||
this case just load the [[Dependency][dependencies]]
|
||||
using the client installed on your computer.
|
||||
|
||||
4. clone in ~$HOME/quicklisp/local-projects/~ the latest version of
|
||||
tooter, a library to access mastodon API.
|
||||
|
||||
This step is temporary as this version will get into quicklisp eventually.
|
||||
#+BEGIN_SRC sh
|
||||
$ cd $HOME/quicklisp/local-projects/
|
||||
$ git clone https://github.com/Shinmera/tooter.git
|
||||
#+END_SRC
|
||||
|
||||
5. build the executable:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ make
|
||||
#+END_SRC
|
||||
|
||||
6. install on your system:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ make install
|
||||
#+END_SRC
|
||||
|
||||
7. run the software!
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ tinmop
|
||||
#+END_SRC
|
||||
|
||||
* Usage
|
||||
|
||||
See the command line options:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ tinmop -h
|
||||
#+END_SRC
|
||||
|
||||
To get instruction about configuration:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
$ man tinmop
|
||||
#+END_SRC
|
||||
|
||||
* BUGS
|
||||
|
||||
Please file bug reports on the
|
||||
[[https://notabug.org/cage/tinmop/][notabug repository]].
|
||||
|
||||
* Translations
|
||||
|
||||
Only Italian translation is regularly updated.
|
||||
|
||||
* License
|
||||
|
||||
This program is released under GNU General Public license version 3
|
||||
or later (see COPYING file).
|
||||
|
||||
The program use data and code from other sources, please see
|
||||
LICENSES.org for credits.
|
||||
|
||||
Although any efforts has been put to make the list of credits
|
||||
exhaustive, errors are always possible. Please send correction to
|
||||
cage-dev at twistfold dot it.
|
||||
|
||||
* Privacy
|
||||
|
||||
This software does collect nothing from its users in places
|
||||
different from their local computer.
|
||||
|
||||
But launching ~quick_quicklisp.sh~ will contact
|
||||
[[https://www.quicklisp.org/]], check the
|
||||
[[https://beta.quicklisp.org/quicklisp.lisp][quicklisp sources]] for
|
||||
details.
|
||||
|
||||
* Contributing
|
||||
|
||||
Any help is appreciated. If you intend to contribute please point
|
||||
your browser to the
|
||||
[[https://notabug.org/cage/tinmop/issues][issue tracker]] or file a
|
||||
[[https://notabug.org/cage/tinmop/pulls][pull request]].
|
||||
|
||||
But, please take a minute to read the file [[./CONTRIBUTING.org]]
|
||||
|
||||
* FAQ
|
||||
- are the encrypted messages secure?
|
||||
|
||||
No. First only a symmetric encryption scheme is implemented (so
|
||||
there is a problem of secure key exchanging). Moreover i am not a
|
||||
crypto expert and probably i made something wrong. Note that i am
|
||||
not claiming that the algorithm (AES256) or the implementation of
|
||||
such encrypting algorithm is flawed but that, likely, is flawed
|
||||
the code i wrote to use the crypto library in this software.
|
||||
|
||||
So, please do not consider the encrypted message secure at all.
|
||||
|
||||
* NO WARRANTY
|
||||
|
||||
tinmop: an humble mastodon client
|
||||
Copyright (C) 2020 cage
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program.
|
||||
If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
|
@ -0,0 +1,271 @@
|
|||
━━━━━━━━
|
||||
TINMOP
|
||||
━━━━━━━━
|
||||
|
||||
|
||||
Table of Contents
|
||||
─────────────────
|
||||
|
||||
1. Introduction
|
||||
2. Peculiar Features
|
||||
3. Dependency
|
||||
.. 1. Programs
|
||||
.. 2. Lisp Libraries
|
||||
.. 3. Foreign (C language) library
|
||||
4. Install
|
||||
5. Usage
|
||||
6. BUGS
|
||||
7. Translations
|
||||
8. License
|
||||
9. Privacy
|
||||
10. Contributing
|
||||
11. FAQ
|
||||
12. NO WARRANTY
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1 Introduction
|
||||
══════════════
|
||||
|
||||
Tinmop is an opinionated client for Mastodon (or Pleroma using the
|
||||
mastodon API). It offer a distraction free terminal interface.
|
||||
|
||||
The name is a recursive acronym: "Tinmop Is Not Mutt or Pine". The
|
||||
older of us can remember that, in turn, Pine is an acronym as well:
|
||||
"Pine Is Not Elm" and, finally, Elm means (according to Wikipedia):
|
||||
"Electronic Mail".
|
||||
|
||||
|
||||
2 Peculiar Features
|
||||
═══════════════════
|
||||
|
||||
• tree structure of messages;
|
||||
• subscriptions of hashtag;
|
||||
• encrypted direct message (but see 11);
|
||||
• no mentions notification, no knowlege of when or who favourited your
|
||||
status;
|
||||
• No blocking or muting, the client can be configured to ignore a list
|
||||
of accounts.
|
||||
|
||||
|
||||
3 Dependency
|
||||
════════════
|
||||
|
||||
3.1 Programs
|
||||
────────────
|
||||
|
||||
⁃ for running the program:
|
||||
• SBCL compiler;
|
||||
• xdg-open;
|
||||
• your favourite editor (default: nano).
|
||||
|
||||
⁃ to install the package, including running the script to install lisp
|
||||
libraries (`quick_quicklisp.sh'):
|
||||
• GNU AWK (Gawk);
|
||||
• BASH shell.
|
||||
|
||||
|
||||
3.2 Lisp Libraries
|
||||
──────────────────
|
||||
|
||||
• alexandria;
|
||||
• cl-ppcre;
|
||||
• tooter;
|
||||
• croatoan;
|
||||
• osicat;
|
||||
• cl-spark;
|
||||
• access;
|
||||
• sqlite;
|
||||
• sxql;
|
||||
• sxql-composer;
|
||||
• marshal;
|
||||
• bordeaux-threads;
|
||||
• log4cl;
|
||||
• local-time;
|
||||
• cl-colors2;
|
||||
• cl-i18n;
|
||||
• clunit2;
|
||||
• esrap;
|
||||
• ieee-floats;
|
||||
• parse-number;
|
||||
• cl-html5-parser;
|
||||
• unix-opts;
|
||||
• crypto-shortcuts;
|
||||
• drakma.
|
||||
|
||||
|
||||
3.3 Foreign (C language) library
|
||||
────────────────────────────────
|
||||
|
||||
• libssl
|
||||
|
||||
|
||||
4 Install
|
||||
═════════
|
||||
|
||||
1. optional step needed only if you have not already the configure
|
||||
script, you will need `autotools' for that.
|
||||
|
||||
┌────
|
||||
│ $ autoreconf -fiv
|
||||
└────
|
||||
|
||||
2. run `configure' and resolve the missing dependencies (if any)
|
||||
|
||||
┌────
|
||||
│ $ ./configure
|
||||
└────
|
||||
|
||||
3. the script `quick-quicklisp.sh' will download and install the
|
||||
library manager and the library on your home dir.
|
||||
|
||||
┌────
|
||||
│ $ ./quick_quicklisp.sh
|
||||
└────
|
||||
|
||||
This step is optional if you have already installed quicklisp, in
|
||||
this case just load the [dependencies] using the client installed
|
||||
on your computer.
|
||||
|
||||
4. clone in `$HOME/quicklisp/local-projects/' the latest version of
|
||||
tooter, a library to access mastodon API.
|
||||
|
||||
This step is temporary as this version will get into quicklisp
|
||||
eventually.
|
||||
┌────
|
||||
│ $ cd $HOME/quicklisp/local-projects/
|
||||
│ $ git clone https://github.com/Shinmera/tooter.git
|
||||
└────
|
||||
|
||||
5. build the executable:
|
||||
|
||||
┌────
|
||||
│ $ make
|
||||
└────
|
||||
|
||||
6. install on your system:
|
||||
|
||||
┌────
|
||||
│ $ make install
|
||||
└────
|
||||
|
||||
7. run the software!
|
||||
|
||||
┌────
|
||||
│ $ tinmop
|
||||
└────
|
||||
|
||||
|
||||
[dependencies] See section 3
|
||||
|
||||
|
||||
5 Usage
|
||||
═══════
|
||||
|
||||
See the command line options:
|
||||
|
||||
┌────
|
||||
│ $ tinmop -h
|
||||
└────
|
||||
|
||||
To get instruction about configuration:
|
||||
|
||||
┌────
|
||||
│ $ man tinmop
|
||||
└────
|
||||
|
||||
|
||||
6 BUGS
|
||||
══════
|
||||
|
||||
Please file bug reports on the [notabug repository].
|
||||
|
||||
|
||||
[notabug repository] <https://notabug.org/cage/tinmop/>
|
||||
|
||||
|
||||
7 Translations
|
||||
══════════════
|
||||
|
||||
Only Italian translation is regularly updated.
|
||||
|
||||
|
||||
8 License
|
||||
═════════
|
||||
|
||||
This program is released under GNU General Public license version 3 or
|
||||
later (see COPYING file).
|
||||
|
||||
The program use data and code from other sources, please see
|
||||
LICENSES.org for credits.
|
||||
|
||||
Although any efforts has been put to make the list of credits
|
||||
exhaustive, errors are always possible. Please send correction to
|
||||
cage-dev at twistfold dot it.
|
||||
|
||||
|
||||
9 Privacy
|
||||
═════════
|
||||
|
||||
This software does collect nothing from its users in places different
|
||||
from their local computer.
|
||||
|
||||
But launching `quick_quicklisp.sh' will contact
|
||||
<https://www.quicklisp.org/>, check the [quicklisp sources] for
|
||||
details.
|
||||
|
||||
|
||||
[quicklisp sources] <https://beta.quicklisp.org/quicklisp.lisp>
|
||||
|
||||
|
||||
10 Contributing
|
||||
═══════════════
|
||||
|
||||
Any help is appreciated. If you intend to contribute please point your
|
||||
browser to the [issue tracker] or file a [pull request].
|
||||
|
||||
But, please take a minute to read the file <file:./CONTRIBUTING.org>
|
||||
|
||||
|
||||
[issue tracker] <https://notabug.org/cage/tinmop/issues>
|
||||
|
||||
[pull request] <https://notabug.org/cage/tinmop/pulls>
|
||||
|
||||
|
||||
11 FAQ
|
||||
══════
|
||||
|
||||
• are the encrypted messages secure?
|
||||
|
||||
No. First only a symmetric encryption scheme is implemented (so
|
||||
there is a problem of secure key exchanging). Moreover i am not a
|
||||
crypto expert and probably i made something wrong. Note that i am
|
||||
not claiming that the algorithm (AES256) or the implementation of
|
||||
such encrypting algorithm is flawed but that, likely, is flawed the
|
||||
code i wrote to use the crypto library in this software.
|
||||
|
||||
So, please do not consider the encrypted message secure at all.
|
||||
|
||||
|
||||
12 NO WARRANTY
|
||||
══════════════
|
||||
|
||||
tinmop: an humble mastodon client Copyright (C) 2020 cage
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
|
||||
[http://www.gnu.org/licenses/] <http://www.gnu.org/licenses/>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
# tinmop: an humble mastodon client
|
||||
# Copyright (C) 2020 cage
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program.
|
||||
# If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
BEGIN {
|
||||
TRUE = 1;
|
||||
FALSE = 0;
|
||||
VERSION_SEP = "\\.";
|
||||
}
|
||||
|
||||
function split_version_number (version, parsed) {
|
||||
split(version, parsed, VERSION_SEP);
|
||||
for (i in parsed) {
|
||||
parsed[i] = strtonum(parsed[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function version_less_than_p (version_a, version_b, idx) {
|
||||
for (idx=1; idx <= length(version_a); idx++){
|
||||
if(version_a[idx] < version_b[idx]){
|
||||
return TRUE;
|
||||
} else if(version_a[idx] > version_b[idx]){
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
// {
|
||||
versions[""]="";
|
||||
version_a[""]="";
|
||||
version_b[""]="";
|
||||
split($0, versions, "[[:space:]]+");
|
||||
split_version_number(versions[1], version_a);
|
||||
split_version_number(versions[2], version_b);
|
||||
print (version_less_than_p(version_a, version_b));
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
#! /bin/sh
|
||||
# Wrapper for compilers which do not understand '-c -o'.
|
||||
|
||||
scriptversion=2018-03-07.03; # UTC
|
||||
|
||||
# Copyright (C) 1999-2020 Free Software Foundation, Inc.
|
||||
# Written by Tom Tromey <tromey@cygnus.com>.
|
||||
#
|
||||
# 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, 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/>.
|
||||
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# This file is maintained in Automake, please report
|
||||
# bugs to <bug-automake@gnu.org> or send patches to
|
||||
# <automake-patches@gnu.org>.
|
||||
|
||||
nl='
|
||||
'
|
||||
|
||||
# We need space, tab and new line, in precisely that order. Quoting is
|
||||
# there to prevent tools from complaining about whitespace usage.
|
||||
IFS=" "" $nl"
|
||||
|
||||
file_conv=
|
||||
|
||||
# func_file_conv build_file lazy
|
||||
# Convert a $build file to $host form and store it in $file
|
||||
# Currently only supports Windows hosts. If the determined conversion
|
||||
# type is listed in (the comma separated) LAZY, no conversion will
|
||||
# take place.
|
||||
func_file_conv ()
|
||||
{
|
||||
file=$1
|
||||
case $file in
|
||||
/ | /[!/]*) # absolute file, and not a UNC file
|
||||
if test -z "$file_conv"; then
|
||||
# lazily determine how to convert abs files
|
||||
case `uname -s` in
|
||||
MINGW*)
|
||||
file_conv=mingw
|
||||
;;
|
||||
CYGWIN* | MSYS*)
|
||||
file_conv=cygwin
|
||||
;;
|
||||
*)
|
||||
file_conv=wine
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
case $file_conv/,$2, in
|
||||
*,$file_conv,*)
|
||||
;;
|
||||
mingw/*)
|
||||
file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
|
||||
;;
|
||||
cygwin/* | msys/*)
|
||||
file=`cygpath -m "$file" || echo "$file"`
|
||||
;;
|
||||
wine/*)
|
||||
file=`winepath -w "$file" || echo "$file"`
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# func_cl_dashL linkdir
|
||||
# Make cl look for libraries in LINKDIR
|
||||
func_cl_dashL ()
|
||||
{
|
||||
func_file_conv "$1"
|
||||
if test -z "$lib_path"; then
|
||||
lib_path=$file
|
||||
else
|
||||
lib_path="$lib_path;$file"
|
||||
fi
|
||||
linker_opts="$linker_opts -LIBPATH:$file"
|
||||
}
|
||||
|
||||
# func_cl_dashl library
|
||||
# Do a library search-path lookup for cl
|
||||
func_cl_dashl ()
|
||||
{
|
||||
lib=$1
|
||||
found=no
|
||||
save_IFS=$IFS
|
||||
IFS=';'
|
||||
for dir in $lib_path $LIB
|
||||
do
|
||||
IFS=$save_IFS
|
||||
if $shared && test -f "$dir/$lib.dll.lib"; then
|
||||
found=yes
|
||||
lib=$dir/$lib.dll.lib
|
||||
break
|
||||
fi
|
||||
if test -f "$dir/$lib.lib"; then
|
||||
found=yes
|
||||
lib=$dir/$lib.lib
|
||||
break
|
||||
fi
|
||||
if test -f "$dir/lib$lib.a"; then
|
||||
found=yes
|
||||
lib=$dir/lib$lib.a
|
||||
break
|
||||
fi
|
||||
done
|
||||
IFS=$save_IFS
|
||||
|
||||
if test "$found" != yes; then
|
||||
lib=$lib.lib
|
||||
fi
|
||||
}
|
||||
|
||||
# func_cl_wrapper cl arg...
|
||||
# Adjust compile command to suit cl
|
||||
func_cl_wrapper ()
|
||||
{
|
||||
# Assume a capable shell
|
||||
lib_path=
|
||||
shared=:
|
||||
linker_opts=
|
||||
for arg
|
||||
do
|
||||
if test -n "$eat"; then
|
||||
eat=
|
||||
else
|
||||
case $1 in
|
||||
-o)
|
||||
# configure might choose to run compile as 'compile cc -o foo foo.c'.
|
||||
eat=1
|
||||
case $2 in
|
||||
*.o | *.[oO][bB][jJ])
|
||||
func_file_conv "$2"
|
||||
set x "$@" -Fo"$file"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
func_file_conv "$2"
|
||||
set x "$@" -Fe"$file"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
-I)
|
||||
eat=1
|
||||
func_file_conv "$2" mingw
|
||||
set x "$@" -I"$file"
|
||||
shift
|
||||
;;
|
||||
-I*)
|
||||
func_file_conv "${1#-I}" mingw
|
||||
set x "$@" -I"$file"
|
||||
shift
|
||||
;;
|
||||
-l)
|
||||
eat=1
|
||||
func_cl_dashl "$2"
|
||||
set x "$@" "$lib"
|
||||
shift
|
||||
;;
|
||||
-l*)
|
||||
func_cl_dashl "${1#-l}"
|
||||
set x "$@" "$lib"
|
||||
shift
|
||||
;;
|
||||
-L)
|
||||
eat=1
|
||||
func_cl_dashL "$2"
|
||||
;;
|
||||
-L*)
|
||||
func_cl_dashL "${1#-L}"
|
||||
;;
|
||||
-static)
|
||||
shared=false
|
||||
;;
|
||||
-Wl,*)
|
||||
arg=${1#-Wl,}
|
||||
save_ifs="$IFS"; IFS=','
|
||||
for flag in $arg; do
|
||||
IFS="$save_ifs"
|
||||
linker_opts="$linker_opts $flag"
|
||||
done
|
||||
IFS="$save_ifs"
|
||||
;;
|
||||
-Xlinker)
|
||||
eat=1
|
||||
linker_opts="$linker_opts $2"
|
||||
;;
|
||||
-*)
|
||||
set x "$@" "$1"
|
||||
shift
|
||||
;;
|
||||
*.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
|
||||
func_file_conv "$1"
|
||||
set x "$@" -Tp"$file"
|
||||
shift
|
||||
;;
|
||||
*.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
|
||||
func_file_conv "$1" mingw
|
||||
set x "$@" "$file"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
set x "$@" "$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if test -n "$linker_opts"; then
|
||||
linker_opts="-link$linker_opts"
|
||||
fi
|
||||
exec "$@" $linker_opts
|
||||
exit 1
|
||||
}
|
||||
|
||||
eat=
|
||||
|
||||
case $1 in
|
||||
'')
|
||||
echo "$0: No command. Try '$0 --help' for more information." 1>&2
|
||||
exit 1;
|
||||
;;
|
||||
-h | --h*)
|
||||
cat <<\EOF
|
||||
Usage: compile [--help] [--version] PROGRAM [ARGS]
|
||||
|
||||
Wrapper for compilers which do not understand '-c -o'.
|
||||
Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
|
||||
arguments, and rename the output as expected.
|
||||
|
||||
If you are trying to build a whole package this is not the
|
||||
right script to run: please start by reading the file 'INSTALL'.
|
||||
|
||||
Report bugs to <bug-automake@gnu.org>.
|
||||
EOF
|
||||
exit $?
|
||||
;;
|
||||
-v | --v*)
|
||||
echo "compile $scriptversion"
|
||||
exit $?
|
||||
;;
|
||||
cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \
|
||||
icl | *[/\\]icl | icl.exe | *[/\\]icl.exe )
|
||||
func_cl_wrapper "$@" # Doesn't return...
|
||||
;;
|
||||
esac
|
||||
|
||||
ofile=
|
||||
cfile=
|
||||
|
||||
for arg
|
||||
do
|
||||
if test -n "$eat"; then
|
||||
eat=
|
||||
else
|
||||
case $1 in
|
||||
-o)
|
||||
# configure might choose to run compile as 'compile cc -o foo foo.c'.
|
||||
# So we strip '-o arg' only if arg is an object.
|
||||
eat=1
|
||||
case $2 in
|
||||
*.o | *.obj)
|
||||
ofile=$2
|
||||
;;
|
||||
*)
|
||||
set x "$@" -o "$2"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*.c)
|
||||
cfile=$1
|
||||
set x "$@" "$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
set x "$@" "$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$ofile" || test -z "$cfile"; then
|
||||
# If no '-o' option was seen then we might have been invoked from a
|
||||
# pattern rule where we don't need one. That is ok -- this is a
|
||||
# normal compilation that the losing compiler can handle. If no
|
||||
# '.c' file was seen then we are probably linking. That is also
|
||||
# ok.
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
# Name of file we expect compiler to create.
|
||||
cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
|
||||
|
||||
# Create the lock directory.
|
||||
# Note: use '[/\\:.-]' here to ensure that we don't use the same name
|
||||
# that we are using for the .o file. Also, base the name on the expected
|
||||
# object file name, since that is what matters with a parallel build.
|
||||
lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
|
||||
while true; do
|
||||
if mkdir "$lockdir" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
# FIXME: race condition here if user kills between mkdir and trap.
|
||||
trap "rmdir '$lockdir'; exit 1" 1 2 15
|
||||
|
||||
# Run the compile.
|
||||
"$@"
|
||||
ret=$?
|
||||
|
||||
if test -f "$cofile"; then
|
||||
test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
|
||||
elif test -f "${cofile}bj"; then
|
||||
test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
|
||||
fi
|
||||
|
||||
rmdir "$lockdir"
|
||||
exit $ret
|
||||
|
||||
# Local Variables:
|
||||
# mode: shell-script
|
||||
# sh-indentation: 2
|
||||
# eval: (add-hook 'before-save-hook 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC0"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,684 @@
|
|||
#! /bin/sh
|
||||
# Output a system dependent set of variables, describing how to set the
|
||||
# run time search path of shared libraries in an executable.
|
||||
#
|
||||
# Copyright 1996-2016 Free Software Foundation, Inc.
|
||||
# Taken from GNU libtool, 2001
|
||||
# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
|
||||
#
|
||||
# This file is free software; the Free Software Foundation gives
|
||||
# unlimited permission to copy and/or distribute it, with or without
|
||||
# modifications, as long as this notice is preserved.
|
||||
#
|
||||
# The first argument passed to this file is the canonical host specification,
|
||||
# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
|
||||
# or
|
||||
# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
|
||||
# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld
|
||||
# should be set by the caller.
|
||||
#
|
||||
# The set of defined variables is at the end of this script.
|
||||
|
||||
# Known limitations:
|
||||
# - On IRIX 6.5 with CC="cc", the run time search patch must not be longer
|
||||
# than 256 bytes, otherwise the compiler driver will dump core. The only
|
||||
# known workaround is to choose shorter directory names for the build
|
||||
# directory and/or the installation directory.
|
||||
|
||||
# All known linkers require a '.a' archive for static linking (except MSVC,
|
||||
# which needs '.lib').
|
||||
libext=a
|
||||
shrext=.so
|
||||
|
||||
host="$1"
|
||||
host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
|
||||
host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
|
||||
host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
|
||||
|
||||
# Code taken from libtool.m4's _LT_CC_BASENAME.
|
||||
|
||||
for cc_temp in $CC""; do
|
||||
case $cc_temp in
|
||||
compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
|
||||
distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
|
||||
\-*) ;;
|
||||
*) break;;
|
||||
esac
|
||||
done
|
||||
cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'`
|
||||
|
||||
# Code taken from libtool.m4's _LT_COMPILER_PIC.
|
||||
|
||||
wl=
|
||||
if test "$GCC" = yes; then
|
||||
wl='-Wl,'
|
||||
else
|
||||
case "$host_os" in
|
||||
aix*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
mingw* | cygwin* | pw32* | os2* | cegcc*)
|
||||
;;
|
||||
hpux9* | hpux10* | hpux11*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
irix5* | irix6* | nonstopux*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
linux* | k*bsd*-gnu | kopensolaris*-gnu)
|
||||
case $cc_basename in
|
||||
ecc*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
icc* | ifort*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
lf95*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
nagfor*)
|
||||
wl='-Wl,-Wl,,'
|
||||
;;
|
||||
pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
ccc*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
xl* | bgxl* | bgf* | mpixl*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
como)
|
||||
wl='-lopt='
|
||||
;;
|
||||
*)
|
||||
case `$CC -V 2>&1 | sed 5q` in
|
||||
*Sun\ F* | *Sun*Fortran*)
|
||||
wl=
|
||||
;;
|
||||
*Sun\ C*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
newsos6)
|
||||
;;
|
||||
*nto* | *qnx*)
|
||||
;;
|
||||
osf3* | osf4* | osf5*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
rdos*)
|
||||
;;
|
||||
solaris*)
|
||||
case $cc_basename in
|
||||
f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
|
||||
wl='-Qoption ld '
|
||||
;;
|
||||
*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
sunos4*)
|
||||
wl='-Qoption ld '
|
||||
;;
|
||||
sysv4 | sysv4.2uw2* | sysv4.3*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
sysv4*MP*)
|
||||
;;
|
||||
sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
unicos*)
|
||||
wl='-Wl,'
|
||||
;;
|
||||
uts4*)
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Code taken from libtool.m4's _LT_LINKER_SHLIBS.
|
||||
|
||||
hardcode_libdir_flag_spec=
|
||||
hardcode_libdir_separator=
|
||||
hardcode_direct=no
|
||||
hardcode_minus_L=no
|
||||
|
||||
case "$host_os" in
|
||||
cygwin* | mingw* | pw32* | cegcc*)
|
||||
# FIXME: the MSVC++ port hasn't been tested in a loooong time
|
||||
# When not using gcc, we currently assume that we are using
|
||||
# Microsoft Visual C++.
|
||||
if test "$GCC" != yes; then
|
||||
with_gnu_ld=no
|
||||
fi
|
||||
;;
|
||||
interix*)
|
||||
# we just hope/assume this is gcc and not c89 (= MSVC++)
|
||||
with_gnu_ld=yes
|
||||
;;
|
||||
openbsd*)
|
||||
with_gnu_ld=no
|
||||
;;
|
||||
esac
|
||||
|
||||
ld_shlibs=yes
|
||||
if test "$with_gnu_ld" = yes; then
|
||||
# Set some defaults for GNU ld with shared library support. These
|
||||
# are reset later if shared libraries are not supported. Putting them
|
||||
# here allows them to be overridden if necessary.
|
||||
# Unlike libtool, we use -rpath here, not --rpath, since the documented
|
||||
# option of GNU ld is called -rpath, not --rpath.
|
||||
hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
|
||||
case "$host_os" in
|
||||
aix[3-9]*)
|
||||
# On AIX/PPC, the GNU linker is very broken
|
||||
if test "$host_cpu" != ia64; then
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
amigaos*)
|
||||
case "$host_cpu" in
|
||||
powerpc)
|
||||
;;
|
||||
m68k)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
beos*)
|
||||
if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
cygwin* | mingw* | pw32* | cegcc*)
|
||||
# hardcode_libdir_flag_spec is actually meaningless, as there is
|
||||
# no search path for DLLs.
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
haiku*)
|
||||
;;
|
||||
interix[3-9]*)
|
||||
hardcode_direct=no
|
||||
hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
|
||||
;;
|
||||
gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
|
||||
if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
netbsd*)
|
||||
;;
|
||||
solaris*)
|
||||
if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then
|
||||
ld_shlibs=no
|
||||
elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
|
||||
case `$LD -v 2>&1` in
|
||||
*\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
|
||||
ld_shlibs=no
|
||||
;;
|
||||
*)
|
||||
if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
|
||||
hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`'
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
sunos4*)
|
||||
hardcode_direct=yes
|
||||
;;
|
||||
*)
|
||||
if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
if test "$ld_shlibs" = no; then
|
||||
hardcode_libdir_flag_spec=
|
||||
fi
|
||||
else
|
||||
case "$host_os" in
|
||||
aix3*)
|
||||
# Note: this linker hardcodes the directories in LIBPATH if there
|
||||
# are no directories specified by -L.
|
||||
hardcode_minus_L=yes
|
||||
if test "$GCC" = yes; then
|
||||
# Neither direct hardcoding nor static linking is supported with a
|
||||
# broken collect2.
|
||||
hardcode_direct=unsupported
|
||||
fi
|
||||
;;
|
||||
aix[4-9]*)
|
||||
if test "$host_cpu" = ia64; then
|
||||
# On IA64, the linker does run time linking by default, so we don't
|
||||
# have to do anything special.
|
||||
aix_use_runtimelinking=no
|
||||
else
|
||||
aix_use_runtimelinking=no
|
||||
# Test if we are trying to use run time linking or normal
|
||||
# AIX style linking. If -brtl is somewhere in LDFLAGS, we
|
||||
# need to do runtime linking.
|
||||
case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
|
||||
for ld_flag in $LDFLAGS; do
|
||||
if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
|
||||
aix_use_runtimelinking=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
hardcode_direct=yes
|
||||
hardcode_libdir_separator=':'
|
||||
if test "$GCC" = yes; then
|
||||
case $host_os in aix4.[012]|aix4.[012].*)
|
||||
collect2name=`${CC} -print-prog-name=collect2`
|
||||
if test -f "$collect2name" && \
|
||||
strings "$collect2name" | grep resolve_lib_name >/dev/null
|
||||
then
|
||||
# We have reworked collect2
|
||||
:
|
||||
else
|
||||
# We have old collect2
|
||||
hardcode_direct=unsupported
|
||||
hardcode_minus_L=yes
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
hardcode_libdir_separator=
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
# Begin _LT_AC_SYS_LIBPATH_AIX.
|
||||
echo 'int main () { return 0; }' > conftest.c
|
||||
${CC} ${LDFLAGS} conftest.c -o conftest
|
||||
aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; }
|
||||
}'`
|
||||
if test -z "$aix_libpath"; then
|
||||
aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; }
|
||||
}'`
|
||||
fi
|
||||
if test -z "$aix_libpath"; then
|
||||
aix_libpath="/usr/lib:/lib"
|
||||
fi
|
||||
rm -f conftest.c conftest
|
||||
# End _LT_AC_SYS_LIBPATH_AIX.
|
||||
if test "$aix_use_runtimelinking" = yes; then
|
||||
hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
|
||||
else
|
||||
if test "$host_cpu" = ia64; then
|
||||
hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
|
||||
else
|
||||
hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
amigaos*)
|
||||
case "$host_cpu" in
|
||||
powerpc)
|
||||
;;
|
||||
m68k)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
bsdi[45]*)
|
||||
;;
|
||||
cygwin* | mingw* | pw32* | cegcc*)
|
||||
# When not using gcc, we currently assume that we are using
|
||||
# Microsoft Visual C++.
|
||||
# hardcode_libdir_flag_spec is actually meaningless, as there is
|
||||
# no search path for DLLs.
|
||||
hardcode_libdir_flag_spec=' '
|
||||
libext=lib
|
||||
;;
|
||||
darwin* | rhapsody*)
|
||||
hardcode_direct=no
|
||||
if { case $cc_basename in ifort*) true;; *) test "$GCC" = yes;; esac; }; then
|
||||
:
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
dgux*)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
;;
|
||||
freebsd2.[01]*)
|
||||
hardcode_direct=yes
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
freebsd* | dragonfly*)
|
||||
hardcode_libdir_flag_spec='-R$libdir'
|
||||
hardcode_direct=yes
|
||||
;;
|
||||
hpux9*)
|
||||
hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
hardcode_direct=yes
|
||||
# hardcode_minus_L: Not really in the search PATH,
|
||||
# but as the default location of the library.
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
hpux10*)
|
||||
if test "$with_gnu_ld" = no; then
|
||||
hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
hardcode_direct=yes
|
||||
# hardcode_minus_L: Not really in the search PATH,
|
||||
# but as the default location of the library.
|
||||
hardcode_minus_L=yes
|
||||
fi
|
||||
;;
|
||||
hpux11*)
|
||||
if test "$with_gnu_ld" = no; then
|
||||
hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
case $host_cpu in
|
||||
hppa*64*|ia64*)
|
||||
hardcode_direct=no
|
||||
;;
|
||||
*)
|
||||
hardcode_direct=yes
|
||||
# hardcode_minus_L: Not really in the search PATH,
|
||||
# but as the default location of the library.
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
irix5* | irix6* | nonstopux*)
|
||||
hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
;;
|
||||
netbsd*)
|
||||
hardcode_libdir_flag_spec='-R$libdir'
|
||||
hardcode_direct=yes
|
||||
;;
|
||||
newsos6)
|
||||
hardcode_direct=yes
|
||||
hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
;;
|
||||
*nto* | *qnx*)
|
||||
;;
|
||||
openbsd*)
|
||||
if test -f /usr/libexec/ld.so; then
|
||||
hardcode_direct=yes
|
||||
if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
|
||||
hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
|
||||
else
|
||||
case "$host_os" in
|
||||
openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
|
||||
hardcode_libdir_flag_spec='-R$libdir'
|
||||
;;
|
||||
*)
|
||||
hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
ld_shlibs=no
|
||||
fi
|
||||
;;
|
||||
os2*)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
osf3*)
|
||||
hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
|
||||
hardcode_libdir_separator=:
|
||||
;;
|
||||
osf4* | osf5*)
|
||||
if test "$GCC" = yes; then
|
||||
hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
|
||||
else
|
||||
# Both cc and cxx compiler support -rpath directly
|
||||
hardcode_libdir_flag_spec='-rpath $libdir'
|
||||
fi
|
||||
hardcode_libdir_separator=:
|
||||
;;
|
||||
solaris*)
|
||||
hardcode_libdir_flag_spec='-R$libdir'
|
||||
;;
|
||||
sunos4*)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
hardcode_direct=yes
|
||||
hardcode_minus_L=yes
|
||||
;;
|
||||
sysv4)
|
||||
case $host_vendor in
|
||||
sni)
|
||||
hardcode_direct=yes # is this really true???
|
||||
;;
|
||||
siemens)
|
||||
hardcode_direct=no
|
||||
;;
|
||||
motorola)
|
||||
hardcode_direct=no #Motorola manual says yes, but my tests say they lie
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
sysv4.3*)
|
||||
;;
|
||||
sysv4*MP*)
|
||||
if test -d /usr/nec; then
|
||||
ld_shlibs=yes
|
||||
fi
|
||||
;;
|
||||
sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
|
||||
;;
|
||||
sysv5* | sco3.2v5* | sco5v6*)
|
||||
hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`'
|
||||
hardcode_libdir_separator=':'
|
||||
;;
|
||||
uts4*)
|
||||
hardcode_libdir_flag_spec='-L$libdir'
|
||||
;;
|
||||
*)
|
||||
ld_shlibs=no
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Check dynamic linker characteristics
|
||||
# Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER.
|
||||
# Unlike libtool.m4, here we don't care about _all_ names of the library, but
|
||||
# only about the one the linker finds when passed -lNAME. This is the last
|
||||
# element of library_names_spec in libtool.m4, or possibly two of them if the
|
||||
# linker has special search rules.
|
||||
library_names_spec= # the last element of library_names_spec in libtool.m4
|
||||
libname_spec='lib$name'
|
||||
case "$host_os" in
|
||||
aix3*)
|
||||
library_names_spec='$libname.a'
|
||||
;;
|
||||
aix[4-9]*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
amigaos*)
|
||||
case "$host_cpu" in
|
||||
powerpc*)
|
||||
library_names_spec='$libname$shrext' ;;
|
||||
m68k)
|
||||
library_names_spec='$libname.a' ;;
|
||||
esac
|
||||
;;
|
||||
beos*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
bsdi[45]*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
cygwin* | mingw* | pw32* | cegcc*)
|
||||
shrext=.dll
|
||||
library_names_spec='$libname.dll.a $libname.lib'
|
||||
;;
|
||||
darwin* | rhapsody*)
|
||||
shrext=.dylib
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
dgux*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
freebsd[23].*)
|
||||
library_names_spec='$libname$shrext$versuffix'
|
||||
;;
|
||||
freebsd* | dragonfly*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
gnu*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
haiku*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
hpux9* | hpux10* | hpux11*)
|
||||
case $host_cpu in
|
||||
ia64*)
|
||||
shrext=.so
|
||||
;;
|
||||
hppa*64*)
|
||||
shrext=.sl
|
||||
;;
|
||||
*)
|
||||
shrext=.sl
|
||||
;;
|
||||
esac
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
interix[3-9]*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
irix5* | irix6* | nonstopux*)
|
||||
library_names_spec='$libname$shrext'
|
||||
case "$host_os" in
|
||||
irix5* | nonstopux*)
|
||||
libsuff= shlibsuff=
|
||||
;;
|
||||
*)
|
||||
case $LD in
|
||||
*-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;;
|
||||
*-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;;
|
||||
*-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;;
|
||||
*) libsuff= shlibsuff= ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
linux*oldld* | linux*aout* | linux*coff*)
|
||||
;;
|
||||
linux* | k*bsd*-gnu | kopensolaris*-gnu)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
knetbsd*-gnu)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
netbsd*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
newsos6)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
*nto* | *qnx*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
openbsd*)
|
||||
library_names_spec='$libname$shrext$versuffix'
|
||||
;;
|
||||
os2*)
|
||||
libname_spec='$name'
|
||||
shrext=.dll
|
||||
library_names_spec='$libname.a'
|
||||
;;
|
||||
osf3* | osf4* | osf5*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
rdos*)
|
||||
;;
|
||||
solaris*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
sunos4*)
|
||||
library_names_spec='$libname$shrext$versuffix'
|
||||
;;
|
||||
sysv4 | sysv4.3*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
sysv4*MP*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
tpf*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
uts4*)
|
||||
library_names_spec='$libname$shrext'
|
||||
;;
|
||||
esac
|
||||
|
||||
sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
|
||||
escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"`
|
||||
shlibext=`echo "$shrext" | sed -e 's,^\.,,'`
|
||||
escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
|
||||
escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
|
||||
escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"`
|
||||
|
||||
LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' <<EOF
|
||||
|
||||
# How to pass a linker flag through the compiler.
|
||||
wl="$escaped_wl"
|
||||
|
||||
# Static library suffix (normally "a").
|
||||
libext="$libext"
|
||||
|
||||
# Shared library suffix (normally "so").
|
||||
shlibext="$shlibext"
|
||||
|
||||
# Format of library name prefix.
|
||||
libname_spec="$escaped_libname_spec"
|
||||
|
||||
# Library names that the linker finds when passed -lNAME.
|
||||
library_names_spec="$escaped_library_names_spec"
|
||||
|
||||
# Flag to hardcode \$libdir into a binary during linking.
|
||||
# This must work even if \$libdir does not exist.
|
||||
hardcode_libdir_flag_spec="$escaped_hardcode_libdir_flag_spec"
|
||||
|
||||
# Whether we need a single -rpath flag with a separated argument.
|
||||
hardcode_libdir_separator="$hardcode_libdir_separator"
|
||||
|
||||
# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the
|
||||
# resulting binary.
|
||||
hardcode_direct="$hardcode_direct"
|
||||
|
||||
# Set to yes if using the -LDIR flag during linking hardcodes DIR into the
|
||||
# resulting binary.
|
||||
hardcode_minus_L="$hardcode_minus_L"
|
||||
|
||||
EOF
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,79 @@
|
|||
dnl tinmop: an humble mastodon client
|
||||
dnl Copyright (C) 2020 cage
|
||||
|
||||
dnl This program is free software: you can redistribute it and/or modify
|
||||
dnl it under the terms of the GNU General Public License as published by
|
||||
dnl the Free Software Foundation, either version 3 of the License, or
|
||||
dnl (at your option) any later version.
|
||||
|
||||
dnl This program is distributed in the hope that it will be useful,
|
||||
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
dnl GNU General Public License for more details.
|
||||
|
||||
dnl You should have received a copy of the GNU General Public License
|
||||
dnl along with this program.
|
||||
dnl If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
AC_INIT([tinmop],[0.0.1],[https://notabug.org/cage/tinmop/],[tinmop])
|
||||
|
||||
AM_INIT_AUTOMAKE([-Wall foreign])
|
||||
|
||||
AM_GNU_GETTEXT([external])
|
||||
|
||||
AC_PATH_PROG([LISP_COMPILER],[sbcl],[no])
|
||||
|
||||
if test "$LISP_COMPILER" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find SBCL, Common Lisp compiler.])
|
||||
fi
|
||||
|
||||
AC_PATH_PROG([CURL],[curl],[no])
|
||||
|
||||
if test "$CURL" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find curl.])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_PATH_PROG([GPG],[gpg],[no])
|
||||
|
||||
if test "$GPG" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find gpg, crypto software.])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_PATH_PROG([BASH],[bash],[no])
|
||||
|
||||
if test "$BASH" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find bash shell.])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_PATH_PROG([GAWK],[gawk],[no])
|
||||
|
||||
if test "$GAWK" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find GNU AWK (gawk).])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_PATH_PROG([XDG_OPEN],[xdg-open],[no])
|
||||
|
||||
if test "$GAWK" = "no" ; then
|
||||
AC_MSG_ERROR([Can not find xdg-open.])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_PROG_MKDIR_P
|
||||
|
||||
dnl check sbcl version
|
||||
SBCL_MIN_VERSION="1.5.9";
|
||||
SBCL_VERSION=`${LISP_COMPILER} --version | ${GAWK} -- '// {print $2}'`
|
||||
SBCL_VERSION_OK=`echo "$SBCL_VERSION $SBCL_MIN_VERSION" | awk -f compare_version.awk`
|
||||
|
||||
if test "$SBCL_VERSION_OK" = "1" ; then
|
||||
AC_MSG_ERROR([Lisp compiler too old, $SBCL_MIN_VERSION is the oldest supported.])
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
AC_CONFIG_FILES([Makefile quick_quicklisp.sh po/Makefile.in])
|
||||
|
||||
AC_OUTPUT
|
|
@ -0,0 +1,213 @@
|
|||
#+TITLE: tinmop
|
||||
|
||||
* Name
|
||||
tinmop - a client for mastodon or pleroma social network
|
||||
|
||||
* Synopsis
|
||||
tinmop [OPTION]...
|
||||
|
||||
* Description
|
||||
|
||||
This document assumes basic knowledge of how mastodon works. More
|
||||
information about this topic can be found on the
|
||||
official website ([[https://docs.joinmastodon.org/]]).
|
||||
|
||||
Tinmop propose a terminal interface to to connect with Mastodon or
|
||||
Pleroma social network
|
||||
|
||||
* Options
|
||||
Without options the program will start a terminal interface and will
|
||||
try to connect to your instance (see [[Configuration]])
|
||||
|
||||
+ -e, --execute-script SCRIPT-FILE :: Execute a script file
|
||||
+ -c, --check-follows-requests :: Checks for follow request at start
|
||||
+ -u, --update-timeline :: Update the selected timeline
|
||||
+ -t, --timeline TIMELINE-NAME :: Start using this timeline
|
||||
+ -f, --folder FOLDER-NAME :: Start on that folder
|
||||
+ -v, --version :: Print program version and exit
|
||||
+ -h, --help :: print program help and exit
|
||||
|
||||
* Usage
|
||||
|
||||
Users of Tinmop are expected to interacts with the social network
|
||||
using a terminal interface (TUI), The terminal screen layout is
|
||||
sketched below:
|
||||
|
||||
#+NAME: screen-layout
|
||||
#+BEGIN_SRC text
|
||||
+---------------+---------------------------+
|
||||
| | |
|
||||
| tags window | thread windows |
|
||||
| | |
|
||||
| | modeline |
|
||||
+---------------+---------------------------+
|
||||
| | |
|
||||
| conversations | message window |
|
||||
| window | |
|
||||
| | |
|
||||
| | |
|
||||
+---------------+---------------------------+
|
||||
| command window |
|
||||
+-------------------------------------------+
|
||||
|
||||
#+END_SRC
|
||||
|
||||
The screen is subdivided in five window:
|
||||
|
||||
- tag window :: shows the tag users subscribed for and available
|
||||
messages for each tag;
|
||||
|
||||
- threads window :: for a given timeline and folder (see
|
||||
[[Folders]]) show the discussions saved in user's local database;
|
||||
|
||||
- conversations window :: show the /private/ conversations the user is having with others;
|
||||
|
||||
- message window :: show the body of the message selected in the tag window
|
||||
|
||||
- command window :: the window where the user instruct the software to perform commands
|
||||
|
||||
The only way to interact with the program is using the keyboard.
|
||||
There is a contextual help that appears when the user input data
|
||||
that provide hints about commands and a quick help window that can
|
||||
be shown by hitting ~?~ (if this keybinding has not been
|
||||
customized).
|
||||
|
||||
** Folders
|
||||
|
||||
A folder is an object to groups messages for each timeline an
|
||||
arbitrary number of folder can be created, when the last message of
|
||||
a folder is deleted the folder is deleted as well.
|
||||
|
||||
* Configuration
|
||||
|
||||
The configuration of tinmop is based on text files but there are
|
||||
available two different kind with different syntax and scope.
|
||||
|
||||
- a key-value text files used to configure the access credential to
|
||||
server and visual theme of the program (simple configuration);
|
||||
|
||||
- common lisp source code. Used to write module (AKA plugin) and to
|
||||
configure keybindings to interact with the software.
|
||||
|
||||
The distribution of this software comes with a bunch of pre-backed
|
||||
configuration files but user is expected to write a simple file with
|
||||
their credential to log into the server.
|
||||
|
||||
** Simple configuration
|
||||
|
||||
This is a simple file with each entry in a single line that look like this:
|
||||
|
||||
#+NAME: simple file example
|
||||
#+BEGIN_SRC text
|
||||
|
||||
# a file can be included in another with this directive:
|
||||
use "default-theme.conf"
|
||||
|
||||
# a line starting with a '#' is a comment
|
||||
# The server instance name
|
||||
server = server address
|
||||
|
||||
# your username
|
||||
username = username
|
||||
|
||||
#+END_SRC
|
||||
|
||||
Not incidentally the information i the example above are the
|
||||
absolute minimum the user has to provide before starts the program:
|
||||
the name you chose when you made the account on the server and the
|
||||
address of the server.
|
||||
|
||||
As you can see a line starting with a *#* is considered comment and
|
||||
skipped by the program
|
||||
|
||||
The file with this credential are confidential and must be put into
|
||||
user's home directory under the path
|
||||
~/home/username/.local/share/tinmop/~. Probably the directory
|
||||
~tinmop~ does not exists on user system, if it does not exists must
|
||||
be created manually.
|
||||
|
||||
If the program was installed correctly two other files with simple
|
||||
semantics are located in your system wide configuration directory
|
||||
(usually ~/etc/tinmop/~), please check these files for more
|
||||
information, as are extensively commented.
|
||||
|
||||
** Lisp program
|
||||
|
||||
These files contains Common lisp (see [[https://common-lisp.net/]])
|
||||
source code. And are used both as a way to configure the program
|
||||
and to write module for tinmop itself.
|
||||
|
||||
These files are the only way to configure program's keybindings:
|
||||
sequence of pressing button to fire command commands (do not worry
|
||||
it is not too difficult!).
|
||||
|
||||
These files must be a valid Common Lisp program to allow the
|
||||
program to even starts. Again this is actual source code that is
|
||||
loaded end executed by the main program; be careful, do not copy
|
||||
and paste code from untrusted sources as this could results in a
|
||||
*severe* security damage.
|
||||
|
||||
Again in the configuration directory there is a (commented) file
|
||||
named ~init.lisp~ that user can use as their starting point to
|
||||
write their files. A custom init file (or other module files must
|
||||
be located into the directory ~$HOME/.local/share/tinmop/~
|
||||
to be successfully loaded.
|
||||
|
||||
However there is no need to write their own init file if user is
|
||||
happy with the provided one by the package maintainers.
|
||||
|
||||
* First time start
|
||||
|
||||
After the configuration the program can be started but we are not
|
||||
ready to join the network yet because tinmop need to be /trusted/ by
|
||||
the server. Just follows the instruction on screen to register the
|
||||
application with your instance. This procedure should be followed
|
||||
once: when the program starts for the first time (but please note
|
||||
that there must be a file with valid credentials available).
|
||||
|
||||
* How to get more help
|
||||
|
||||
For help with mastodon visit the mastodon website.
|
||||
|
||||
The program has an inline help (default binding for help is "?")
|
||||
|
||||
Moreover you can have some useful hint at the program web page:
|
||||
|
||||
[https://www.autistici.org/interzona/tinmop/]
|
||||
|
||||
* BUGS
|
||||
There are many, totally unknown, hiding in the code! Please help the
|
||||
programmer to nail them using the
|
||||
[[https://notabug.org/cage/tinmop/issues/][issue tracker]].
|
||||
|
||||
* Contributing
|
||||
|
||||
There is always need for help, you can join the developer, sending
|
||||
patches or translating the UI to your favourite language.
|
||||
|
||||
Just point your browser to the
|
||||
[[https://notabug.org/cage/tinmop/][code repository]].
|
||||
|
||||
See also the file CONTRIBUTE.org
|
||||
|
||||
* Privacy
|
||||
|
||||
This program do not interact with no other computer other than the
|
||||
mastodon instance that the user configured.
|
||||
|
||||
If installed from the source note that the script
|
||||
~quick_quicklisp.sh~ will contact [[https://www.quicklisp.org/]],
|
||||
check the
|
||||
[[https://beta.quicklisp.org/quicklisp.lisp][quicklisp sources]]
|
||||
for details.
|
||||
|
||||
* Acknowledgment
|
||||
|
||||
My deep thanks to the folks that provided us with wonderful SBCL and
|
||||
Common lisp libraries.
|
||||
|
||||
In particular i want to thanks the authors of the libraries Croatoan and Tooter
|
||||
for their help when I started to develop this program.
|
||||
|
||||
There are more people i borrowed code and data from, they are mentioned
|
||||
in the file LINCENSES.org
|
|
@ -0,0 +1,21 @@
|
|||
;; a comment starts with a semicolon like that
|
||||
(in-package :scripts) ; always starts a script with this line
|
||||
|
||||
;; defun means 'define a function'
|
||||
(defun read-stdin ()
|
||||
;; 'let' introduce a new variable, 'data' in this case
|
||||
(let ((data (loop ; read from standard and collect character in a list
|
||||
for char = (read-char *standard-input* nil nil)
|
||||
while char
|
||||
collect char)))
|
||||
(coerce data 'string))) ; transform the list in a string
|
||||
|
||||
(defun main ()
|
||||
(when-let* ((body (read-stdin)))
|
||||
;; the first element of a list (the stuff between parents is the
|
||||
;; function name the rest of the lists are the functions parameters.
|
||||
;; nil means false or kind of 'empty'
|
||||
(send-status body nil nil nil +status-public-visibility+)))
|
||||
|
||||
;; call the function to send a toot
|
||||
(main)
|
|
@ -0,0 +1,272 @@
|
|||
.TH "tinmop" "1"
|
||||
|
||||
.SH "Name"
|
||||
.PP
|
||||
tinmop - a client for mastodon or pleroma social network
|
||||
|
||||
.SH "Synopsis"
|
||||
.PP
|
||||
tinmop [OPTION]...
|
||||
|
||||
.SH "Description"
|
||||
.PP
|
||||
This document assumes basic knowledge of how mastodon works. More
|
||||
information about this topic can be found on the
|
||||
official website (\fIhttps://docs.joinmastodon.org/\fP).
|
||||
|
||||
.PP
|
||||
Tinmop propose a terminal interface to to connect with Mastodon or
|
||||
Pleroma social network
|
||||
|
||||
.SH "Options"
|
||||
.PP
|
||||
Without options the program will start a terminal interface and will
|
||||
try to connect to your instance (see \fIConfiguration\fP)
|
||||
|
||||
.TP
|
||||
\fB-e, --execute-script SCRIPT-FILE\fP
|
||||
Execute a script file
|
||||
.TP
|
||||
\fB-c, --check-follows-requests \fP
|
||||
Checks for follow request at start
|
||||
.TP
|
||||
\fB-u, --update-timeline \fP
|
||||
Update the selected timeline
|
||||
.TP
|
||||
\fB-t, --timeline TIMELINE-NAME \fP
|
||||
Start using this timeline
|
||||
.TP
|
||||
\fB-f, --folder FOLDER-NAME \fP
|
||||
Start on that folder
|
||||
.TP
|
||||
\fB-v, --version \fP
|
||||
Print program version and exit
|
||||
.TP
|
||||
\fB-h, --help \fP
|
||||
print program help and exit
|
||||
|
||||
.SH "Usage"
|
||||
.PP
|
||||
Users of Tinmop are expected to interact with the social network
|
||||
using a terminal interface (TUI), The terminal screen layout is
|
||||
sketched below:
|
||||
|
||||
.RS
|
||||
.nf
|
||||
\fC+---------------+---------------------------+
|
||||
| | |
|
||||
| tags window | thread windows |
|
||||
| | |
|
||||
| | modeline |
|
||||
+---------------+---------------------------+
|
||||
| | |
|
||||
| conversations | message window |
|
||||
| window | |
|
||||
| | |
|
||||
| | |
|
||||
+---------------+---------------------------+
|
||||
| command window |
|
||||
+-------------------------------------------+
|
||||
|
||||
\fP
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.PP
|
||||
The screen is subdivided in five window:
|
||||
|
||||
.TP
|
||||
\fBtag window\fP
|
||||
shows the tag users subscribed for and available
|
||||
messages for each tag;
|
||||
|
||||
.TP
|
||||
\fBthreads window\fP
|
||||
for a given timeline and folder (see \fIFolders\fP) show
|
||||
the discussions saved in user's local database;
|
||||
|
||||
.TP
|
||||
\fBconversations window\fP
|
||||
show the \fIprivate\fP conversations the user is having with others;
|
||||
|
||||
.TP
|
||||
\fBmessage window\fP
|
||||
show the body of the message selected in the tag window
|
||||
|
||||
.TP
|
||||
\fBcommand window\fP
|
||||
the window where the user instruct the software to perform commands
|
||||
|
||||
.PP
|
||||
The only way to interact with the program is using the keyboard.
|
||||
There is a contextual help that appears when the user input data
|
||||
that provide hints about commands and a quick help window that can
|
||||
be shown by hitting \fC?\fP (if this keybinding has not been
|
||||
customized).
|
||||
|
||||
.SS "Folders"
|
||||
.PP
|
||||
A folder is an object to groups messages for each timeline an
|
||||
arbitrary number of folder can be created, when the last message of
|
||||
a folder is deleted the folder is deleted as well.
|
||||
|
||||
.SH "Configuration"
|
||||
.PP
|
||||
The configuration of tinmop is based on text files but there are
|
||||
available two different kind with different syntax and scope.
|
||||
|
||||
.IP \(em 4
|
||||
a key value text files used to configure the access credential to
|
||||
.PP
|
||||
server and visual theme of the program (simple configuration);
|
||||
|
||||
.IP \(em 4
|
||||
common lisp source code. Used to write module (AKA plugin) and to
|
||||
.PP
|
||||
configure keybindings to interact with the software.
|
||||
|
||||
.PP
|
||||
The distribution of this software comes with a bunch of pre-backed
|
||||
configuration files but user is expected to write a simple file with
|
||||
their credential to log into the server.
|
||||
|
||||
.SS "Simple configuration"
|
||||
.PP
|
||||
This is a simple file with each entry in a single line that look like this:
|
||||
|
||||
.RS
|
||||
.nf
|
||||
\fC
|
||||
# a file can be included in another with this directive:
|
||||
use "default-theme.conf"
|
||||
|
||||
# a line starting with a '#' is a comment
|
||||
# The server instance name
|
||||
server = server address
|
||||
|
||||
# your username
|
||||
username = username
|
||||
|
||||
\fP
|
||||
.fi
|
||||
.RE
|
||||
|
||||
.PP
|
||||
Not incidentally the information in the example above are the
|
||||
absolute minimum the user has to provide before starts the program:
|
||||
the name you chose when you made the account on the server and the
|
||||
address of the server.
|
||||
|
||||
.PP
|
||||
As you can see a line starting with a \fB#\fP is considered comment and
|
||||
skipped by the program
|
||||
|
||||
.PP
|
||||
The file with this credential are confidential and must be put into
|
||||
user's home directory under the path
|
||||
\fC/home/username/.local/share/tinmop/\fP. Probably the directory
|
||||
\fCtinmop\fP does not exists on user system, if it does not exists must
|
||||
be created manually.
|
||||
|
||||
.PP
|
||||
If the program was installed correctly two other files with simple
|
||||
semantics are located in your system wide configuration directory
|
||||
(usually \fC/etc/tinmop/\fP), please check these files for more
|
||||
information, as are extensively commented.
|
||||
|
||||
.SS "Lisp program"
|
||||
.PP
|
||||
These files contains Common lisp (see \fIhttps://common-lisp.net\fP)
|
||||
source code. And are used both as a way to configure the program
|
||||
and to write module for tinmop itself.
|
||||
|
||||
.PP
|
||||
These files are the only way to configure program's keybindings:
|
||||
sequence of pressing button to fire command commands (do not worry
|
||||
it is not too difficult!).
|
||||
|
||||
.PP
|
||||
These files must be a valid Common Lisp program to allow the
|
||||
program to even starts. Again this is actual source code that is
|
||||
loaded end executed by the main program; be careful, do not copy
|
||||
and paste code from untrusted sources as this could results in a
|
||||
\fBsevere\fP security damage.
|
||||
|
||||
.PP
|
||||
Again in the configuration directory there is a (commented) file
|
||||
named \fCinit.lisp\fP that user can use as their starting point to
|
||||
write their files. A custom init file (or other module files must
|
||||
be located into the directory \fC/home/username/.local/share/tinmop/\fP
|
||||
to be successfully loaded.
|
||||
|
||||
.PP
|
||||
However there is no need to write their own init file if user is
|
||||
happy with the provided one by the package maintainers.
|
||||
|
||||
.SH "First time start"
|
||||
.PP
|
||||
After the configuration the program can be started but we are not
|
||||
ready to join the network yet because tinmop need to be \fItrusted\fP by
|
||||
the server. Just follows the instruction on screen to register the
|
||||
application with your instance. This procedure should be followed
|
||||
once. When the program starts for the first time (but please note
|
||||
that there must be a file with valid credentials available).
|
||||
|
||||
.SH "Modules and scripts"
|
||||
.PP
|
||||
Users can write modules and scripts for tinmop, both are lisp
|
||||
program so there is no artificial limiting to what they can do, the
|
||||
only difference is that the script are loaded without the TUI so
|
||||
they are useful for non interactive tasks.
|
||||
|
||||
.PP
|
||||
An example of module is the distributed file \fCinit.lisp\fP and users
|
||||
can find a simple script to send a toot in the their system wide
|
||||
documentation directory (sometimes: \fC/usr/share/doc/\fP).
|
||||
|
||||
.SH "How to get more help"
|
||||
.PP
|
||||
For help with mastodon visit the mastodon website.
|
||||
|
||||
.PP
|
||||
The program has an inline help (default binding for help is "?")
|
||||
|
||||
.PP
|
||||
Moreover you can have some useful hint at the program web page:
|
||||
|
||||
.PP
|
||||
\fIhttps://www.autistici.org/interzona/tinmop/\fP
|
||||
|
||||
.SH "BUGS"
|
||||
.PP
|
||||
There are many, totally unknown, hiding in the code! Please help the
|
||||
programmer to nail them using the
|
||||
https://notabug.org/cage/tinmop/issues/ \fBat\fP \fIissue tracker\fP.
|
||||
|
||||
.SH "Contributing"
|
||||
.PP
|
||||
There is always need for help, you can join the developer, sending
|
||||
patches or translating the UI to your favourite language.
|
||||
|
||||
.PP
|
||||
Just point your browser to the
|
||||
https://notabug.org/cage/tinmop/ \fBat\fP \fIcode repository\fP.
|
||||
|
||||
.PP
|
||||
See also the file CONTRIBUTE.org
|
||||
|
||||
.SH "Privacy"
|
||||
.PP
|
||||
This program do not interact with no other computer other than the
|
||||
mastodon instance that the user configured.
|
||||
|
||||
.PP
|
||||
If installed from the source note that the script
|
||||
\fCquick_quicklisp.sh\fP will contact \fIhttps://www.quicklisp.org/\fP,
|
||||
check the
|
||||
https://beta.quicklisp.org/quicklisp.lisp \fBat\fP \fIquicklisp sources\fP
|
||||
for details.
|
||||
|
||||
.SH "Acknowledgment"
|
||||
.PP
|
||||
See file LICENSES.org
|
|
@ -0,0 +1,418 @@
|
|||
# this is the default theme for tinmop, feel free to customize it
|
||||
# according to your taste , and do not forget to share! :)
|
||||
|
||||
# Note:
|
||||
|
||||
# valid color are specified using RGB triplets as #RRGGBB or color names
|
||||
# valid color names are:
|
||||
# - red
|
||||
# - green
|
||||
# - yellow
|
||||
# - blue
|
||||
# - magenta
|
||||
# - cyan
|
||||
# - white
|
||||
#
|
||||
# valid attributes are:
|
||||
# - bold
|
||||
# - italic
|
||||
# - underline
|
||||
# - blink
|
||||
|
||||
# The text that starts the title section of a window
|
||||
window.title.left.stopper.value = "╼▌"
|
||||
|
||||
# The text taht end the title section of a window
|
||||
window.title.right.stopper.value = "▐╾"
|
||||
|
||||
# default background color of terminal
|
||||
|
||||
main-window.background = black
|
||||
|
||||
# default foreground color (text) of terminal
|
||||
|
||||
main-window.foreground = white
|
||||
|
||||
# shown when a message was trasmitted in crypted form
|
||||
|
||||
crypted.mark.value = " 🔏👌"
|
||||
|
||||
# quick help window style
|
||||
quick-help.header.foreground = white
|
||||
|
||||
quick-help.header.background = red
|
||||
|
||||
quick-help.header.attribute = bold
|
||||
|
||||
# help dialog style
|
||||
|
||||
help-dialog.background = white
|
||||
|
||||
help-dialog.foreground = red
|
||||
|
||||
# info dialog style
|
||||
|
||||
info-dialog.background = blue
|
||||
|
||||
info-dialog.foreground = yellow
|
||||
|
||||
# error dialog style
|
||||
|
||||
error-dialog.background = red
|
||||
|
||||
error-dialog.foreground = yellow
|
||||
|
||||
# input dialog style
|
||||
|
||||
input-dialog.background = blue
|
||||
|
||||
input-dialog.foreground = white
|
||||
|
||||
# this color specifies the style for form of the dialog
|
||||
|
||||
input-dialog.input.foreground = black
|
||||
|
||||
input-dialog.input.background = #aaaaaa
|
||||
|
||||
# this color specify the style for active form of the dialog
|
||||
|
||||
input-dialog.input.selected.foreground = black
|
||||
|
||||
input-dialog.input.selected.background = white
|
||||
|
||||
# the notify window shows useful information to the user
|
||||
|
||||
notify-window.background = #0219A2
|
||||
|
||||
notify-window.foreground = #55D67C
|
||||
|
||||
notify-window.life = 2
|
||||
|
||||
# a window shows this text in the top left corner to indicate that it
|
||||
# has focus
|
||||
|
||||
window.focus.mark.value = "📌"
|
||||
|
||||
window.focus.mark.foreground = white
|
||||
|
||||
window.focus.mark.background = black
|
||||
|
||||
# this specify style for the thread window
|
||||
|
||||
thread-window.background = black
|
||||
|
||||
thread-window.foreground = blue
|
||||
|
||||
# the modeline window is a small section on the very bottom of the
|
||||
# thread window that shows some information about the threads see
|
||||
# below.
|
||||
|
||||
thread-window.modeline.background = blue
|
||||
|
||||
thread-window.modeline.foreground = yellow
|
||||
|
||||
# this variable customize the information that the modeline will
|
||||
# shows, values prefixed with a '%' will be expanded, allowe values to
|
||||
# be expanded are:
|
||||
|
||||
# - %u user account
|
||||
# - %s server connected to
|
||||
# - %k current timeline
|
||||
# - %f current folder
|
||||
# - %r number of read messages in this timeline/folder
|
||||
# - %r number of total messages in this timeline/folder
|
||||
# - %tags in selected messages (if any)
|
||||
# - %% a percent sign
|
||||
|
||||
thread-window.modeline.value = "%u@%s ◈ %k %r/%t ◈ folder: %f ◈ tags: %h"
|
||||
|
||||
# this is the only width you have to specify as the others windows
|
||||
# just fills the void left by this one
|
||||
thread-window.width = 5/6
|
||||
|
||||
thread-window.height = 1/4
|
||||
|
||||
# colors for selected messages in thread window
|
||||
|
||||
thread-window.message.selected.background = cyan
|
||||
|
||||
thread-window.message.selected.foreground = black
|
||||
|
||||
#thread-window.message.selected.attribute = bold
|
||||
|
||||
# colors for messages marked for deletion in thread window
|
||||
|
||||
thread-window.message.deleted.background = red
|
||||
|
||||
thread-window.message.deleted.foreground = white
|
||||
|
||||
thread-window.message.deleted.attribute = bold
|
||||
|
||||
# colors for already read messages in thread window
|
||||
|
||||
thread-window.message.read.background = black
|
||||
|
||||
thread-window.message.read.foreground = #aaaaaa
|
||||
|
||||
thread-window.message.read.attribute = italic
|
||||
|
||||
# colors for new (not read) messages in thread window
|
||||
|
||||
thread-window.message.unread.background = black
|
||||
|
||||
thread-window.message.unread.foreground = cyan
|
||||
|
||||
#thread-window.message.unread.attribute = bold
|
||||
|
||||
# text to signal that you favourited this message
|
||||
thread-window.message.favourite.value = "★"
|
||||
|
||||
# color of the text that signals that you favourited this message
|
||||
thread-window.message.favourite.foreground = yellow
|
||||
|
||||
# text to signal that this message is marked as sensible
|
||||
thread-window.message.sensitive.value = "⚠ "
|
||||
|
||||
# color of the text that signals that this message is marked as sensible
|
||||
thread-window.message.sensitive.foreground = blue
|
||||
|
||||
# text that signals that you boosted this message
|
||||
thread-window.message.boosted.value = "♻"
|
||||
|
||||
# color of the text that signals that you boosted this message
|
||||
thread-window.message.boosted.foreground = cyan
|
||||
|
||||
# the messages are organized in trees
|
||||
|
||||
# color of the branch of the tree (the segments that connect messages)
|
||||
|
||||
thread-window.tree.branch.foreground = red
|
||||
|
||||
# color of the arrow in the tree that points to a single message
|
||||
|
||||
thread-window.tree.arrow.foreground = magenta
|
||||
|
||||
# color of the subject of the message (AKA sensistive text) for a
|
||||
# message
|
||||
|
||||
thread-window.tree.data.foreground = white
|
||||
|
||||
# color of the subject of the message (AKA sensistive text) for
|
||||
# message with no replies
|
||||
|
||||
thread-window.tree.data-leaf.foreground = white
|
||||
|
||||
# arrow that point to a message
|
||||
|
||||
thread-window.tree.arrow.value = "🞂 "
|
||||
|
||||
# segment that connect a message with no replies to the tree
|
||||
|
||||
thread-window.tree.leaf.value = "╰"
|
||||
|
||||
# segment that connect a message with replies to the tree
|
||||
|
||||
thread-window.tree.branch.value = "├"
|
||||
|
||||
# segment that push to the left a message subject
|
||||
|
||||
thread-window.tree.spacer.value = "─"
|
||||
|
||||
# vertical segment that connect tree branches
|
||||
|
||||
thread-window.tree.vertical-line.value = "│"
|
||||
|
||||
# a message shows the composition date, specify the format:
|
||||
# values starting with '%' will be expanded, allowed values are:
|
||||
# - %hour 0-23
|
||||
# - %min
|
||||
# - %second
|
||||
# - %month numeric month
|
||||
# - %year
|
||||
# - %day day of the month
|
||||
# - %weekday 0 to 7 (note: 0 is Sunday)
|
||||
# - %short-weekday Sun to Mon
|
||||
# - %long-weekday Sunday to Monday
|
||||
# - %long-month Januray to December
|
||||
# - %short-month Jan to Dec
|
||||
# - %% a percent sign
|
||||
|
||||
thread-window.date-format.value = "%year %short-month %day %hour:%min"
|
||||
|
||||
# the windows that shows tags subscriptions
|
||||
|
||||
tags-window.height = 1/2
|
||||
|
||||
tags-window.background = black
|
||||
|
||||
tags-window.foreground = #67998B
|
||||
|
||||
# the colors for currently selected tags
|
||||
|
||||
tags-window.input.selected.background = black
|
||||
|
||||
tags-window.input.selected.foreground = #71AF8C
|
||||
|
||||
# tags shows a little histogram (note that some servers do not provide
|
||||
# this information) for number of messages posted every day that
|
||||
# contains this tag
|
||||
|
||||
tags-window.histogram.foreground = yellow
|
||||
|
||||
# test to indicate that this tags got new messages
|
||||
|
||||
tags-window.new-message.mark.value = " 📬"
|
||||
|
||||
# this is the window that shows active conversation (a conversation is
|
||||
# active until the user chooses to ignore it)
|
||||
|
||||
conversations-window.background = black
|
||||
|
||||
conversations-window.foreground = #B48B21
|
||||
|
||||
# the colors for currently selected conversation
|
||||
|
||||
conversations-window.input.selected.background = #4B0301
|
||||
|
||||
conversations-window.input.selected.foreground = #B27DE5
|
||||
|
||||
#colors for count of read messages for conversation
|
||||
|
||||
#conversations-window.read.background = black
|
||||
|
||||
#conversations-window.read.foreground = blue
|
||||
|
||||
#colors for count of unreaded messages for conversation
|
||||
|
||||
conversations-window.unread.background = black
|
||||
|
||||
conversations-window.unread.foreground = red
|
||||
|
||||
# this is the message that shows available keybindings
|
||||
|
||||
keybindings-window.background = black
|
||||
|
||||
keybindings-window.foreground = #E2BE6F
|
||||
|
||||
keybindings-window.height = 1/2
|
||||
|
||||
# see configuration for tree in thread window above
|
||||
|
||||
keybindings-window.tree.branch.foreground = red
|
||||
|
||||
keybindings-window.tree.arrow.foreground = magenta
|
||||
|
||||
keybindings-window.tree.data.foreground = white
|
||||
|
||||
keybindings-window.tree.data-leaf.foreground = cyan
|
||||
|
||||
keybindings-window.tree.arrow.value = "🞂 "
|
||||
|
||||
keybindings-window.tree.leaf.value = "╰"
|
||||
|
||||
keybindings-window.tree.branch.value = "├"
|
||||
|
||||
keybindings-window.tree.spacer.value = "─"
|
||||
|
||||
keybindings-window.tree.vertical-line.value = "│"
|
||||
|
||||
# autocomplete window
|
||||
|
||||
suggestions-window.background = blue
|
||||
|
||||
suggestions-window.foreground = yellow
|
||||
|
||||
suggestions-window.height = 1/4
|
||||
|
||||
# the directive belows configure the window at the very bottom of the
|
||||
# screen that user uses to give command to the program, also is used
|
||||
# to shows some input errors or other informations
|
||||
|
||||
command-window.background = black
|
||||
|
||||
command-window.foreground = white
|
||||
|
||||
# text to separate keybindig added so far by the user
|
||||
|
||||
command-window.command-separator.value = " → "
|
||||
|
||||
# colors of the separator above
|
||||
command-window.command-separator.foreground = yellow
|
||||
|
||||
command-window.command-separator.background = black
|
||||
|
||||
# color for error message shown in command window
|
||||
|
||||
command-window.error.message.background = black
|
||||
|
||||
command-window.error.message.foreground = red
|
||||
|
||||
command-window.error.message.attribute = bold
|
||||
|
||||
# color for info message shown in command window
|
||||
|
||||
command-window.info.message.foreground = yellow
|
||||
|
||||
command-window.info.message.background = black
|
||||
|
||||
command-window.info.message.attribute = bold
|
||||
|
||||
# this is the window that show the content of a message
|
||||
|
||||
message-window.background = black
|
||||
|
||||
message-window.foreground = #c9c0c0
|
||||
|
||||
# a marker on the right side of the window to show the position of the
|
||||
# message is visualized in repect of the message lines length (similar
|
||||
# to scrollbar in GUI)
|
||||
|
||||
message-window.line-position-mark.foreground = white
|
||||
|
||||
message-window.line-position-mark.background = black
|
||||
|
||||
# the text for the marker above
|
||||
|
||||
message-window.line-position-mark.value = "⧫"
|
||||
|
||||
# the date format for message
|
||||
# values starting with '%' will be expanded, allowed values are:
|
||||
# - %hour 0-23
|
||||
# - %min
|
||||
# - %second
|
||||
# - %month numeric month
|
||||
# - %year
|
||||
# - %day day of the month
|
||||
# - %weekday 0 to 7 (note: 0 is Sunday)
|
||||
# - %short-weekday Sun to Mon
|
||||
# - %long-weekday Sunday to Monday
|
||||
# - %long-month Januray to December
|
||||
# - %short-month Jan to Dec
|
||||
# - %% a percent sign
|
||||
|
||||
message-window.date-format.value = "%year %short-month %day %hour:%min"
|
||||
|
||||
message-window.attachment-header.prefix.value = "~%──── "
|
||||
|
||||
message-window.attachment-header.postfix.value = " ────~%"
|
||||
|
||||
message-window.account.locked.mark.value = " 🔒"
|
||||
|
||||
message-window.account.unlocked.mark.value = " 🔓"
|
||||
|
||||
# the string for the header of attachments in a message, if not
|
||||
# specified a default is chosen by the software.
|
||||
|
||||
#message-window.attachment-header.value = " attachment "
|
||||
|
||||
# this is the window that allow to browse the attachments of a message
|
||||
|
||||
open-attach-window.background = black
|
||||
|
||||
open-attach-window.foreground = #67998B
|
||||
|
||||
# the colors of selected attachments
|
||||
|
||||
open-attach-window.input.selected.background = black
|
||||
|
||||
open-attach-window.input.selected.foreground = #71AF8C
|
|
@ -0,0 +1,293 @@
|
|||
;; this is the main configuration file for tinmop.
|
||||
|
||||
;; This file must be a valid common lisp program to allow the program
|
||||
;; to even starts. This file is actual common lisp source code that is
|
||||
;; loaded end executed by the main program; be careful, do not copy
|
||||
;; and paste code from untrusted sources as this could results in a
|
||||
;; *severe* security damage.
|
||||
|
||||
;; Anyway, even if you do not know lisp you should be able to change
|
||||
;; keybindings with no difficult. Editing this file is the way to
|
||||
;; accomplish this task.
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; This line on top of the file is mandatory if you want to write a
|
||||
;; module (AKA plugin) for the program as the form below will provide
|
||||
;; you with access to many program's machinery
|
||||
|
||||
(in-package :modules)
|
||||
|
||||
;; of course you can define new functions. Also note that the function called at
|
||||
;; the end of the each command:
|
||||
;;
|
||||
;; (define-key "command keybinding" function)
|
||||
;; ^^^^^^^^
|
||||
;;
|
||||
;; can not be an anonymous function.
|
||||
|
||||
(defun quit () ; define a custom function named 'quit' and no parameters
|
||||
"Quit the program" ; this string after the function name and parameters is
|
||||
; called 'docstring' and will be presented to the
|
||||
; user as inline help
|
||||
(ui:clean-close-program))
|
||||
|
||||
;; keybindings syntax:
|
||||
|
||||
;; a command is executed after a sequence of one or more keys. a key
|
||||
;; can be composed by a single physical button pressed on the keyboard
|
||||
;; or by a combination of a button pressed together.
|
||||
|
||||
;; Example of keys are:
|
||||
|
||||
;; "a" -> press the button on the keyboard with the symbol 'a' printed on top
|
||||
;; "a b" -> press and release the button 'a' and then press the button 'b'
|
||||
;; "f1 f2" -> press function key 1 and function key 2
|
||||
|
||||
;; sometimes a key is composed by two pressed buttons. For example to
|
||||
;; input the character 'C' (i.e. capital 'c') usually you should use a
|
||||
;; combination of SHIFT and C but there is more: a key can be composed
|
||||
;; by two button pressed, the first called a "modifier" button.
|
||||
|
||||
;; The "modifier" button must be keep pressed
|
||||
;; while the second button is hit for the key to be valid.
|
||||
|
||||
;; There are only two legal modifier buttons: the "Alt" button
|
||||
;; (indicated with the letter "M") and the "Control" button (indicated
|
||||
;; with "C").
|
||||
|
||||
;; So said some combined keys example are given:
|
||||
|
||||
;; "M-1" -> press "Alt" and, while pressed, press "1"
|
||||
|
||||
;; Note the dash between the symbols, the key below:
|
||||
|
||||
;; "M 1"
|
||||
|
||||
;; means: "Press the combination of button to print the capital button
|
||||
;; 'M' and the button to print the character '1', so this is not
|
||||
;; equivalent to "M-1".
|
||||
|
||||
;; With this information in mind we can decode more keys like the ones
|
||||
;; given below:
|
||||
|
||||
;; "C-c x" -> Press control and keep pressed press 'c' then release
|
||||
;; the two buttons and press 'x'.
|
||||
|
||||
;; "C-c c" -> Press control and keep pressed press 'c' then release
|
||||
;; the two buttons and press 'c' again.
|
||||
|
||||
;; Caveat:
|
||||
|
||||
;; - The single letter after control modifier are case insensitive;
|
||||
;; - the enter key must be specified by "C-J";
|
||||
;; - "dc" is the key Delete;
|
||||
|
||||
;; To define a new key just use the 'define-key' form below:
|
||||
|
||||
;; (define key COMMAND FUNCTION)
|
||||
|
||||
;; Where COMMAND is a sequence of keys and FUNCTION is the name
|
||||
;; (prefixed by: #') of the function to fire after command has been
|
||||
;; completed
|
||||
|
||||
;; See the command below as examples.
|
||||
|
||||
;; key conflict
|
||||
|
||||
;; Sometime two commands key may conflict, for example:
|
||||
|
||||
;; (define "C-x a" #'foo)
|
||||
|
||||
;; (define "C-x a b" #'bar)
|
||||
|
||||
;; How the program could know which way choose when the button 'a' is
|
||||
;; pressed? Should be executed the function 'foo' or should we go
|
||||
;; beyond this function and wait for the button 'b' to be pressed?
|
||||
|
||||
;; The convention chosen is that will be executed the shorter path
|
||||
;; that lead to a function so, in the case above, the function 'foo'
|
||||
;; will be executed.
|
||||
|
||||
;; Note that the two command below are *not* in conflict:
|
||||
|
||||
;; (define "C-x a b c d" #'foo)
|
||||
|
||||
;; (define "C-x a e" #'bar)
|
||||
|
||||
|
||||
;; global keymap
|
||||
|
||||
(define-key "C-q" #'quit) ; here we are calling the custom
|
||||
; function defined above
|
||||
|
||||
(define-key "?" #'print-quick-help)
|
||||
|
||||
;; focus
|
||||
|
||||
(define-key "f1" #'focus-to-tags-window)
|
||||
|
||||
(define-key "f2" #'focus-to-thread-window)
|
||||
|
||||
(define-key "f3" #'focus-to-message-window)
|
||||
|
||||
(define-key "f4" #'focus-to-conversations-window)
|
||||
|
||||
;; follow requests keymap
|
||||
|
||||
(define-key "up" #'follow-request-go-up *follow-requests-keymap*)
|
||||
|
||||
(define-key "down" #'follow-request-go-down *follow-requests-keymap*)
|
||||
|
||||
(define-key "d" #'follow-request-delete *follow-requests-keymap*)
|
||||
|
||||
(define-key "C-J" #'process-follow-requests *follow-requests-keymap*)
|
||||
|
||||
(define-key "q" #'cancel-follow-requests *follow-requests-keymap*)
|
||||
|
||||
;; send message keymap
|
||||
|
||||
(define-key "up" #'attach-go-up *send-message-keymap*)
|
||||
|
||||
(define-key "down" #'attach-go-down *send-message-keymap*)
|
||||
|
||||
(define-key "d" #'attach-delete *send-message-keymap*)
|
||||
|
||||
(define-key "s" #'change-subject *send-message-keymap*)
|
||||
|
||||
(define-key "q" #'cancel-send-message *send-message-keymap*)
|
||||
|
||||
(define-key "v" #'change-visibility *send-message-keymap*)
|
||||
|
||||
(define-key "e" #'edit-message-body *send-message-keymap*)
|
||||
|
||||
(define-key "C-J" #'send-message *send-message-keymap*)
|
||||
|
||||
;; thread window keymap
|
||||
|
||||
(define-key "up" #'thread-go-up *thread-keymap*)
|
||||
|
||||
(define-key "down" #'thread-go-down *thread-keymap*)
|
||||
|
||||
(define-key "C-J" #'thread-open-selected-message *thread-keymap*)
|
||||
|
||||
(define-key "dc" #'thread-mark-delete-selected-message *thread-keymap*)
|
||||
|
||||
(define-key "U" #'thread-mark-prevent-delete-selected-message *thread-keymap*)
|
||||
|
||||
(define-key "g" #'thread-goto-message *thread-keymap*)
|
||||
|
||||
(define-key "/ b" #'thread-search-next-message-body *thread-keymap*)
|
||||
|
||||
(define-key "\\\\ b" #'thread-search-previous-message-body *thread-keymap*)
|
||||
|
||||
(define-key "/ m" #'thread-search-next-message-meta *thread-keymap*)
|
||||
|
||||
(define-key "\\\\ m" #'thread-search-previous-message-meta *thread-keymap*)
|
||||
|
||||
(define-key "n" #'thread-search-next-unread-message *thread-keymap*)
|
||||
|
||||
(define-key "home" #'thread-goto-first-message *thread-keymap*)
|
||||
|
||||
(define-key "end" #'thread-goto-last-message *thread-keymap*)
|
||||
|
||||
(define-key "c" #'compose-message *thread-keymap*)
|
||||
|
||||
(define-key "r" #'reply-message *thread-keymap*)
|
||||
|
||||
(define-key "v" #'open-message-attach *thread-keymap*)
|
||||
|
||||
(define-key "C-f c" #'change-folder *thread-keymap*)
|
||||
|
||||
(define-key "C-t c" #'change-timeline *thread-keymap*)
|
||||
|
||||
(define-key "C-t u" #'update-current-timeline *thread-keymap*)
|
||||
|
||||
(define-key "C-t h r" #'refresh-tags *thread-keymap*)
|
||||
|
||||
(define-key "C-X m t" #'move-message-tree *thread-keymap*)
|
||||
|
||||
(define-key "C-X m f" #'favourite-selected-status *thread-keymap*)
|
||||
|
||||
(define-key "C-X m r f" #'unfavourite-selected-status *thread-keymap*)
|
||||
|
||||
(define-key "C-X m b" #'boost-selected-status *thread-keymap*)
|
||||
|
||||
(define-key "C-X m r b" #'unboost-selected-status *thread-keymap*)
|
||||
|
||||
(define-key "C-X m s" #'subscribe-to-hash *thread-keymap*)
|
||||
|
||||
(define-key "C-X m u" #'unsubscribe-to-hash *thread-keymap*)
|
||||
|
||||
(define-key "C-u i" #'ignore-user *thread-keymap*)
|
||||
|
||||
(define-key "C-u x" #'unignore-user *thread-keymap*)
|
||||
|
||||
(define-key "C-u f" #'follow-user *thread-keymap*)
|
||||
|
||||
(define-key "C-u r f" #'start-follow-request-processing *thread-keymap*)
|
||||
|
||||
(define-key "C-u r r" #'report-status *thread-keymap*)
|
||||
|
||||
(define-key "C-u u" #'unfollow-user *thread-keymap*)
|
||||
|
||||
(define-key "C-u c k i" #'crypto-import-key *thread-keymap*)
|
||||
|
||||
(define-key "C-u c k s" #'crypto-export-key *thread-keymap*)
|
||||
|
||||
(define-key "C-u c k g" #'crypto-generate-key *thread-keymap*)
|
||||
|
||||
(define-key "C-c u" #'update-conversations *thread-keymap*)
|
||||
|
||||
(define-key "C-c o" #'open-conversation *thread-keymap*)
|
||||
|
||||
(define-key "C-c c" #'change-conversation-name *thread-keymap*)
|
||||
|
||||
;; message window keymap
|
||||
|
||||
(define-key "up" #'message-scroll-up *message-keymap*)
|
||||
|
||||
(define-key "down" #'message-scroll-down *message-keymap*)
|
||||
|
||||
(define-key "home" #'message-scroll-begin *message-keymap*)
|
||||
|
||||
(define-key "end" #'message-scroll-end *message-keymap*)
|
||||
|
||||
(define-key "/" #'message-search-regex *message-keymap*)
|
||||
|
||||
(define-key "npage" #'message-scroll-next-page *message-keymap*)
|
||||
|
||||
(define-key "ppage" #'message-scroll-previous-page *message-keymap*)
|
||||
|
||||
;; tags keymap
|
||||
|
||||
(define-key "up" #'tag-go-up *tags-keymap*)
|
||||
|
||||
(define-key "down" #'tag-go-down *tags-keymap*)
|
||||
|
||||
(define-key "C-J" #'open-tag-folder *tags-keymap*)
|
||||
|
||||
(define-key "C-X m u" #'unsubscribe-to-hash *tags-keymap*)
|
||||
|
||||
(define-key "C-t h r" #'refresh-tags *tags-keymap*)
|
||||
|
||||
;; conversations keymap
|
||||
|
||||
(define-key "C-J" #'goto-conversation *conversations-keymap*)
|
||||
|
||||
(define-key "up" #'conversation-go-up *conversations-keymap*)
|
||||
|
||||
(define-key "down" #'conversation-go-down *conversations-keymap*)
|
||||
|
||||
(define-key "dc" #'delete-conversation *conversations-keymap*)
|
||||
|
||||
(define-key "I" #'ignore-conversation *conversations-keymap*)
|
||||
|
||||
;; attachments keymap
|
||||
|
||||
(define-key "C-J" #'open-message-attach-perform-opening *open-attach-keymap*)
|
||||
|
||||
(define-key "up" #'open-message-attach-go-up *open-attach-keymap*)
|
||||
|
||||
(define-key "down" #'open-message-attach-go-down *open-attach-keymap*)
|
||||
|
||||
(define-key "q" #'close-open-message-window *open-attach-keymap*)
|
|
@ -0,0 +1,74 @@
|
|||
# a line starting with a '#' is a comment
|
||||
# The server instance name
|
||||
# add this entry to your file (the one in your home)
|
||||
# server = test.server.org
|
||||
|
||||
# your username
|
||||
# add this entry to your file (the one in your home)
|
||||
# username = username
|
||||
|
||||
# theme
|
||||
|
||||
use "default-theme.conf"
|
||||
|
||||
# maximum number of attachments allowed for a single toot
|
||||
# default is 4
|
||||
#max-numbers-allowed-attachments = 4
|
||||
|
||||
# maximum number of characters allowed for a single toot
|
||||
# default is 500
|
||||
#max-message-lenght = 500
|
||||
|
||||
# maximum number of characters allowed for reporting an user to
|
||||
# instance's admin
|
||||
#default is 100
|
||||
#max-report-comment-length = 100
|
||||
|
||||
# Character to use when replying to a message
|
||||
reply-quoted-character = "> "
|
||||
|
||||
# delete the command history entries that are older than this number
|
||||
# of days
|
||||
purge-history-days-offset = -7
|
||||
|
||||
# delete the cache entries that are older than this number of days
|
||||
purge-cache-days-offset = -7
|
||||
|
||||
# chosen editor (as shell command line) for compose a message
|
||||
editor = "nano --locking"
|
||||
|
||||
# color parts of a displayed message according to a regular expression
|
||||
# syntax is regular expression color attribute
|
||||
# valid color are specified using RGB triplets as #RRGGBB or color names
|
||||
# valid color names are:
|
||||
# - red
|
||||
# - green
|
||||
# - yellow
|
||||
# - blue
|
||||
# - magenta
|
||||
# - cyan
|
||||
# - white
|
||||
#
|
||||
# valid attributes are:
|
||||
# - bold
|
||||
# - italic
|
||||
# - underline
|
||||
# - blink
|
||||
|
||||
# attribute is optional
|
||||
|
||||
# Some examples follows
|
||||
|
||||
color-regexp = "http(s)?://[^ ]+" #ff0000
|
||||
|
||||
color-regexp = "(?i)(\\(c\\))|(\\(r\\))" #ff0000 bold
|
||||
|
||||
color-regexp = "[0-9]{4}-[0-9]?[0-9]-[0-9]?[0-9]" #0000ff bold
|
||||
|
||||
color-regexp = "-?[0-9]+%" #ff00ff bold
|
||||
|
||||
color-regexp = "\*[^*]+\*" #ffff00 bold
|
||||
|
||||
color-regexp = "_[^_]+_" #ffff00 underline
|
||||
|
||||
color-regexp = "/[^/]+/" #ffff00 italic
|
|
@ -0,0 +1,529 @@
|
|||
#!/bin/sh
|
||||
# install - install a program, script, or datafile
|
||||
|
||||
scriptversion=2018-03-11.20; # UTC
|
||||
|
||||
# This originates from X11R5 (mit/util/scripts/install.sh), which was
|
||||
# later released in X11R6 (xc/config/util/install.sh) with the
|
||||
# following copyright and license.
|
||||
#
|
||||
# Copyright (C) 1994 X Consortium
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
|
||||
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Except as contained in this notice, the name of the X Consortium shall not
|
||||
# be used in advertising or otherwise to promote the sale, use or other deal-
|
||||
# ings in this Software without prior written authorization from the X Consor-
|
||||
# tium.
|
||||
#
|
||||
#
|
||||
# FSF changes to this file are in the public domain.
|
||||
#
|
||||
# Calling this script install-sh is preferred over install.sh, to prevent
|
||||
# 'make' implicit rules from creating a file called install from it
|
||||
# when there is no Makefile.
|
||||
#
|
||||
# This script is compatible with the BSD install script, but was written
|
||||
# from scratch.
|
||||
|
||||
tab=' '
|
||||
nl='
|
||||
'
|
||||
IFS=" $tab$nl"
|
||||
|
||||
# Set DOITPROG to "echo" to test this script.
|
||||
|
||||
doit=${DOITPROG-}
|
||||
doit_exec=${doit:-exec}
|
||||
|
||||
# Put in absolute file names if you don't have them in your path;
|
||||
# or use environment vars.
|
||||
|
||||
chgrpprog=${CHGRPPROG-chgrp}
|
||||
chmodprog=${CHMODPROG-chmod}
|
||||
chownprog=${CHOWNPROG-chown}
|
||||
cmpprog=${CMPPROG-cmp}
|
||||
cpprog=${CPPROG-cp}
|
||||
mkdirprog=${MKDIRPROG-mkdir}
|
||||
mvprog=${MVPROG-mv}
|
||||
rmprog=${RMPROG-rm}
|
||||
stripprog=${STRIPPROG-strip}
|
||||
|
||||
posix_mkdir=
|
||||
|
||||
# Desired mode of installed file.
|
||||
mode=0755
|
||||
|
||||
chgrpcmd=
|
||||
chmodcmd=$chmodprog
|
||||
chowncmd=
|
||||
mvcmd=$mvprog
|
||||
rmcmd="$rmprog -f"
|
||||
stripcmd=
|
||||
|
||||
src=
|
||||
dst=
|
||||
dir_arg=
|
||||
dst_arg=
|
||||
|
||||
copy_on_change=false
|
||||
is_target_a_directory=possibly
|
||||
|
||||
usage="\
|
||||
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
|
||||
or: $0 [OPTION]... SRCFILES... DIRECTORY
|
||||
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
|
||||
or: $0 [OPTION]... -d DIRECTORIES...
|
||||
|
||||
In the 1st form, copy SRCFILE to DSTFILE.
|
||||
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
|
||||
In the 4th, create DIRECTORIES.
|
||||
|
||||
Options:
|
||||
--help display this help and exit.
|
||||
--version display version info and exit.
|
||||
|
||||
-c (ignored)
|
||||
-C install only if different (preserve the last data modification time)
|
||||
-d create directories instead of installing files.
|
||||
-g GROUP $chgrpprog installed files to GROUP.
|
||||
-m MODE $chmodprog installed files to MODE.
|
||||
-o USER $chownprog installed files to USER.
|
||||
-s $stripprog installed files.
|
||||
-t DIRECTORY install into DIRECTORY.
|
||||
-T report an error if DSTFILE is a directory.
|
||||
|
||||
Environment variables override the default commands:
|
||||
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
|
||||
RMPROG STRIPPROG
|
||||
"
|
||||
|
||||
while test $# -ne 0; do
|
||||
case $1 in
|
||||
-c) ;;
|
||||
|
||||
-C) copy_on_change=true;;
|
||||
|
||||
-d) dir_arg=true;;
|
||||
|
||||
-g) chgrpcmd="$chgrpprog $2"
|
||||
shift;;
|
||||
|
||||
--help) echo "$usage"; exit $?;;
|
||||
|
||||
-m) mode=$2
|
||||
case $mode in
|
||||
*' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
|
||||
echo "$0: invalid mode: $mode" >&2
|
||||
exit 1;;
|
||||
esac
|
||||
shift;;
|
||||
|
||||
-o) chowncmd="$chownprog $2"
|
||||
shift;;
|
||||
|
||||
-s) stripcmd=$stripprog;;
|
||||
|
||||
-t)
|
||||
is_target_a_directory=always
|
||||
dst_arg=$2
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $dst_arg in
|
||||
-* | [=\(\)!]) dst_arg=./$dst_arg;;
|
||||
esac
|
||||
shift;;
|
||||
|
||||
-T) is_target_a_directory=never;;
|
||||
|
||||
--version) echo "$0 $scriptversion"; exit $?;;
|
||||
|
||||
--) shift
|
||||
break;;
|
||||
|
||||
-*) echo "$0: invalid option: $1" >&2
|
||||
exit 1;;
|
||||
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# We allow the use of options -d and -T together, by making -d
|
||||
# take the precedence; this is for compatibility with GNU install.
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
if test -n "$dst_arg"; then
|
||||
echo "$0: target directory not allowed when installing a directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
|
||||
# When -d is used, all remaining arguments are directories to create.
|
||||
# When -t is used, the destination is already specified.
|
||||
# Otherwise, the last argument is the destination. Remove it from $@.
|
||||
for arg
|
||||
do
|
||||
if test -n "$dst_arg"; then
|
||||
# $@ is not empty: it contains at least $arg.
|
||||
set fnord "$@" "$dst_arg"
|
||||
shift # fnord
|
||||
fi
|
||||
shift # arg
|
||||
dst_arg=$arg
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $dst_arg in
|
||||
-* | [=\(\)!]) dst_arg=./$dst_arg;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
if test $# -eq 0; then
|
||||
if test -z "$dir_arg"; then
|
||||
echo "$0: no input file specified." >&2
|
||||
exit 1
|
||||
fi
|
||||
# It's OK to call 'install-sh -d' without argument.
|
||||
# This can happen when creating conditional directories.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if test -z "$dir_arg"; then
|
||||
if test $# -gt 1 || test "$is_target_a_directory" = always; then
|
||||
if test ! -d "$dst_arg"; then
|
||||
echo "$0: $dst_arg: Is not a directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$dir_arg"; then
|
||||
do_exit='(exit $ret); exit $ret'
|
||||
trap "ret=129; $do_exit" 1
|
||||
trap "ret=130; $do_exit" 2
|
||||
trap "ret=141; $do_exit" 13
|
||||
trap "ret=143; $do_exit" 15
|
||||
|
||||
# Set umask so as not to create temps with too-generous modes.
|
||||
# However, 'strip' requires both read and write access to temps.
|
||||
case $mode in
|
||||
# Optimize common cases.
|
||||
*644) cp_umask=133;;
|
||||
*755) cp_umask=22;;
|
||||
|
||||
*[0-7])
|
||||
if test -z "$stripcmd"; then
|
||||
u_plus_rw=
|
||||
else
|
||||
u_plus_rw='% 200'
|
||||
fi
|
||||
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
|
||||
*)
|
||||
if test -z "$stripcmd"; then
|
||||
u_plus_rw=
|
||||
else
|
||||
u_plus_rw=,u+rw
|
||||
fi
|
||||
cp_umask=$mode$u_plus_rw;;
|
||||
esac
|
||||
fi
|
||||
|
||||
for src
|
||||
do
|
||||
# Protect names problematic for 'test' and other utilities.
|
||||
case $src in
|
||||
-* | [=\(\)!]) src=./$src;;
|
||||
esac
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
dst=$src
|
||||
dstdir=$dst
|
||||
test -d "$dstdir"
|
||||
dstdir_status=$?
|
||||
else
|
||||
|
||||
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
|
||||
# might cause directories to be created, which would be especially bad
|
||||
# if $src (and thus $dsttmp) contains '*'.
|
||||
if test ! -f "$src" && test ! -d "$src"; then
|
||||
echo "$0: $src does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test -z "$dst_arg"; then
|
||||
echo "$0: no destination specified." >&2
|
||||
exit 1
|
||||
fi
|
||||
dst=$dst_arg
|
||||
|
||||
# If destination is a directory, append the input filename.
|
||||
if test -d "$dst"; then
|
||||
if test "$is_target_a_directory" = never; then
|
||||
echo "$0: $dst_arg: Is a directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
dstdir=$dst
|
||||
dstbase=`basename "$src"`
|
||||
case $dst in
|
||||
*/) dst=$dst$dstbase;;
|
||||
*) dst=$dst/$dstbase;;
|
||||
esac
|
||||
dstdir_status=0
|
||||
else
|
||||
dstdir=`dirname "$dst"`
|
||||
test -d "$dstdir"
|
||||
dstdir_status=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
case $dstdir in
|
||||
*/) dstdirslash=$dstdir;;
|
||||
*) dstdirslash=$dstdir/;;
|
||||
esac
|
||||
|
||||
obsolete_mkdir_used=false
|
||||
|
||||
if test $dstdir_status != 0; then
|
||||
case $posix_mkdir in
|
||||
'')
|
||||
# Create intermediate dirs using mode 755 as modified by the umask.
|
||||
# This is like FreeBSD 'install' as of 1997-10-28.
|
||||
umask=`umask`
|
||||
case $stripcmd.$umask in
|
||||
# Optimize common cases.
|
||||
*[2367][2367]) mkdir_umask=$umask;;
|
||||
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
|
||||
|
||||
*[0-7])
|
||||
mkdir_umask=`expr $umask + 22 \
|
||||
- $umask % 100 % 40 + $umask % 20 \
|
||||
- $umask % 10 % 4 + $umask % 2
|
||||
`;;
|
||||
*) mkdir_umask=$umask,go-w;;
|
||||
esac
|
||||
|
||||
# With -d, create the new directory with the user-specified mode.
|
||||
# Otherwise, rely on $mkdir_umask.
|
||||
if test -n "$dir_arg"; then
|
||||
mkdir_mode=-m$mode
|
||||
else
|
||||
mkdir_mode=
|
||||
fi
|
||||
|
||||
posix_mkdir=false
|
||||
case $umask in
|
||||
*[123567][0-7][0-7])
|
||||
# POSIX mkdir -p sets u+wx bits regardless of umask, which
|
||||
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
|
||||
;;
|
||||
*)
|
||||
# Note that $RANDOM variable is not portable (e.g. dash); Use it
|
||||
# here however when possible just to lower collision chance.
|
||||
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
|
||||
|
||||
trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0
|
||||
|
||||
# Because "mkdir -p" follows existing symlinks and we likely work
|
||||
# directly in world-writeable /tmp, make sure that the '$tmpdir'
|
||||
# directory is successfully created first before we actually test
|
||||
# 'mkdir -p' feature.
|
||||
if (umask $mkdir_umask &&
|
||||
$mkdirprog $mkdir_mode "$tmpdir" &&
|
||||
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
|
||||
then
|
||||
if test -z "$dir_arg" || {
|
||||
# Check for POSIX incompatibilities with -m.
|
||||
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
|
||||
# other-writable bit of parent directory when it shouldn't.
|
||||
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
|
||||
test_tmpdir="$tmpdir/a"
|
||||
ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
|
||||
case $ls_ld_tmpdir in
|
||||
d????-?r-*) different_mode=700;;
|
||||
d????-?--*) different_mode=755;;
|
||||
*) false;;
|
||||
esac &&
|
||||
$mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
|
||||
ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
|
||||
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
|
||||
}
|
||||
}
|
||||
then posix_mkdir=:
|
||||
fi
|
||||
rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
|
||||
else
|
||||
# Remove any dirs left behind by ancient mkdir implementations.
|
||||
rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
|
||||
fi
|
||||
trap '' 0;;
|
||||
esac;;
|
||||
esac
|
||||
|
||||
if
|
||||
$posix_mkdir && (
|
||||
umask $mkdir_umask &&
|
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
|
||||
)
|
||||
then :
|
||||
else
|
||||
|
||||
# The umask is ridiculous, or mkdir does not conform to POSIX,
|
||||
# or it failed possibly due to a race condition. Create the
|
||||
# directory the slow way, step by step, checking for races as we go.
|
||||
|
||||
case $dstdir in
|
||||
/*) prefix='/';;
|
||||
[-=\(\)!]*) prefix='./';;
|
||||
*) prefix='';;
|
||||
esac
|
||||
|
||||
oIFS=$IFS
|
||||
IFS=/
|
||||
set -f
|
||||
set fnord $dstdir
|
||||
shift
|
||||
set +f
|
||||
IFS=$oIFS
|
||||
|
||||
prefixes=
|
||||
|
||||
for d
|
||||
do
|
||||
test X"$d" = X && continue
|
||||
|
||||
prefix=$prefix$d
|
||||
if test -d "$prefix"; then
|
||||
prefixes=
|
||||
else
|
||||
if $posix_mkdir; then
|
||||
(umask=$mkdir_umask &&
|
||||
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
|
||||
# Don't fail if two instances are running concurrently.
|
||||
test -d "$prefix" || exit 1
|
||||
else
|
||||
case $prefix in
|
||||
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
|
||||
*) qprefix=$prefix;;
|
||||
esac
|
||||
prefixes="$prefixes '$qprefix'"
|
||||
fi
|
||||
fi
|
||||
prefix=$prefix/
|
||||
done
|
||||
|
||||
if test -n "$prefixes"; then
|
||||
# Don't fail if two instances are running concurrently.
|
||||
(umask $mkdir_umask &&
|
||||
eval "\$doit_exec \$mkdirprog $prefixes") ||
|
||||
test -d "$dstdir" || exit 1
|
||||
obsolete_mkdir_used=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -n "$dir_arg"; then
|
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
|
||||
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
|
||||
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
|
||||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
|
||||
else
|
||||
|
||||
# Make a couple of temp file names in the proper directory.
|
||||
dsttmp=${dstdirslash}_inst.$$_
|
||||
rmtmp=${dstdirslash}_rm.$$_
|
||||
|
||||
# Trap to clean up those temp files at exit.
|
||||
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
|
||||
|
||||
# Copy the file name to the temp name.
|
||||
(umask $cp_umask &&
|
||||
{ test -z "$stripcmd" || {
|
||||
# Create $dsttmp read-write so that cp doesn't create it read-only,
|
||||
# which would cause strip to fail.
|
||||
if test -z "$doit"; then
|
||||
: >"$dsttmp" # No need to fork-exec 'touch'.
|
||||
else
|
||||
$doit touch "$dsttmp"
|
||||
fi
|
||||
}
|
||||
} &&
|
||||
$doit_exec $cpprog "$src" "$dsttmp") &&
|
||||
|
||||
# and set any options; do chmod last to preserve setuid bits.
|
||||
#
|
||||
# If any of these fail, we abort the whole thing. If we want to
|
||||
# ignore errors from any of these, just make sure not to ignore
|
||||
# errors from the above "$doit $cpprog $src $dsttmp" command.
|
||||
#
|
||||
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
|
||||
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
|
||||
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
|
||||
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
|
||||
|
||||
# If -C, don't bother to copy if it wouldn't change the file.
|
||||
if $copy_on_change &&
|
||||
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
|
||||
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
|
||||
set -f &&
|
||||
set X $old && old=:$2:$4:$5:$6 &&
|
||||
set X $new && new=:$2:$4:$5:$6 &&
|
||||
set +f &&
|
||||
test "$old" = "$new" &&
|
||||
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
|
||||
then
|
||||
rm -f "$dsttmp"
|
||||
else
|
||||
# Rename the file to the real destination.
|
||||
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
|
||||
|
||||
# The rename failed, perhaps because mv can't rename something else
|
||||
# to itself, or perhaps because mv is so ancient that it does not
|
||||
# support -f.
|
||||
{
|
||||
# Now remove or move aside any old file at destination location.
|
||||
# We try this two ways since rm can't unlink itself on some
|
||||
# systems and the destination file might be busy for other
|
||||
# reasons. In this case, the final cleanup might fail but the new
|
||||
# file should still install successfully.
|
||||
{
|
||||
test ! -f "$dst" ||
|
||||
$doit $rmcmd -f "$dst" 2>/dev/null ||
|
||||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
|
||||
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
|
||||
} ||
|
||||
{ echo "$0: cannot unlink or rename $dst" >&2
|
||||
(exit 1); exit 1
|
||||
}
|
||||
} &&
|
||||
|
||||
# Now rename the file to the real destination.
|
||||
$doit $mvcmd "$dsttmp" "$dst"
|
||||
}
|
||||
fi || exit 1
|
||||
|
||||
trap '' 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Local variables:
|
||||
# eval: (add-hook 'before-save-hook 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC0"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
|
@ -0,0 +1,11 @@
|
|||
2019-12-26 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* gettext.m4: New file, from gettext-0.19.8.1.
|
||||
* iconv.m4: New file, from gettext-0.19.8.1.
|
||||
* lib-ld.m4: New file, from gettext-0.19.8.1.
|
||||
* lib-link.m4: New file, from gettext-0.19.8.1.
|
||||
* lib-prefix.m4: New file, from gettext-0.19.8.1.
|
||||
* nls.m4: New file, from gettext-0.19.8.1.
|
||||
* po.m4: New file, from gettext-0.19.8.1.
|
||||
* progtest.m4: New file, from gettext-0.19.8.1.
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
# gettext.m4 serial 68 (gettext-0.19.8)
|
||||
dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
dnl
|
||||
dnl This file can be used in projects which are not available under
|
||||
dnl the GNU General Public License or the GNU Library General Public
|
||||
dnl License but which still want to provide support for the GNU gettext
|
||||
dnl functionality.
|
||||
dnl Please note that the actual code of the GNU gettext library is covered
|
||||
dnl by the GNU Library General Public License, and the rest of the GNU
|
||||
dnl gettext package is covered by the GNU General Public License.
|
||||
dnl They are *not* in the public domain.
|
||||
|
||||
dnl Authors:
|
||||
dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
|
||||
dnl Bruno Haible <haible@clisp.cons.org>, 2000-2006, 2008-2010.
|
||||
|
||||
dnl Macro to add for using GNU gettext.
|
||||
|
||||
dnl Usage: AM_GNU_GETTEXT([INTLSYMBOL], [NEEDSYMBOL], [INTLDIR]).
|
||||
dnl INTLSYMBOL can be one of 'external', 'no-libtool', 'use-libtool'. The
|
||||
dnl default (if it is not specified or empty) is 'no-libtool'.
|
||||
dnl INTLSYMBOL should be 'external' for packages with no intl directory,
|
||||
dnl and 'no-libtool' or 'use-libtool' for packages with an intl directory.
|
||||
dnl If INTLSYMBOL is 'use-libtool', then a libtool library
|
||||
dnl $(top_builddir)/intl/libintl.la will be created (shared and/or static,
|
||||
dnl depending on --{enable,disable}-{shared,static} and on the presence of
|
||||
dnl AM-DISABLE-SHARED). If INTLSYMBOL is 'no-libtool', a static library
|
||||
dnl $(top_builddir)/intl/libintl.a will be created.
|
||||
dnl If NEEDSYMBOL is specified and is 'need-ngettext', then GNU gettext
|
||||
dnl implementations (in libc or libintl) without the ngettext() function
|
||||
dnl will be ignored. If NEEDSYMBOL is specified and is
|
||||
dnl 'need-formatstring-macros', then GNU gettext implementations that don't
|
||||
dnl support the ISO C 99 <inttypes.h> formatstring macros will be ignored.
|
||||
dnl INTLDIR is used to find the intl libraries. If empty,
|
||||
dnl the value '$(top_builddir)/intl/' is used.
|
||||
dnl
|
||||
dnl The result of the configuration is one of three cases:
|
||||
dnl 1) GNU gettext, as included in the intl subdirectory, will be compiled
|
||||
dnl and used.
|
||||
dnl Catalog format: GNU --> install in $(datadir)
|
||||
dnl Catalog extension: .mo after installation, .gmo in source tree
|
||||
dnl 2) GNU gettext has been found in the system's C library.
|
||||
dnl Catalog format: GNU --> install in $(datadir)
|
||||
dnl Catalog extension: .mo after installation, .gmo in source tree
|
||||
dnl 3) No internationalization, always use English msgid.
|
||||
dnl Catalog format: none
|
||||
dnl Catalog extension: none
|
||||
dnl If INTLSYMBOL is 'external', only cases 2 and 3 can occur.
|
||||
dnl The use of .gmo is historical (it was needed to avoid overwriting the
|
||||
dnl GNU format catalogs when building on a platform with an X/Open gettext),
|
||||
dnl but we keep it in order not to force irrelevant filename changes on the
|
||||
dnl maintainers.
|
||||
dnl
|
||||
AC_DEFUN([AM_GNU_GETTEXT],
|
||||
[
|
||||
dnl Argument checking.
|
||||
ifelse([$1], [], , [ifelse([$1], [external], , [ifelse([$1], [no-libtool], , [ifelse([$1], [use-libtool], ,
|
||||
[errprint([ERROR: invalid first argument to AM_GNU_GETTEXT
|
||||
])])])])])
|
||||
ifelse(ifelse([$1], [], [old])[]ifelse([$1], [no-libtool], [old]), [old],
|
||||
[AC_DIAGNOSE([obsolete], [Use of AM_GNU_GETTEXT without [external] argument is deprecated.])])
|
||||
ifelse([$2], [], , [ifelse([$2], [need-ngettext], , [ifelse([$2], [need-formatstring-macros], ,
|
||||
[errprint([ERROR: invalid second argument to AM_GNU_GETTEXT
|
||||
])])])])
|
||||
define([gt_included_intl],
|
||||
ifelse([$1], [external],
|
||||
ifdef([AM_GNU_GETTEXT_][INTL_SUBDIR], [yes], [no]),
|
||||
[yes]))
|
||||
define([gt_libtool_suffix_prefix], ifelse([$1], [use-libtool], [l], []))
|
||||
gt_NEEDS_INIT
|
||||
AM_GNU_GETTEXT_NEED([$2])
|
||||
|
||||
AC_REQUIRE([AM_PO_SUBDIRS])dnl
|
||||
ifelse(gt_included_intl, yes, [
|
||||
AC_REQUIRE([AM_INTL_SUBDIR])dnl
|
||||
])
|
||||
|
||||
dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
|
||||
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
|
||||
AC_REQUIRE([AC_LIB_RPATH])
|
||||
|
||||
dnl Sometimes libintl requires libiconv, so first search for libiconv.
|
||||
dnl Ideally we would do this search only after the
|
||||
dnl if test "$USE_NLS" = "yes"; then
|
||||
dnl if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then
|
||||
dnl tests. But if configure.in invokes AM_ICONV after AM_GNU_GETTEXT
|
||||
dnl the configure script would need to contain the same shell code
|
||||
dnl again, outside any 'if'. There are two solutions:
|
||||
dnl - Invoke AM_ICONV_LINKFLAGS_BODY here, outside any 'if'.
|
||||
dnl - Control the expansions in more detail using AC_PROVIDE_IFELSE.
|
||||
dnl Since AC_PROVIDE_IFELSE is only in autoconf >= 2.52 and not
|
||||
dnl documented, we avoid it.
|
||||
ifelse(gt_included_intl, yes, , [
|
||||
AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
|
||||
])
|
||||
|
||||
dnl Sometimes, on Mac OS X, libintl requires linking with CoreFoundation.
|
||||
gt_INTL_MACOSX
|
||||
|
||||
dnl Set USE_NLS.
|
||||
AC_REQUIRE([AM_NLS])
|
||||
|
||||
ifelse(gt_included_intl, yes, [
|
||||
BUILD_INCLUDED_LIBINTL=no
|
||||
USE_INCLUDED_LIBINTL=no
|
||||
])
|
||||
LIBINTL=
|
||||
LTLIBINTL=
|
||||
POSUB=
|
||||
|
||||
dnl Add a version number to the cache macros.
|
||||
case " $gt_needs " in
|
||||
*" need-formatstring-macros "*) gt_api_version=3 ;;
|
||||
*" need-ngettext "*) gt_api_version=2 ;;
|
||||
*) gt_api_version=1 ;;
|
||||
esac
|
||||
gt_func_gnugettext_libc="gt_cv_func_gnugettext${gt_api_version}_libc"
|
||||
gt_func_gnugettext_libintl="gt_cv_func_gnugettext${gt_api_version}_libintl"
|
||||
|
||||
dnl If we use NLS figure out what method
|
||||
if test "$USE_NLS" = "yes"; then
|
||||
gt_use_preinstalled_gnugettext=no
|
||||
ifelse(gt_included_intl, yes, [
|
||||
AC_MSG_CHECKING([whether included gettext is requested])
|
||||
AC_ARG_WITH([included-gettext],
|
||||
[ --with-included-gettext use the GNU gettext library included here],
|
||||
nls_cv_force_use_gnu_gettext=$withval,
|
||||
nls_cv_force_use_gnu_gettext=no)
|
||||
AC_MSG_RESULT([$nls_cv_force_use_gnu_gettext])
|
||||
|
||||
nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext"
|
||||
if test "$nls_cv_force_use_gnu_gettext" != "yes"; then
|
||||
])
|
||||
dnl User does not insist on using GNU NLS library. Figure out what
|
||||
dnl to use. If GNU gettext is available we use this. Else we have
|
||||
dnl to fall back to GNU NLS library.
|
||||
|
||||
if test $gt_api_version -ge 3; then
|
||||
gt_revision_test_code='
|
||||
#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
|
||||
#define __GNU_GETTEXT_SUPPORTED_REVISION(major) ((major) == 0 ? 0 : -1)
|
||||
#endif
|
||||
changequote(,)dnl
|
||||
typedef int array [2 * (__GNU_GETTEXT_SUPPORTED_REVISION(0) >= 1) - 1];
|
||||
changequote([,])dnl
|
||||
'
|
||||
else
|
||||
gt_revision_test_code=
|
||||
fi
|
||||
if test $gt_api_version -ge 2; then
|
||||
gt_expression_test_code=' + * ngettext ("", "", 0)'
|
||||
else
|
||||
gt_expression_test_code=
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for GNU gettext in libc], [$gt_func_gnugettext_libc],
|
||||
[AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <libintl.h>
|
||||
#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
|
||||
extern int _nl_msg_cat_cntr;
|
||||
extern int *_nl_domain_bindings;
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_domain_bindings)
|
||||
#else
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
|
||||
#endif
|
||||
$gt_revision_test_code
|
||||
]],
|
||||
[[
|
||||
bindtextdomain ("", "");
|
||||
return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
|
||||
]])],
|
||||
[eval "$gt_func_gnugettext_libc=yes"],
|
||||
[eval "$gt_func_gnugettext_libc=no"])])
|
||||
|
||||
if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then
|
||||
dnl Sometimes libintl requires libiconv, so first search for libiconv.
|
||||
ifelse(gt_included_intl, yes, , [
|
||||
AM_ICONV_LINK
|
||||
])
|
||||
dnl Search for libintl and define LIBINTL, LTLIBINTL and INCINTL
|
||||
dnl accordingly. Don't use AC_LIB_LINKFLAGS_BODY([intl],[iconv])
|
||||
dnl because that would add "-liconv" to LIBINTL and LTLIBINTL
|
||||
dnl even if libiconv doesn't exist.
|
||||
AC_LIB_LINKFLAGS_BODY([intl])
|
||||
AC_CACHE_CHECK([for GNU gettext in libintl],
|
||||
[$gt_func_gnugettext_libintl],
|
||||
[gt_save_CPPFLAGS="$CPPFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $INCINTL"
|
||||
gt_save_LIBS="$LIBS"
|
||||
LIBS="$LIBS $LIBINTL"
|
||||
dnl Now see whether libintl exists and does not depend on libiconv.
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <libintl.h>
|
||||
#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
|
||||
extern int _nl_msg_cat_cntr;
|
||||
extern
|
||||
#ifdef __cplusplus
|
||||
"C"
|
||||
#endif
|
||||
const char *_nl_expand_alias (const char *);
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias (""))
|
||||
#else
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
|
||||
#endif
|
||||
$gt_revision_test_code
|
||||
]],
|
||||
[[
|
||||
bindtextdomain ("", "");
|
||||
return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
|
||||
]])],
|
||||
[eval "$gt_func_gnugettext_libintl=yes"],
|
||||
[eval "$gt_func_gnugettext_libintl=no"])
|
||||
dnl Now see whether libintl exists and depends on libiconv.
|
||||
if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" != yes; } && test -n "$LIBICONV"; then
|
||||
LIBS="$LIBS $LIBICONV"
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <libintl.h>
|
||||
#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
|
||||
extern int _nl_msg_cat_cntr;
|
||||
extern
|
||||
#ifdef __cplusplus
|
||||
"C"
|
||||
#endif
|
||||
const char *_nl_expand_alias (const char *);
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias (""))
|
||||
#else
|
||||
#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
|
||||
#endif
|
||||
$gt_revision_test_code
|
||||
]],
|
||||
[[
|
||||
bindtextdomain ("", "");
|
||||
return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
|
||||
]])],
|
||||
[LIBINTL="$LIBINTL $LIBICONV"
|
||||
LTLIBINTL="$LTLIBINTL $LTLIBICONV"
|
||||
eval "$gt_func_gnugettext_libintl=yes"
|
||||
])
|
||||
fi
|
||||
CPPFLAGS="$gt_save_CPPFLAGS"
|
||||
LIBS="$gt_save_LIBS"])
|
||||
fi
|
||||
|
||||
dnl If an already present or preinstalled GNU gettext() is found,
|
||||
dnl use it. But if this macro is used in GNU gettext, and GNU
|
||||
dnl gettext is already preinstalled in libintl, we update this
|
||||
dnl libintl. (Cf. the install rule in intl/Makefile.in.)
|
||||
if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" = "yes"; } \
|
||||
|| { { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; } \
|
||||
&& test "$PACKAGE" != gettext-runtime \
|
||||
&& test "$PACKAGE" != gettext-tools; }; then
|
||||
gt_use_preinstalled_gnugettext=yes
|
||||
else
|
||||
dnl Reset the values set by searching for libintl.
|
||||
LIBINTL=
|
||||
LTLIBINTL=
|
||||
INCINTL=
|
||||
fi
|
||||
|
||||
ifelse(gt_included_intl, yes, [
|
||||
if test "$gt_use_preinstalled_gnugettext" != "yes"; then
|
||||
dnl GNU gettext is not found in the C library.
|
||||
dnl Fall back on included GNU gettext library.
|
||||
nls_cv_use_gnu_gettext=yes
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$nls_cv_use_gnu_gettext" = "yes"; then
|
||||
dnl Mark actions used to generate GNU NLS library.
|
||||
BUILD_INCLUDED_LIBINTL=yes
|
||||
USE_INCLUDED_LIBINTL=yes
|
||||
LIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LIBICONV $LIBTHREAD"
|
||||
LTLIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LTLIBICONV $LTLIBTHREAD"
|
||||
LIBS=`echo " $LIBS " | sed -e 's/ -lintl / /' -e 's/^ //' -e 's/ $//'`
|
||||
fi
|
||||
|
||||
CATOBJEXT=
|
||||
if test "$gt_use_preinstalled_gnugettext" = "yes" \
|
||||
|| test "$nls_cv_use_gnu_gettext" = "yes"; then
|
||||
dnl Mark actions to use GNU gettext tools.
|
||||
CATOBJEXT=.gmo
|
||||
fi
|
||||
])
|
||||
|
||||
if test -n "$INTL_MACOSX_LIBS"; then
|
||||
if test "$gt_use_preinstalled_gnugettext" = "yes" \
|
||||
|| test "$nls_cv_use_gnu_gettext" = "yes"; then
|
||||
dnl Some extra flags are needed during linking.
|
||||
LIBINTL="$LIBINTL $INTL_MACOSX_LIBS"
|
||||
LTLIBINTL="$LTLIBINTL $INTL_MACOSX_LIBS"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$gt_use_preinstalled_gnugettext" = "yes" \
|
||||
|| test "$nls_cv_use_gnu_gettext" = "yes"; then
|
||||
AC_DEFINE([ENABLE_NLS], [1],
|
||||
[Define to 1 if translation of program messages to the user's native language
|
||||
is requested.])
|
||||
else
|
||||
USE_NLS=no
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([whether to use NLS])
|
||||
AC_MSG_RESULT([$USE_NLS])
|
||||
if test "$USE_NLS" = "yes"; then
|
||||
AC_MSG_CHECKING([where the gettext function comes from])
|
||||
if test "$gt_use_preinstalled_gnugettext" = "yes"; then
|
||||
if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then
|
||||
gt_source="external libintl"
|
||||
else
|
||||
gt_source="libc"
|
||||
fi
|
||||
else
|
||||
gt_source="included intl directory"
|
||||
fi
|
||||
AC_MSG_RESULT([$gt_source])
|
||||
fi
|
||||
|
||||
if test "$USE_NLS" = "yes"; then
|
||||
|
||||
if test "$gt_use_preinstalled_gnugettext" = "yes"; then
|
||||
if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then
|
||||
AC_MSG_CHECKING([how to link with libintl])
|
||||
AC_MSG_RESULT([$LIBINTL])
|
||||
AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCINTL])
|
||||
fi
|
||||
|
||||
dnl For backward compatibility. Some packages may be using this.
|
||||
AC_DEFINE([HAVE_GETTEXT], [1],
|
||||
[Define if the GNU gettext() function is already present or preinstalled.])
|
||||
AC_DEFINE([HAVE_DCGETTEXT], [1],
|
||||
[Define if the GNU dcgettext() function is already present or preinstalled.])
|
||||
fi
|
||||
|
||||
dnl We need to process the po/ directory.
|
||||
POSUB=po
|
||||
fi
|
||||
|
||||
ifelse(gt_included_intl, yes, [
|
||||
dnl If this is used in GNU gettext we have to set BUILD_INCLUDED_LIBINTL
|
||||
dnl to 'yes' because some of the testsuite requires it.
|
||||
if test "$PACKAGE" = gettext-runtime || test "$PACKAGE" = gettext-tools; then
|
||||
BUILD_INCLUDED_LIBINTL=yes
|
||||
fi
|
||||
|
||||
dnl Make all variables we use known to autoconf.
|
||||
AC_SUBST([BUILD_INCLUDED_LIBINTL])
|
||||
AC_SUBST([USE_INCLUDED_LIBINTL])
|
||||
AC_SUBST([CATOBJEXT])
|
||||
|
||||
dnl For backward compatibility. Some configure.ins may be using this.
|
||||
nls_cv_header_intl=
|
||||
nls_cv_header_libgt=
|
||||
|
||||
dnl For backward compatibility. Some Makefiles may be using this.
|
||||
DATADIRNAME=share
|
||||
AC_SUBST([DATADIRNAME])
|
||||
|
||||
dnl For backward compatibility. Some Makefiles may be using this.
|
||||
INSTOBJEXT=.mo
|
||||
AC_SUBST([INSTOBJEXT])
|
||||
|
||||
dnl For backward compatibility. Some Makefiles may be using this.
|
||||
GENCAT=gencat
|
||||
AC_SUBST([GENCAT])
|
||||
|
||||
dnl For backward compatibility. Some Makefiles may be using this.
|
||||
INTLOBJS=
|
||||
if test "$USE_INCLUDED_LIBINTL" = yes; then
|
||||
INTLOBJS="\$(GETTOBJS)"
|
||||
fi
|
||||
AC_SUBST([INTLOBJS])
|
||||
|
||||
dnl Enable libtool support if the surrounding package wishes it.
|
||||
INTL_LIBTOOL_SUFFIX_PREFIX=gt_libtool_suffix_prefix
|
||||
AC_SUBST([INTL_LIBTOOL_SUFFIX_PREFIX])
|
||||
])
|
||||
|
||||
dnl For backward compatibility. Some Makefiles may be using this.
|
||||
INTLLIBS="$LIBINTL"
|
||||
AC_SUBST([INTLLIBS])
|
||||
|
||||
dnl Make all documented variables known to autoconf.
|
||||
AC_SUBST([LIBINTL])
|
||||
AC_SUBST([LTLIBINTL])
|
||||
AC_SUBST([POSUB])
|
||||
])
|
||||
|
||||
|
||||
dnl gt_NEEDS_INIT ensures that the gt_needs variable is initialized.
|
||||
m4_define([gt_NEEDS_INIT],
|
||||
[
|
||||
m4_divert_text([DEFAULTS], [gt_needs=])
|
||||
m4_define([gt_NEEDS_INIT], [])
|
||||
])
|
||||
|
||||
|
||||
dnl Usage: AM_GNU_GETTEXT_NEED([NEEDSYMBOL])
|
||||
AC_DEFUN([AM_GNU_GETTEXT_NEED],
|
||||
[
|
||||
m4_divert_text([INIT_PREPARE], [gt_needs="$gt_needs $1"])
|
||||
])
|
||||
|
||||
|
||||
dnl Usage: AM_GNU_GETTEXT_VERSION([gettext-version])
|
||||
AC_DEFUN([AM_GNU_GETTEXT_VERSION], [])
|
||||
|
||||
|
||||
dnl Usage: AM_GNU_GETTEXT_REQUIRE_VERSION([gettext-version])
|
||||
AC_DEFUN([AM_GNU_GETTEXT_REQUIRE_VERSION], [])
|
|
@ -0,0 +1,271 @@
|
|||
# iconv.m4 serial 19 (gettext-0.18.2)
|
||||
dnl Copyright (C) 2000-2002, 2007-2014, 2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
|
||||
dnl From Bruno Haible.
|
||||
|
||||
AC_DEFUN([AM_ICONV_LINKFLAGS_BODY],
|
||||
[
|
||||
dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
|
||||
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
|
||||
AC_REQUIRE([AC_LIB_RPATH])
|
||||
|
||||
dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
|
||||
dnl accordingly.
|
||||
AC_LIB_LINKFLAGS_BODY([iconv])
|
||||
])
|
||||
|
||||
AC_DEFUN([AM_ICONV_LINK],
|
||||
[
|
||||
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
|
||||
dnl those with the standalone portable GNU libiconv installed).
|
||||
AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
|
||||
|
||||
dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
|
||||
dnl accordingly.
|
||||
AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
|
||||
|
||||
dnl Add $INCICONV to CPPFLAGS before performing the following checks,
|
||||
dnl because if the user has installed libiconv and not disabled its use
|
||||
dnl via --without-libiconv-prefix, he wants to use it. The first
|
||||
dnl AC_LINK_IFELSE will then fail, the second AC_LINK_IFELSE will succeed.
|
||||
am_save_CPPFLAGS="$CPPFLAGS"
|
||||
AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV])
|
||||
|
||||
AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [
|
||||
am_cv_func_iconv="no, consider installing GNU libiconv"
|
||||
am_cv_lib_iconv=no
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <stdlib.h>
|
||||
#include <iconv.h>
|
||||
]],
|
||||
[[iconv_t cd = iconv_open("","");
|
||||
iconv(cd,NULL,NULL,NULL,NULL);
|
||||
iconv_close(cd);]])],
|
||||
[am_cv_func_iconv=yes])
|
||||
if test "$am_cv_func_iconv" != yes; then
|
||||
am_save_LIBS="$LIBS"
|
||||
LIBS="$LIBS $LIBICONV"
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <stdlib.h>
|
||||
#include <iconv.h>
|
||||
]],
|
||||
[[iconv_t cd = iconv_open("","");
|
||||
iconv(cd,NULL,NULL,NULL,NULL);
|
||||
iconv_close(cd);]])],
|
||||
[am_cv_lib_iconv=yes]
|
||||
[am_cv_func_iconv=yes])
|
||||
LIBS="$am_save_LIBS"
|
||||
fi
|
||||
])
|
||||
if test "$am_cv_func_iconv" = yes; then
|
||||
AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [
|
||||
dnl This tests against bugs in AIX 5.1, AIX 6.1..7.1, HP-UX 11.11,
|
||||
dnl Solaris 10.
|
||||
am_save_LIBS="$LIBS"
|
||||
if test $am_cv_lib_iconv = yes; then
|
||||
LIBS="$LIBS $LIBICONV"
|
||||
fi
|
||||
am_cv_func_iconv_works=no
|
||||
for ac_iconv_const in '' 'const'; do
|
||||
AC_RUN_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <iconv.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef ICONV_CONST
|
||||
# define ICONV_CONST $ac_iconv_const
|
||||
#endif
|
||||
]],
|
||||
[[int result = 0;
|
||||
/* Test against AIX 5.1 bug: Failures are not distinguishable from successful
|
||||
returns. */
|
||||
{
|
||||
iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8");
|
||||
if (cd_utf8_to_88591 != (iconv_t)(-1))
|
||||
{
|
||||
static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */
|
||||
char buf[10];
|
||||
ICONV_CONST char *inptr = input;
|
||||
size_t inbytesleft = strlen (input);
|
||||
char *outptr = buf;
|
||||
size_t outbytesleft = sizeof (buf);
|
||||
size_t res = iconv (cd_utf8_to_88591,
|
||||
&inptr, &inbytesleft,
|
||||
&outptr, &outbytesleft);
|
||||
if (res == 0)
|
||||
result |= 1;
|
||||
iconv_close (cd_utf8_to_88591);
|
||||
}
|
||||
}
|
||||
/* Test against Solaris 10 bug: Failures are not distinguishable from
|
||||
successful returns. */
|
||||
{
|
||||
iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646");
|
||||
if (cd_ascii_to_88591 != (iconv_t)(-1))
|
||||
{
|
||||
static ICONV_CONST char input[] = "\263";
|
||||
char buf[10];
|
||||
ICONV_CONST char *inptr = input;
|
||||
size_t inbytesleft = strlen (input);
|
||||
char *outptr = buf;
|
||||
size_t outbytesleft = sizeof (buf);
|
||||
size_t res = iconv (cd_ascii_to_88591,
|
||||
&inptr, &inbytesleft,
|
||||
&outptr, &outbytesleft);
|
||||
if (res == 0)
|
||||
result |= 2;
|
||||
iconv_close (cd_ascii_to_88591);
|
||||
}
|
||||
}
|
||||
/* Test against AIX 6.1..7.1 bug: Buffer overrun. */
|
||||
{
|
||||
iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1");
|
||||
if (cd_88591_to_utf8 != (iconv_t)(-1))
|
||||
{
|
||||
static ICONV_CONST char input[] = "\304";
|
||||
static char buf[2] = { (char)0xDE, (char)0xAD };
|
||||
ICONV_CONST char *inptr = input;
|
||||
size_t inbytesleft = 1;
|
||||
char *outptr = buf;
|
||||
size_t outbytesleft = 1;
|
||||
size_t res = iconv (cd_88591_to_utf8,
|
||||
&inptr, &inbytesleft,
|
||||
&outptr, &outbytesleft);
|
||||
if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD)
|
||||
result |= 4;
|
||||
iconv_close (cd_88591_to_utf8);
|
||||
}
|
||||
}
|
||||
#if 0 /* This bug could be worked around by the caller. */
|
||||
/* Test against HP-UX 11.11 bug: Positive return value instead of 0. */
|
||||
{
|
||||
iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591");
|
||||
if (cd_88591_to_utf8 != (iconv_t)(-1))
|
||||
{
|
||||
static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337";
|
||||
char buf[50];
|
||||
ICONV_CONST char *inptr = input;
|
||||
size_t inbytesleft = strlen (input);
|
||||
char *outptr = buf;
|
||||
size_t outbytesleft = sizeof (buf);
|
||||
size_t res = iconv (cd_88591_to_utf8,
|
||||
&inptr, &inbytesleft,
|
||||
&outptr, &outbytesleft);
|
||||
if ((int)res > 0)
|
||||
result |= 8;
|
||||
iconv_close (cd_88591_to_utf8);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is
|
||||
provided. */
|
||||
if (/* Try standardized names. */
|
||||
iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1)
|
||||
/* Try IRIX, OSF/1 names. */
|
||||
&& iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1)
|
||||
/* Try AIX names. */
|
||||
&& iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1)
|
||||
/* Try HP-UX names. */
|
||||
&& iconv_open ("utf8", "eucJP") == (iconv_t)(-1))
|
||||
result |= 16;
|
||||
return result;
|
||||
]])],
|
||||
[am_cv_func_iconv_works=yes], ,
|
||||
[case "$host_os" in
|
||||
aix* | hpux*) am_cv_func_iconv_works="guessing no" ;;
|
||||
*) am_cv_func_iconv_works="guessing yes" ;;
|
||||
esac])
|
||||
test "$am_cv_func_iconv_works" = no || break
|
||||
done
|
||||
LIBS="$am_save_LIBS"
|
||||
])
|
||||
case "$am_cv_func_iconv_works" in
|
||||
*no) am_func_iconv=no am_cv_lib_iconv=no ;;
|
||||
*) am_func_iconv=yes ;;
|
||||
esac
|
||||
else
|
||||
am_func_iconv=no am_cv_lib_iconv=no
|
||||
fi
|
||||
if test "$am_func_iconv" = yes; then
|
||||
AC_DEFINE([HAVE_ICONV], [1],
|
||||
[Define if you have the iconv() function and it works.])
|
||||
fi
|
||||
if test "$am_cv_lib_iconv" = yes; then
|
||||
AC_MSG_CHECKING([how to link with libiconv])
|
||||
AC_MSG_RESULT([$LIBICONV])
|
||||
else
|
||||
dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV
|
||||
dnl either.
|
||||
CPPFLAGS="$am_save_CPPFLAGS"
|
||||
LIBICONV=
|
||||
LTLIBICONV=
|
||||
fi
|
||||
AC_SUBST([LIBICONV])
|
||||
AC_SUBST([LTLIBICONV])
|
||||
])
|
||||
|
||||
dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to
|
||||
dnl avoid warnings like
|
||||
dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required".
|
||||
dnl This is tricky because of the way 'aclocal' is implemented:
|
||||
dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN.
|
||||
dnl Otherwise aclocal's initial scan pass would miss the macro definition.
|
||||
dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN expansions.
|
||||
dnl Otherwise aclocal would emit many "Use of uninitialized value $1"
|
||||
dnl warnings.
|
||||
m4_define([gl_iconv_AC_DEFUN],
|
||||
m4_version_prereq([2.64],
|
||||
[[AC_DEFUN_ONCE(
|
||||
[$1], [$2])]],
|
||||
[m4_ifdef([gl_00GNULIB],
|
||||
[[AC_DEFUN_ONCE(
|
||||
[$1], [$2])]],
|
||||
[[AC_DEFUN(
|
||||
[$1], [$2])]])]))
|
||||
gl_iconv_AC_DEFUN([AM_ICONV],
|
||||
[
|
||||
AM_ICONV_LINK
|
||||
if test "$am_cv_func_iconv" = yes; then
|
||||
AC_MSG_CHECKING([for iconv declaration])
|
||||
AC_CACHE_VAL([am_cv_proto_iconv], [
|
||||
AC_COMPILE_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[[
|
||||
#include <stdlib.h>
|
||||
#include <iconv.h>
|
||||
extern
|
||||
#ifdef __cplusplus
|
||||
"C"
|
||||
#endif
|
||||
#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus)
|
||||
size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
|
||||
#else
|
||||
size_t iconv();
|
||||
#endif
|
||||
]],
|
||||
[[]])],
|
||||
[am_cv_proto_iconv_arg1=""],
|
||||
[am_cv_proto_iconv_arg1="const"])
|
||||
am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"])
|
||||
am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
|
||||
AC_MSG_RESULT([
|
||||
$am_cv_proto_iconv])
|
||||
AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1],
|
||||
[Define as const if the declaration of iconv() needs const.])
|
||||
dnl Also substitute ICONV_CONST in the gnulib generated <iconv.h>.
|
||||
m4_ifdef([gl_ICONV_H_DEFAULTS],
|
||||
[AC_REQUIRE([gl_ICONV_H_DEFAULTS])
|
||||
if test -n "$am_cv_proto_iconv_arg1"; then
|
||||
ICONV_CONST="const"
|
||||
fi
|
||||
])
|
||||
fi
|
||||
])
|
|
@ -0,0 +1,119 @@
|
|||
# lib-ld.m4 serial 6
|
||||
dnl Copyright (C) 1996-2003, 2009-2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
|
||||
dnl Subroutines of libtool.m4,
|
||||
dnl with replacements s/_*LT_PATH/AC_LIB_PROG/ and s/lt_/acl_/ to avoid
|
||||
dnl collision with libtool.m4.
|
||||
|
||||
dnl From libtool-2.4. Sets the variable with_gnu_ld to yes or no.
|
||||
AC_DEFUN([AC_LIB_PROG_LD_GNU],
|
||||
[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld],
|
||||
[# I'd rather use --version here, but apparently some GNU lds only accept -v.
|
||||
case `$LD -v 2>&1 </dev/null` in
|
||||
*GNU* | *'with BFD'*)
|
||||
acl_cv_prog_gnu_ld=yes
|
||||
;;
|
||||
*)
|
||||
acl_cv_prog_gnu_ld=no
|
||||
;;
|
||||
esac])
|
||||
with_gnu_ld=$acl_cv_prog_gnu_ld
|
||||
])
|
||||
|
||||
dnl From libtool-2.4. Sets the variable LD.
|
||||
AC_DEFUN([AC_LIB_PROG_LD],
|
||||
[AC_REQUIRE([AC_PROG_CC])dnl
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])dnl
|
||||
|
||||
AC_ARG_WITH([gnu-ld],
|
||||
[AS_HELP_STRING([--with-gnu-ld],
|
||||
[assume the C compiler uses GNU ld [default=no]])],
|
||||
[test "$withval" = no || with_gnu_ld=yes],
|
||||
[with_gnu_ld=no])dnl
|
||||
|
||||
# Prepare PATH_SEPARATOR.
|
||||
# The user is always right.
|
||||
if test "${PATH_SEPARATOR+set}" != set; then
|
||||
# Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
|
||||
# contains only /bin. Note that ksh looks also at the FPATH variable,
|
||||
# so we have to set that as well for the test.
|
||||
PATH_SEPARATOR=:
|
||||
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
|
||||
&& { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
|
||||
|| PATH_SEPARATOR=';'
|
||||
}
|
||||
fi
|
||||
|
||||
ac_prog=ld
|
||||
if test "$GCC" = yes; then
|
||||
# Check if gcc -print-prog-name=ld gives a path.
|
||||
AC_MSG_CHECKING([for ld used by $CC])
|
||||
case $host in
|
||||
*-*-mingw*)
|
||||
# gcc leaves a trailing carriage return which upsets mingw
|
||||
ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
|
||||
*)
|
||||
ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
|
||||
esac
|
||||
case $ac_prog in
|
||||
# Accept absolute paths.
|
||||
[[\\/]]* | ?:[[\\/]]*)
|
||||
re_direlt='/[[^/]][[^/]]*/\.\./'
|
||||
# Canonicalize the pathname of ld
|
||||
ac_prog=`echo "$ac_prog"| sed 's%\\\\%/%g'`
|
||||
while echo "$ac_prog" | grep "$re_direlt" > /dev/null 2>&1; do
|
||||
ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
|
||||
done
|
||||
test -z "$LD" && LD="$ac_prog"
|
||||
;;
|
||||
"")
|
||||
# If it fails, then pretend we aren't using GCC.
|
||||
ac_prog=ld
|
||||
;;
|
||||
*)
|
||||
# If it is relative, then search for the first ld in PATH.
|
||||
with_gnu_ld=unknown
|
||||
;;
|
||||
esac
|
||||
elif test "$with_gnu_ld" = yes; then
|
||||
AC_MSG_CHECKING([for GNU ld])
|
||||
else
|
||||
AC_MSG_CHECKING([for non-GNU ld])
|
||||
fi
|
||||
AC_CACHE_VAL([acl_cv_path_LD],
|
||||
[if test -z "$LD"; then
|
||||
acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
|
||||
for ac_dir in $PATH; do
|
||||
IFS="$acl_save_ifs"
|
||||
test -z "$ac_dir" && ac_dir=.
|
||||
if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
|
||||
acl_cv_path_LD="$ac_dir/$ac_prog"
|
||||
# Check to see if the program is GNU ld. I'd rather use --version,
|
||||
# but apparently some variants of GNU ld only accept -v.
|
||||
# Break only if it was the GNU/non-GNU ld that we prefer.
|
||||
case `"$acl_cv_path_LD" -v 2>&1 </dev/null` in
|
||||
*GNU* | *'with BFD'*)
|
||||
test "$with_gnu_ld" != no && break
|
||||
;;
|
||||
*)
|
||||
test "$with_gnu_ld" != yes && break
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
IFS="$acl_save_ifs"
|
||||
else
|
||||
acl_cv_path_LD="$LD" # Let the user override the test with a path.
|
||||
fi])
|
||||
LD="$acl_cv_path_LD"
|
||||
if test -n "$LD"; then
|
||||
AC_MSG_RESULT([$LD])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
|
||||
AC_LIB_PROG_LD_GNU
|
||||
])
|
|
@ -0,0 +1,777 @@
|
|||
# lib-link.m4 serial 26 (gettext-0.18.2)
|
||||
dnl Copyright (C) 2001-2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
|
||||
dnl From Bruno Haible.
|
||||
|
||||
AC_PREREQ([2.54])
|
||||
|
||||
dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and
|
||||
dnl the libraries corresponding to explicit and implicit dependencies.
|
||||
dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and
|
||||
dnl augments the CPPFLAGS variable.
|
||||
dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
|
||||
dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
|
||||
AC_DEFUN([AC_LIB_LINKFLAGS],
|
||||
[
|
||||
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
|
||||
AC_REQUIRE([AC_LIB_RPATH])
|
||||
pushdef([Name],[m4_translit([$1],[./+-], [____])])
|
||||
pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [
|
||||
AC_LIB_LINKFLAGS_BODY([$1], [$2])
|
||||
ac_cv_lib[]Name[]_libs="$LIB[]NAME"
|
||||
ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME"
|
||||
ac_cv_lib[]Name[]_cppflags="$INC[]NAME"
|
||||
ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX"
|
||||
])
|
||||
LIB[]NAME="$ac_cv_lib[]Name[]_libs"
|
||||
LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs"
|
||||
INC[]NAME="$ac_cv_lib[]Name[]_cppflags"
|
||||
LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix"
|
||||
AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
|
||||
AC_SUBST([LIB]NAME)
|
||||
AC_SUBST([LTLIB]NAME)
|
||||
AC_SUBST([LIB]NAME[_PREFIX])
|
||||
dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the
|
||||
dnl results of this search when this library appears as a dependency.
|
||||
HAVE_LIB[]NAME=yes
|
||||
popdef([NAME])
|
||||
popdef([Name])
|
||||
])
|
||||
|
||||
dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode, [missing-message])
|
||||
dnl searches for libname and the libraries corresponding to explicit and
|
||||
dnl implicit dependencies, together with the specified include files and
|
||||
dnl the ability to compile and link the specified testcode. The missing-message
|
||||
dnl defaults to 'no' and may contain additional hints for the user.
|
||||
dnl If found, it sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME}
|
||||
dnl and LTLIB${NAME} variables and augments the CPPFLAGS variable, and
|
||||
dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs
|
||||
dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty.
|
||||
dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
|
||||
dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
|
||||
AC_DEFUN([AC_LIB_HAVE_LINKFLAGS],
|
||||
[
|
||||
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
|
||||
AC_REQUIRE([AC_LIB_RPATH])
|
||||
pushdef([Name],[m4_translit([$1],[./+-], [____])])
|
||||
pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
|
||||
dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME
|
||||
dnl accordingly.
|
||||
AC_LIB_LINKFLAGS_BODY([$1], [$2])
|
||||
|
||||
dnl Add $INC[]NAME to CPPFLAGS before performing the following checks,
|
||||
dnl because if the user has installed lib[]Name and not disabled its use
|
||||
dnl via --without-lib[]Name-prefix, he wants to use it.
|
||||
ac_save_CPPFLAGS="$CPPFLAGS"
|
||||
AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
|
||||
|
||||
AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [
|
||||
ac_save_LIBS="$LIBS"
|
||||
dnl If $LIB[]NAME contains some -l options, add it to the end of LIBS,
|
||||
dnl because these -l options might require -L options that are present in
|
||||
dnl LIBS. -l options benefit only from the -L options listed before it.
|
||||
dnl Otherwise, add it to the front of LIBS, because it may be a static
|
||||
dnl library that depends on another static library that is present in LIBS.
|
||||
dnl Static libraries benefit only from the static libraries listed after
|
||||
dnl it.
|
||||
case " $LIB[]NAME" in
|
||||
*" -l"*) LIBS="$LIBS $LIB[]NAME" ;;
|
||||
*) LIBS="$LIB[]NAME $LIBS" ;;
|
||||
esac
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM([[$3]], [[$4]])],
|
||||
[ac_cv_lib[]Name=yes],
|
||||
[ac_cv_lib[]Name='m4_if([$5], [], [no], [[$5]])'])
|
||||
LIBS="$ac_save_LIBS"
|
||||
])
|
||||
if test "$ac_cv_lib[]Name" = yes; then
|
||||
HAVE_LIB[]NAME=yes
|
||||
AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the lib][$1 library.])
|
||||
AC_MSG_CHECKING([how to link with lib[]$1])
|
||||
AC_MSG_RESULT([$LIB[]NAME])
|
||||
else
|
||||
HAVE_LIB[]NAME=no
|
||||
dnl If $LIB[]NAME didn't lead to a usable library, we don't need
|
||||
dnl $INC[]NAME either.
|
||||
CPPFLAGS="$ac_save_CPPFLAGS"
|
||||
LIB[]NAME=
|
||||
LTLIB[]NAME=
|
||||
LIB[]NAME[]_PREFIX=
|
||||
fi
|
||||
AC_SUBST([HAVE_LIB]NAME)
|
||||
AC_SUBST([LIB]NAME)
|
||||
AC_SUBST([LTLIB]NAME)
|
||||
AC_SUBST([LIB]NAME[_PREFIX])
|
||||
popdef([NAME])
|
||||
popdef([Name])
|
||||
])
|
||||
|
||||
dnl Determine the platform dependent parameters needed to use rpath:
|
||||
dnl acl_libext,
|
||||
dnl acl_shlibext,
|
||||
dnl acl_libname_spec,
|
||||
dnl acl_library_names_spec,
|
||||
dnl acl_hardcode_libdir_flag_spec,
|
||||
dnl acl_hardcode_libdir_separator,
|
||||
dnl acl_hardcode_direct,
|
||||
dnl acl_hardcode_minus_L.
|
||||
AC_DEFUN([AC_LIB_RPATH],
|
||||
[
|
||||
dnl Tell automake >= 1.10 to complain if config.rpath is missing.
|
||||
m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])])
|
||||
AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS
|
||||
AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld
|
||||
AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host
|
||||
AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir
|
||||
AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [
|
||||
CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \
|
||||
${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh
|
||||
. ./conftest.sh
|
||||
rm -f ./conftest.sh
|
||||
acl_cv_rpath=done
|
||||
])
|
||||
wl="$acl_cv_wl"
|
||||
acl_libext="$acl_cv_libext"
|
||||
acl_shlibext="$acl_cv_shlibext"
|
||||
acl_libname_spec="$acl_cv_libname_spec"
|
||||
acl_library_names_spec="$acl_cv_library_names_spec"
|
||||
acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec"
|
||||
acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator"
|
||||
acl_hardcode_direct="$acl_cv_hardcode_direct"
|
||||
acl_hardcode_minus_L="$acl_cv_hardcode_minus_L"
|
||||
dnl Determine whether the user wants rpath handling at all.
|
||||
AC_ARG_ENABLE([rpath],
|
||||
[ --disable-rpath do not hardcode runtime library paths],
|
||||
:, enable_rpath=yes)
|
||||
])
|
||||
|
||||
dnl AC_LIB_FROMPACKAGE(name, package)
|
||||
dnl declares that libname comes from the given package. The configure file
|
||||
dnl will then not have a --with-libname-prefix option but a
|
||||
dnl --with-package-prefix option. Several libraries can come from the same
|
||||
dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar
|
||||
dnl macro call that searches for libname.
|
||||
AC_DEFUN([AC_LIB_FROMPACKAGE],
|
||||
[
|
||||
pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
define([acl_frompackage_]NAME, [$2])
|
||||
popdef([NAME])
|
||||
pushdef([PACK],[$2])
|
||||
pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
define([acl_libsinpackage_]PACKUP,
|
||||
m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1])
|
||||
popdef([PACKUP])
|
||||
popdef([PACK])
|
||||
])
|
||||
|
||||
dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and
|
||||
dnl the libraries corresponding to explicit and implicit dependencies.
|
||||
dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables.
|
||||
dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found
|
||||
dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
|
||||
AC_DEFUN([AC_LIB_LINKFLAGS_BODY],
|
||||
[
|
||||
AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
|
||||
pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])])
|
||||
pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
|
||||
[ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
|
||||
pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])])
|
||||
dnl Autoconf >= 2.61 supports dots in --with options.
|
||||
pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[m4_translit(PACK,[.],[_])],PACK)])
|
||||
dnl By default, look in $includedir and $libdir.
|
||||
use_additional=yes
|
||||
AC_LIB_WITH_FINAL_PREFIX([
|
||||
eval additional_includedir=\"$includedir\"
|
||||
eval additional_libdir=\"$libdir\"
|
||||
])
|
||||
AC_ARG_WITH(P_A_C_K[-prefix],
|
||||
[[ --with-]]P_A_C_K[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib
|
||||
--without-]]P_A_C_K[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]],
|
||||
[
|
||||
if test "X$withval" = "Xno"; then
|
||||
use_additional=no
|
||||
else
|
||||
if test "X$withval" = "X"; then
|
||||
AC_LIB_WITH_FINAL_PREFIX([
|
||||
eval additional_includedir=\"$includedir\"
|
||||
eval additional_libdir=\"$libdir\"
|
||||
])
|
||||
else
|
||||
additional_includedir="$withval/include"
|
||||
additional_libdir="$withval/$acl_libdirstem"
|
||||
if test "$acl_libdirstem2" != "$acl_libdirstem" \
|
||||
&& ! test -d "$withval/$acl_libdirstem"; then
|
||||
additional_libdir="$withval/$acl_libdirstem2"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
])
|
||||
dnl Search the library and its dependencies in $additional_libdir and
|
||||
dnl $LDFLAGS. Using breadth-first-seach.
|
||||
LIB[]NAME=
|
||||
LTLIB[]NAME=
|
||||
INC[]NAME=
|
||||
LIB[]NAME[]_PREFIX=
|
||||
dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been
|
||||
dnl computed. So it has to be reset here.
|
||||
HAVE_LIB[]NAME=
|
||||
rpathdirs=
|
||||
ltrpathdirs=
|
||||
names_already_handled=
|
||||
names_next_round='$1 $2'
|
||||
while test -n "$names_next_round"; do
|
||||
names_this_round="$names_next_round"
|
||||
names_next_round=
|
||||
for name in $names_this_round; do
|
||||
already_handled=
|
||||
for n in $names_already_handled; do
|
||||
if test "$n" = "$name"; then
|
||||
already_handled=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$already_handled"; then
|
||||
names_already_handled="$names_already_handled $name"
|
||||
dnl See if it was already located by an earlier AC_LIB_LINKFLAGS
|
||||
dnl or AC_LIB_HAVE_LINKFLAGS call.
|
||||
uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'`
|
||||
eval value=\"\$HAVE_LIB$uppername\"
|
||||
if test -n "$value"; then
|
||||
if test "$value" = yes; then
|
||||
eval value=\"\$LIB$uppername\"
|
||||
test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value"
|
||||
eval value=\"\$LTLIB$uppername\"
|
||||
test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value"
|
||||
else
|
||||
dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined
|
||||
dnl that this library doesn't exist. So just drop it.
|
||||
:
|
||||
fi
|
||||
else
|
||||
dnl Search the library lib$name in $additional_libdir and $LDFLAGS
|
||||
dnl and the already constructed $LIBNAME/$LTLIBNAME.
|
||||
found_dir=
|
||||
found_la=
|
||||
found_so=
|
||||
found_a=
|
||||
eval libname=\"$acl_libname_spec\" # typically: libname=lib$name
|
||||
if test -n "$acl_shlibext"; then
|
||||
shrext=".$acl_shlibext" # typically: shrext=.so
|
||||
else
|
||||
shrext=
|
||||
fi
|
||||
if test $use_additional = yes; then
|
||||
dir="$additional_libdir"
|
||||
dnl The same code as in the loop below:
|
||||
dnl First look for a shared library.
|
||||
if test -n "$acl_shlibext"; then
|
||||
if test -f "$dir/$libname$shrext"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$libname$shrext"
|
||||
else
|
||||
if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
|
||||
ver=`(cd "$dir" && \
|
||||
for f in "$libname$shrext".*; do echo "$f"; done \
|
||||
| sed -e "s,^$libname$shrext\\\\.,," \
|
||||
| sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
|
||||
| sed 1q ) 2>/dev/null`
|
||||
if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$libname$shrext.$ver"
|
||||
fi
|
||||
else
|
||||
eval library_names=\"$acl_library_names_spec\"
|
||||
for f in $library_names; do
|
||||
if test -f "$dir/$f"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
dnl Then look for a static library.
|
||||
if test "X$found_dir" = "X"; then
|
||||
if test -f "$dir/$libname.$acl_libext"; then
|
||||
found_dir="$dir"
|
||||
found_a="$dir/$libname.$acl_libext"
|
||||
fi
|
||||
fi
|
||||
if test "X$found_dir" != "X"; then
|
||||
if test -f "$dir/$libname.la"; then
|
||||
found_la="$dir/$libname.la"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if test "X$found_dir" = "X"; then
|
||||
for x in $LDFLAGS $LTLIB[]NAME; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
case "$x" in
|
||||
-L*)
|
||||
dir=`echo "X$x" | sed -e 's/^X-L//'`
|
||||
dnl First look for a shared library.
|
||||
if test -n "$acl_shlibext"; then
|
||||
if test -f "$dir/$libname$shrext"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$libname$shrext"
|
||||
else
|
||||
if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
|
||||
ver=`(cd "$dir" && \
|
||||
for f in "$libname$shrext".*; do echo "$f"; done \
|
||||
| sed -e "s,^$libname$shrext\\\\.,," \
|
||||
| sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
|
||||
| sed 1q ) 2>/dev/null`
|
||||
if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$libname$shrext.$ver"
|
||||
fi
|
||||
else
|
||||
eval library_names=\"$acl_library_names_spec\"
|
||||
for f in $library_names; do
|
||||
if test -f "$dir/$f"; then
|
||||
found_dir="$dir"
|
||||
found_so="$dir/$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
dnl Then look for a static library.
|
||||
if test "X$found_dir" = "X"; then
|
||||
if test -f "$dir/$libname.$acl_libext"; then
|
||||
found_dir="$dir"
|
||||
found_a="$dir/$libname.$acl_libext"
|
||||
fi
|
||||
fi
|
||||
if test "X$found_dir" != "X"; then
|
||||
if test -f "$dir/$libname.la"; then
|
||||
found_la="$dir/$libname.la"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
if test "X$found_dir" != "X"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if test "X$found_dir" != "X"; then
|
||||
dnl Found the library.
|
||||
LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name"
|
||||
if test "X$found_so" != "X"; then
|
||||
dnl Linking with a shared library. We attempt to hardcode its
|
||||
dnl directory into the executable's runpath, unless it's the
|
||||
dnl standard /usr/lib.
|
||||
if test "$enable_rpath" = no \
|
||||
|| test "X$found_dir" = "X/usr/$acl_libdirstem" \
|
||||
|| test "X$found_dir" = "X/usr/$acl_libdirstem2"; then
|
||||
dnl No hardcoding is needed.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
|
||||
else
|
||||
dnl Use an explicit option to hardcode DIR into the resulting
|
||||
dnl binary.
|
||||
dnl Potentially add DIR to ltrpathdirs.
|
||||
dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
|
||||
haveit=
|
||||
for x in $ltrpathdirs; do
|
||||
if test "X$x" = "X$found_dir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
ltrpathdirs="$ltrpathdirs $found_dir"
|
||||
fi
|
||||
dnl The hardcoding into $LIBNAME is system dependent.
|
||||
if test "$acl_hardcode_direct" = yes; then
|
||||
dnl Using DIR/libNAME.so during linking hardcodes DIR into the
|
||||
dnl resulting binary.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
|
||||
else
|
||||
if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
|
||||
dnl Use an explicit option to hardcode DIR into the resulting
|
||||
dnl binary.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
|
||||
dnl Potentially add DIR to rpathdirs.
|
||||
dnl The rpathdirs will be appended to $LIBNAME at the end.
|
||||
haveit=
|
||||
for x in $rpathdirs; do
|
||||
if test "X$x" = "X$found_dir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
rpathdirs="$rpathdirs $found_dir"
|
||||
fi
|
||||
else
|
||||
dnl Rely on "-L$found_dir".
|
||||
dnl But don't add it if it's already contained in the LDFLAGS
|
||||
dnl or the already constructed $LIBNAME
|
||||
haveit=
|
||||
for x in $LDFLAGS $LIB[]NAME; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-L$found_dir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir"
|
||||
fi
|
||||
if test "$acl_hardcode_minus_L" != no; then
|
||||
dnl FIXME: Not sure whether we should use
|
||||
dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
|
||||
dnl here.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
|
||||
else
|
||||
dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH
|
||||
dnl here, because this doesn't fit in flags passed to the
|
||||
dnl compiler. So give up. No hardcoding. This affects only
|
||||
dnl very old systems.
|
||||
dnl FIXME: Not sure whether we should use
|
||||
dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
|
||||
dnl here.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if test "X$found_a" != "X"; then
|
||||
dnl Linking with a static library.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a"
|
||||
else
|
||||
dnl We shouldn't come here, but anyway it's good to have a
|
||||
dnl fallback.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name"
|
||||
fi
|
||||
fi
|
||||
dnl Assume the include files are nearby.
|
||||
additional_includedir=
|
||||
case "$found_dir" in
|
||||
*/$acl_libdirstem | */$acl_libdirstem/)
|
||||
basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'`
|
||||
if test "$name" = '$1'; then
|
||||
LIB[]NAME[]_PREFIX="$basedir"
|
||||
fi
|
||||
additional_includedir="$basedir/include"
|
||||
;;
|
||||
*/$acl_libdirstem2 | */$acl_libdirstem2/)
|
||||
basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'`
|
||||
if test "$name" = '$1'; then
|
||||
LIB[]NAME[]_PREFIX="$basedir"
|
||||
fi
|
||||
additional_includedir="$basedir/include"
|
||||
;;
|
||||
esac
|
||||
if test "X$additional_includedir" != "X"; then
|
||||
dnl Potentially add $additional_includedir to $INCNAME.
|
||||
dnl But don't add it
|
||||
dnl 1. if it's the standard /usr/include,
|
||||
dnl 2. if it's /usr/local/include and we are using GCC on Linux,
|
||||
dnl 3. if it's already present in $CPPFLAGS or the already
|
||||
dnl constructed $INCNAME,
|
||||
dnl 4. if it doesn't exist as a directory.
|
||||
if test "X$additional_includedir" != "X/usr/include"; then
|
||||
haveit=
|
||||
if test "X$additional_includedir" = "X/usr/local/include"; then
|
||||
if test -n "$GCC"; then
|
||||
case $host_os in
|
||||
linux* | gnu* | k*bsd*-gnu) haveit=yes;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
if test -z "$haveit"; then
|
||||
for x in $CPPFLAGS $INC[]NAME; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-I$additional_includedir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
if test -d "$additional_includedir"; then
|
||||
dnl Really add $additional_includedir to $INCNAME.
|
||||
INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
dnl Look for dependencies.
|
||||
if test -n "$found_la"; then
|
||||
dnl Read the .la file. It defines the variables
|
||||
dnl dlname, library_names, old_library, dependency_libs, current,
|
||||
dnl age, revision, installed, dlopen, dlpreopen, libdir.
|
||||
save_libdir="$libdir"
|
||||
case "$found_la" in
|
||||
*/* | *\\*) . "$found_la" ;;
|
||||
*) . "./$found_la" ;;
|
||||
esac
|
||||
libdir="$save_libdir"
|
||||
dnl We use only dependency_libs.
|
||||
for dep in $dependency_libs; do
|
||||
case "$dep" in
|
||||
-L*)
|
||||
additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'`
|
||||
dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME.
|
||||
dnl But don't add it
|
||||
dnl 1. if it's the standard /usr/lib,
|
||||
dnl 2. if it's /usr/local/lib and we are using GCC on Linux,
|
||||
dnl 3. if it's already present in $LDFLAGS or the already
|
||||
dnl constructed $LIBNAME,
|
||||
dnl 4. if it doesn't exist as a directory.
|
||||
if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \
|
||||
&& test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then
|
||||
haveit=
|
||||
if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \
|
||||
|| test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then
|
||||
if test -n "$GCC"; then
|
||||
case $host_os in
|
||||
linux* | gnu* | k*bsd*-gnu) haveit=yes;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
if test -z "$haveit"; then
|
||||
haveit=
|
||||
for x in $LDFLAGS $LIB[]NAME; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-L$additional_libdir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
if test -d "$additional_libdir"; then
|
||||
dnl Really add $additional_libdir to $LIBNAME.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir"
|
||||
fi
|
||||
fi
|
||||
haveit=
|
||||
for x in $LDFLAGS $LTLIB[]NAME; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-L$additional_libdir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
if test -d "$additional_libdir"; then
|
||||
dnl Really add $additional_libdir to $LTLIBNAME.
|
||||
LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
-R*)
|
||||
dir=`echo "X$dep" | sed -e 's/^X-R//'`
|
||||
if test "$enable_rpath" != no; then
|
||||
dnl Potentially add DIR to rpathdirs.
|
||||
dnl The rpathdirs will be appended to $LIBNAME at the end.
|
||||
haveit=
|
||||
for x in $rpathdirs; do
|
||||
if test "X$x" = "X$dir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
rpathdirs="$rpathdirs $dir"
|
||||
fi
|
||||
dnl Potentially add DIR to ltrpathdirs.
|
||||
dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
|
||||
haveit=
|
||||
for x in $ltrpathdirs; do
|
||||
if test "X$x" = "X$dir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
ltrpathdirs="$ltrpathdirs $dir"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
-l*)
|
||||
dnl Handle this in the next round.
|
||||
names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'`
|
||||
;;
|
||||
*.la)
|
||||
dnl Handle this in the next round. Throw away the .la's
|
||||
dnl directory; it is already contained in a preceding -L
|
||||
dnl option.
|
||||
names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'`
|
||||
;;
|
||||
*)
|
||||
dnl Most likely an immediate library name.
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep"
|
||||
LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
else
|
||||
dnl Didn't find the library; assume it is in the system directories
|
||||
dnl known to the linker and runtime loader. (All the system
|
||||
dnl directories known to the linker should also be known to the
|
||||
dnl runtime loader, otherwise the system is severely misconfigured.)
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
|
||||
LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
if test "X$rpathdirs" != "X"; then
|
||||
if test -n "$acl_hardcode_libdir_separator"; then
|
||||
dnl Weird platform: only the last -rpath option counts, the user must
|
||||
dnl pass all path elements in one option. We can arrange that for a
|
||||
dnl single library, but not when more than one $LIBNAMEs are used.
|
||||
alldirs=
|
||||
for found_dir in $rpathdirs; do
|
||||
alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir"
|
||||
done
|
||||
dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl.
|
||||
acl_save_libdir="$libdir"
|
||||
libdir="$alldirs"
|
||||
eval flag=\"$acl_hardcode_libdir_flag_spec\"
|
||||
libdir="$acl_save_libdir"
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
|
||||
else
|
||||
dnl The -rpath options are cumulative.
|
||||
for found_dir in $rpathdirs; do
|
||||
acl_save_libdir="$libdir"
|
||||
libdir="$found_dir"
|
||||
eval flag=\"$acl_hardcode_libdir_flag_spec\"
|
||||
libdir="$acl_save_libdir"
|
||||
LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
if test "X$ltrpathdirs" != "X"; then
|
||||
dnl When using libtool, the option that works for both libraries and
|
||||
dnl executables is -R. The -R options are cumulative.
|
||||
for found_dir in $ltrpathdirs; do
|
||||
LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir"
|
||||
done
|
||||
fi
|
||||
popdef([P_A_C_K])
|
||||
popdef([PACKLIBS])
|
||||
popdef([PACKUP])
|
||||
popdef([PACK])
|
||||
popdef([NAME])
|
||||
])
|
||||
|
||||
dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR,
|
||||
dnl unless already present in VAR.
|
||||
dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes
|
||||
dnl contains two or three consecutive elements that belong together.
|
||||
AC_DEFUN([AC_LIB_APPENDTOVAR],
|
||||
[
|
||||
for element in [$2]; do
|
||||
haveit=
|
||||
for x in $[$1]; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X$element"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
[$1]="${[$1]}${[$1]:+ }$element"
|
||||
fi
|
||||
done
|
||||
])
|
||||
|
||||
dnl For those cases where a variable contains several -L and -l options
|
||||
dnl referring to unknown libraries and directories, this macro determines the
|
||||
dnl necessary additional linker options for the runtime path.
|
||||
dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL])
|
||||
dnl sets LDADDVAR to linker options needed together with LIBSVALUE.
|
||||
dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed,
|
||||
dnl otherwise linking without libtool is assumed.
|
||||
AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS],
|
||||
[
|
||||
AC_REQUIRE([AC_LIB_RPATH])
|
||||
AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
|
||||
$1=
|
||||
if test "$enable_rpath" != no; then
|
||||
if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
|
||||
dnl Use an explicit option to hardcode directories into the resulting
|
||||
dnl binary.
|
||||
rpathdirs=
|
||||
next=
|
||||
for opt in $2; do
|
||||
if test -n "$next"; then
|
||||
dir="$next"
|
||||
dnl No need to hardcode the standard /usr/lib.
|
||||
if test "X$dir" != "X/usr/$acl_libdirstem" \
|
||||
&& test "X$dir" != "X/usr/$acl_libdirstem2"; then
|
||||
rpathdirs="$rpathdirs $dir"
|
||||
fi
|
||||
next=
|
||||
else
|
||||
case $opt in
|
||||
-L) next=yes ;;
|
||||
-L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'`
|
||||
dnl No need to hardcode the standard /usr/lib.
|
||||
if test "X$dir" != "X/usr/$acl_libdirstem" \
|
||||
&& test "X$dir" != "X/usr/$acl_libdirstem2"; then
|
||||
rpathdirs="$rpathdirs $dir"
|
||||
fi
|
||||
next= ;;
|
||||
*) next= ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
if test "X$rpathdirs" != "X"; then
|
||||
if test -n ""$3""; then
|
||||
dnl libtool is used for linking. Use -R options.
|
||||
for dir in $rpathdirs; do
|
||||
$1="${$1}${$1:+ }-R$dir"
|
||||
done
|
||||
else
|
||||
dnl The linker is used for linking directly.
|
||||
if test -n "$acl_hardcode_libdir_separator"; then
|
||||
dnl Weird platform: only the last -rpath option counts, the user
|
||||
dnl must pass all path elements in one option.
|
||||
alldirs=
|
||||
for dir in $rpathdirs; do
|
||||
alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir"
|
||||
done
|
||||
acl_save_libdir="$libdir"
|
||||
libdir="$alldirs"
|
||||
eval flag=\"$acl_hardcode_libdir_flag_spec\"
|
||||
libdir="$acl_save_libdir"
|
||||
$1="$flag"
|
||||
else
|
||||
dnl The -rpath options are cumulative.
|
||||
for dir in $rpathdirs; do
|
||||
acl_save_libdir="$libdir"
|
||||
libdir="$dir"
|
||||
eval flag=\"$acl_hardcode_libdir_flag_spec\"
|
||||
libdir="$acl_save_libdir"
|
||||
$1="${$1}${$1:+ }$flag"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
AC_SUBST([$1])
|
||||
])
|
|
@ -0,0 +1,224 @@
|
|||
# lib-prefix.m4 serial 7 (gettext-0.18)
|
||||
dnl Copyright (C) 2001-2005, 2008-2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
|
||||
dnl From Bruno Haible.
|
||||
|
||||
dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and
|
||||
dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't
|
||||
dnl require excessive bracketing.
|
||||
ifdef([AC_HELP_STRING],
|
||||
[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])],
|
||||
[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])])
|
||||
|
||||
dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed
|
||||
dnl to access previously installed libraries. The basic assumption is that
|
||||
dnl a user will want packages to use other packages he previously installed
|
||||
dnl with the same --prefix option.
|
||||
dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate
|
||||
dnl libraries, but is otherwise very convenient.
|
||||
AC_DEFUN([AC_LIB_PREFIX],
|
||||
[
|
||||
AC_BEFORE([$0], [AC_LIB_LINKFLAGS])
|
||||
AC_REQUIRE([AC_PROG_CC])
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||
AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
|
||||
AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
|
||||
dnl By default, look in $includedir and $libdir.
|
||||
use_additional=yes
|
||||
AC_LIB_WITH_FINAL_PREFIX([
|
||||
eval additional_includedir=\"$includedir\"
|
||||
eval additional_libdir=\"$libdir\"
|
||||
])
|
||||
AC_LIB_ARG_WITH([lib-prefix],
|
||||
[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib
|
||||
--without-lib-prefix don't search for libraries in includedir and libdir],
|
||||
[
|
||||
if test "X$withval" = "Xno"; then
|
||||
use_additional=no
|
||||
else
|
||||
if test "X$withval" = "X"; then
|
||||
AC_LIB_WITH_FINAL_PREFIX([
|
||||
eval additional_includedir=\"$includedir\"
|
||||
eval additional_libdir=\"$libdir\"
|
||||
])
|
||||
else
|
||||
additional_includedir="$withval/include"
|
||||
additional_libdir="$withval/$acl_libdirstem"
|
||||
fi
|
||||
fi
|
||||
])
|
||||
if test $use_additional = yes; then
|
||||
dnl Potentially add $additional_includedir to $CPPFLAGS.
|
||||
dnl But don't add it
|
||||
dnl 1. if it's the standard /usr/include,
|
||||
dnl 2. if it's already present in $CPPFLAGS,
|
||||
dnl 3. if it's /usr/local/include and we are using GCC on Linux,
|
||||
dnl 4. if it doesn't exist as a directory.
|
||||
if test "X$additional_includedir" != "X/usr/include"; then
|
||||
haveit=
|
||||
for x in $CPPFLAGS; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-I$additional_includedir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
if test "X$additional_includedir" = "X/usr/local/include"; then
|
||||
if test -n "$GCC"; then
|
||||
case $host_os in
|
||||
linux* | gnu* | k*bsd*-gnu) haveit=yes;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
if test -z "$haveit"; then
|
||||
if test -d "$additional_includedir"; then
|
||||
dnl Really add $additional_includedir to $CPPFLAGS.
|
||||
CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
dnl Potentially add $additional_libdir to $LDFLAGS.
|
||||
dnl But don't add it
|
||||
dnl 1. if it's the standard /usr/lib,
|
||||
dnl 2. if it's already present in $LDFLAGS,
|
||||
dnl 3. if it's /usr/local/lib and we are using GCC on Linux,
|
||||
dnl 4. if it doesn't exist as a directory.
|
||||
if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then
|
||||
haveit=
|
||||
for x in $LDFLAGS; do
|
||||
AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
|
||||
if test "X$x" = "X-L$additional_libdir"; then
|
||||
haveit=yes
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$haveit"; then
|
||||
if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then
|
||||
if test -n "$GCC"; then
|
||||
case $host_os in
|
||||
linux*) haveit=yes;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
if test -z "$haveit"; then
|
||||
if test -d "$additional_libdir"; then
|
||||
dnl Really add $additional_libdir to $LDFLAGS.
|
||||
LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
])
|
||||
|
||||
dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix,
|
||||
dnl acl_final_exec_prefix, containing the values to which $prefix and
|
||||
dnl $exec_prefix will expand at the end of the configure script.
|
||||
AC_DEFUN([AC_LIB_PREPARE_PREFIX],
|
||||
[
|
||||
dnl Unfortunately, prefix and exec_prefix get only finally determined
|
||||
dnl at the end of configure.
|
||||
if test "X$prefix" = "XNONE"; then
|
||||
acl_final_prefix="$ac_default_prefix"
|
||||
else
|
||||
acl_final_prefix="$prefix"
|
||||
fi
|
||||
if test "X$exec_prefix" = "XNONE"; then
|
||||
acl_final_exec_prefix='${prefix}'
|
||||
else
|
||||
acl_final_exec_prefix="$exec_prefix"
|
||||
fi
|
||||
acl_save_prefix="$prefix"
|
||||
prefix="$acl_final_prefix"
|
||||
eval acl_final_exec_prefix=\"$acl_final_exec_prefix\"
|
||||
prefix="$acl_save_prefix"
|
||||
])
|
||||
|
||||
dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the
|
||||
dnl variables prefix and exec_prefix bound to the values they will have
|
||||
dnl at the end of the configure script.
|
||||
AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX],
|
||||
[
|
||||
acl_save_prefix="$prefix"
|
||||
prefix="$acl_final_prefix"
|
||||
acl_save_exec_prefix="$exec_prefix"
|
||||
exec_prefix="$acl_final_exec_prefix"
|
||||
$1
|
||||
exec_prefix="$acl_save_exec_prefix"
|
||||
prefix="$acl_save_prefix"
|
||||
])
|
||||
|
||||
dnl AC_LIB_PREPARE_MULTILIB creates
|
||||
dnl - a variable acl_libdirstem, containing the basename of the libdir, either
|
||||
dnl "lib" or "lib64" or "lib/64",
|
||||
dnl - a variable acl_libdirstem2, as a secondary possible value for
|
||||
dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or
|
||||
dnl "lib/amd64".
|
||||
AC_DEFUN([AC_LIB_PREPARE_MULTILIB],
|
||||
[
|
||||
dnl There is no formal standard regarding lib and lib64.
|
||||
dnl On glibc systems, the current practice is that on a system supporting
|
||||
dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
|
||||
dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine
|
||||
dnl the compiler's default mode by looking at the compiler's library search
|
||||
dnl path. If at least one of its elements ends in /lib64 or points to a
|
||||
dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI.
|
||||
dnl Otherwise we use the default, namely "lib".
|
||||
dnl On Solaris systems, the current practice is that on a system supporting
|
||||
dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
|
||||
dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or
|
||||
dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib.
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||
acl_libdirstem=lib
|
||||
acl_libdirstem2=
|
||||
case "$host_os" in
|
||||
solaris*)
|
||||
dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment
|
||||
dnl <http://docs.sun.com/app/docs/doc/816-5138/dev-env?l=en&a=view>.
|
||||
dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link."
|
||||
dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the
|
||||
dnl symlink is missing, so we set acl_libdirstem2 too.
|
||||
AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit],
|
||||
[AC_EGREP_CPP([sixtyfour bits], [
|
||||
#ifdef _LP64
|
||||
sixtyfour bits
|
||||
#endif
|
||||
], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no])
|
||||
])
|
||||
if test $gl_cv_solaris_64bit = yes; then
|
||||
acl_libdirstem=lib/64
|
||||
case "$host_cpu" in
|
||||
sparc*) acl_libdirstem2=lib/sparcv9 ;;
|
||||
i*86 | x86_64) acl_libdirstem2=lib/amd64 ;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'`
|
||||
if test -n "$searchpath"; then
|
||||
acl_save_IFS="${IFS= }"; IFS=":"
|
||||
for searchdir in $searchpath; do
|
||||
if test -d "$searchdir"; then
|
||||
case "$searchdir" in
|
||||
*/lib64/ | */lib64 ) acl_libdirstem=lib64 ;;
|
||||
*/../ | */.. )
|
||||
# Better ignore directories of this form. They are misleading.
|
||||
;;
|
||||
*) searchdir=`cd "$searchdir" && pwd`
|
||||
case "$searchdir" in
|
||||
*/lib64 ) acl_libdirstem=lib64 ;;
|
||||
esac ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
IFS="$acl_save_IFS"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem"
|
||||
])
|
|
@ -0,0 +1,32 @@
|
|||
# nls.m4 serial 5 (gettext-0.18)
|
||||
dnl Copyright (C) 1995-2003, 2005-2006, 2008-2014, 2016 Free Software
|
||||
dnl Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
dnl
|
||||
dnl This file can be used in projects which are not available under
|
||||
dnl the GNU General Public License or the GNU Library General Public
|
||||
dnl License but which still want to provide support for the GNU gettext
|
||||
dnl functionality.
|
||||
dnl Please note that the actual code of the GNU gettext library is covered
|
||||
dnl by the GNU Library General Public License, and the rest of the GNU
|
||||
dnl gettext package is covered by the GNU General Public License.
|
||||
dnl They are *not* in the public domain.
|
||||
|
||||
dnl Authors:
|
||||
dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
|
||||
dnl Bruno Haible <haible@clisp.cons.org>, 2000-2003.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
|
||||
AC_DEFUN([AM_NLS],
|
||||
[
|
||||
AC_MSG_CHECKING([whether NLS is requested])
|
||||
dnl Default is enabled NLS
|
||||
AC_ARG_ENABLE([nls],
|
||||
[ --disable-nls do not use Native Language Support],
|
||||
USE_NLS=$enableval, USE_NLS=yes)
|
||||
AC_MSG_RESULT([$USE_NLS])
|
||||
AC_SUBST([USE_NLS])
|
||||
])
|
|
@ -0,0 +1,453 @@
|
|||
# po.m4 serial 24 (gettext-0.19)
|
||||
dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
dnl
|
||||
dnl This file can be used in projects which are not available under
|
||||
dnl the GNU General Public License or the GNU Library General Public
|
||||
dnl License but which still want to provide support for the GNU gettext
|
||||
dnl functionality.
|
||||
dnl Please note that the actual code of the GNU gettext library is covered
|
||||
dnl by the GNU Library General Public License, and the rest of the GNU
|
||||
dnl gettext package is covered by the GNU General Public License.
|
||||
dnl They are *not* in the public domain.
|
||||
|
||||
dnl Authors:
|
||||
dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
|
||||
dnl Bruno Haible <haible@clisp.cons.org>, 2000-2003.
|
||||
|
||||
AC_PREREQ([2.60])
|
||||
|
||||
dnl Checks for all prerequisites of the po subdirectory.
|
||||
AC_DEFUN([AM_PO_SUBDIRS],
|
||||
[
|
||||
AC_REQUIRE([AC_PROG_MAKE_SET])dnl
|
||||
AC_REQUIRE([AC_PROG_INSTALL])dnl
|
||||
AC_REQUIRE([AC_PROG_MKDIR_P])dnl
|
||||
AC_REQUIRE([AC_PROG_SED])dnl
|
||||
AC_REQUIRE([AM_NLS])dnl
|
||||
|
||||
dnl Release version of the gettext macros. This is used to ensure that
|
||||
dnl the gettext macros and po/Makefile.in.in are in sync.
|
||||
AC_SUBST([GETTEXT_MACRO_VERSION], [0.19])
|
||||
|
||||
dnl Perform the following tests also if --disable-nls has been given,
|
||||
dnl because they are needed for "make dist" to work.
|
||||
|
||||
dnl Search for GNU msgfmt in the PATH.
|
||||
dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions.
|
||||
dnl The second test excludes FreeBSD msgfmt.
|
||||
AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
|
||||
[$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
|
||||
(if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
|
||||
:)
|
||||
AC_PATH_PROG([GMSGFMT], [gmsgfmt], [$MSGFMT])
|
||||
|
||||
dnl Test whether it is GNU msgfmt >= 0.15.
|
||||
changequote(,)dnl
|
||||
case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;;
|
||||
*) MSGFMT_015=$MSGFMT ;;
|
||||
esac
|
||||
changequote([,])dnl
|
||||
AC_SUBST([MSGFMT_015])
|
||||
changequote(,)dnl
|
||||
case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;;
|
||||
*) GMSGFMT_015=$GMSGFMT ;;
|
||||
esac
|
||||
changequote([,])dnl
|
||||
AC_SUBST([GMSGFMT_015])
|
||||
|
||||
dnl Search for GNU xgettext 0.12 or newer in the PATH.
|
||||
dnl The first test excludes Solaris xgettext and early GNU xgettext versions.
|
||||
dnl The second test excludes FreeBSD xgettext.
|
||||
AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
|
||||
[$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
|
||||
(if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
|
||||
:)
|
||||
dnl Remove leftover from FreeBSD xgettext call.
|
||||
rm -f messages.po
|
||||
|
||||
dnl Test whether it is GNU xgettext >= 0.15.
|
||||
changequote(,)dnl
|
||||
case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;;
|
||||
*) XGETTEXT_015=$XGETTEXT ;;
|
||||
esac
|
||||
changequote([,])dnl
|
||||
AC_SUBST([XGETTEXT_015])
|
||||
|
||||
dnl Search for GNU msgmerge 0.11 or newer in the PATH.
|
||||
AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge,
|
||||
[$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :)
|
||||
|
||||
dnl Installation directories.
|
||||
dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we
|
||||
dnl have to define it here, so that it can be used in po/Makefile.
|
||||
test -n "$localedir" || localedir='${datadir}/locale'
|
||||
AC_SUBST([localedir])
|
||||
|
||||
dnl Support for AM_XGETTEXT_OPTION.
|
||||
test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS=
|
||||
AC_SUBST([XGETTEXT_EXTRA_OPTIONS])
|
||||
|
||||
AC_CONFIG_COMMANDS([po-directories], [[
|
||||
for ac_file in $CONFIG_FILES; do
|
||||
# Support "outfile[:infile[:infile...]]"
|
||||
case "$ac_file" in
|
||||
*:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
|
||||
esac
|
||||
# PO directories have a Makefile.in generated from Makefile.in.in.
|
||||
case "$ac_file" in */Makefile.in)
|
||||
# Adjust a relative srcdir.
|
||||
ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
|
||||
ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
|
||||
ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
|
||||
# In autoconf-2.13 it is called $ac_given_srcdir.
|
||||
# In autoconf-2.50 it is called $srcdir.
|
||||
test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
|
||||
case "$ac_given_srcdir" in
|
||||
.) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
|
||||
/*) top_srcdir="$ac_given_srcdir" ;;
|
||||
*) top_srcdir="$ac_dots$ac_given_srcdir" ;;
|
||||
esac
|
||||
# Treat a directory as a PO directory if and only if it has a
|
||||
# POTFILES.in file. This allows packages to have multiple PO
|
||||
# directories under different names or in different locations.
|
||||
if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then
|
||||
rm -f "$ac_dir/POTFILES"
|
||||
test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES"
|
||||
gt_tab=`printf '\t'`
|
||||
cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ ${gt_tab}]*\$/d" -e "s,.*, $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES"
|
||||
POMAKEFILEDEPS="POTFILES.in"
|
||||
# ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend
|
||||
# on $ac_dir but don't depend on user-specified configuration
|
||||
# parameters.
|
||||
if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
|
||||
# The LINGUAS file contains the set of available languages.
|
||||
if test -n "$OBSOLETE_ALL_LINGUAS"; then
|
||||
test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
|
||||
fi
|
||||
ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
|
||||
# Hide the ALL_LINGUAS assignment from automake < 1.5.
|
||||
eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
|
||||
POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
|
||||
else
|
||||
# The set of available languages was given in configure.in.
|
||||
# Hide the ALL_LINGUAS assignment from automake < 1.5.
|
||||
eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS'
|
||||
fi
|
||||
# Compute POFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
|
||||
# Compute UPDATEPOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
|
||||
# Compute DUMMYPOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
|
||||
# Compute GMOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
|
||||
case "$ac_given_srcdir" in
|
||||
.) srcdirpre= ;;
|
||||
*) srcdirpre='$(srcdir)/' ;;
|
||||
esac
|
||||
POFILES=
|
||||
UPDATEPOFILES=
|
||||
DUMMYPOFILES=
|
||||
GMOFILES=
|
||||
for lang in $ALL_LINGUAS; do
|
||||
POFILES="$POFILES $srcdirpre$lang.po"
|
||||
UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
|
||||
DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
|
||||
GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
|
||||
done
|
||||
# CATALOGS depends on both $ac_dir and the user's LINGUAS
|
||||
# environment variable.
|
||||
INST_LINGUAS=
|
||||
if test -n "$ALL_LINGUAS"; then
|
||||
for presentlang in $ALL_LINGUAS; do
|
||||
useit=no
|
||||
if test "%UNSET%" != "$LINGUAS"; then
|
||||
desiredlanguages="$LINGUAS"
|
||||
else
|
||||
desiredlanguages="$ALL_LINGUAS"
|
||||
fi
|
||||
for desiredlang in $desiredlanguages; do
|
||||
# Use the presentlang catalog if desiredlang is
|
||||
# a. equal to presentlang, or
|
||||
# b. a variant of presentlang (because in this case,
|
||||
# presentlang can be used as a fallback for messages
|
||||
# which are not translated in the desiredlang catalog).
|
||||
case "$desiredlang" in
|
||||
"$presentlang"*) useit=yes;;
|
||||
esac
|
||||
done
|
||||
if test $useit = yes; then
|
||||
INST_LINGUAS="$INST_LINGUAS $presentlang"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
CATALOGS=
|
||||
if test -n "$INST_LINGUAS"; then
|
||||
for lang in $INST_LINGUAS; do
|
||||
CATALOGS="$CATALOGS $lang.gmo"
|
||||
done
|
||||
fi
|
||||
test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile"
|
||||
sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile"
|
||||
for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do
|
||||
if test -f "$f"; then
|
||||
case "$f" in
|
||||
*.orig | *.bak | *~) ;;
|
||||
*) cat "$f" >> "$ac_dir/Makefile" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done]],
|
||||
[# Capture the value of obsolete ALL_LINGUAS because we need it to compute
|
||||
# POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it
|
||||
# from automake < 1.5.
|
||||
eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"'
|
||||
# Capture the value of LINGUAS because we need it to compute CATALOGS.
|
||||
LINGUAS="${LINGUAS-%UNSET%}"
|
||||
])
|
||||
])
|
||||
|
||||
dnl Postprocesses a Makefile in a directory containing PO files.
|
||||
AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE],
|
||||
[
|
||||
# When this code is run, in config.status, two variables have already been
|
||||
# set:
|
||||
# - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in,
|
||||
# - LINGUAS is the value of the environment variable LINGUAS at configure
|
||||
# time.
|
||||
|
||||
changequote(,)dnl
|
||||
# Adjust a relative srcdir.
|
||||
ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
|
||||
ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
|
||||
ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
|
||||
# In autoconf-2.13 it is called $ac_given_srcdir.
|
||||
# In autoconf-2.50 it is called $srcdir.
|
||||
test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
|
||||
case "$ac_given_srcdir" in
|
||||
.) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
|
||||
/*) top_srcdir="$ac_given_srcdir" ;;
|
||||
*) top_srcdir="$ac_dots$ac_given_srcdir" ;;
|
||||
esac
|
||||
|
||||
# Find a way to echo strings without interpreting backslash.
|
||||
if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then
|
||||
gt_echo='echo'
|
||||
else
|
||||
if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then
|
||||
gt_echo='printf %s\n'
|
||||
else
|
||||
echo_func () {
|
||||
cat <<EOT
|
||||
$*
|
||||
EOT
|
||||
}
|
||||
gt_echo='echo_func'
|
||||
fi
|
||||
fi
|
||||
|
||||
# A sed script that extracts the value of VARIABLE from a Makefile.
|
||||
tab=`printf '\t'`
|
||||
sed_x_variable='
|
||||
# Test if the hold space is empty.
|
||||
x
|
||||
s/P/P/
|
||||
x
|
||||
ta
|
||||
# Yes it was empty. Look if we have the expected variable definition.
|
||||
/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=/{
|
||||
# Seen the first line of the variable definition.
|
||||
s/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=//
|
||||
ba
|
||||
}
|
||||
bd
|
||||
:a
|
||||
# Here we are processing a line from the variable definition.
|
||||
# Remove comment, more precisely replace it with a space.
|
||||
s/#.*$/ /
|
||||
# See if the line ends in a backslash.
|
||||
tb
|
||||
:b
|
||||
s/\\$//
|
||||
# Print the line, without the trailing backslash.
|
||||
p
|
||||
tc
|
||||
# There was no trailing backslash. The end of the variable definition is
|
||||
# reached. Clear the hold space.
|
||||
s/^.*$//
|
||||
x
|
||||
bd
|
||||
:c
|
||||
# A trailing backslash means that the variable definition continues in the
|
||||
# next line. Put a nonempty string into the hold space to indicate this.
|
||||
s/^.*$/P/
|
||||
x
|
||||
:d
|
||||
'
|
||||
changequote([,])dnl
|
||||
|
||||
# Set POTFILES to the value of the Makefile variable POTFILES.
|
||||
sed_x_POTFILES=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/POTFILES/g'`
|
||||
POTFILES=`sed -n -e "$sed_x_POTFILES" < "$ac_file"`
|
||||
# Compute POTFILES_DEPS as
|
||||
# $(foreach file, $(POTFILES), $(top_srcdir)/$(file))
|
||||
POTFILES_DEPS=
|
||||
for file in $POTFILES; do
|
||||
POTFILES_DEPS="$POTFILES_DEPS "'$(top_srcdir)/'"$file"
|
||||
done
|
||||
POMAKEFILEDEPS=""
|
||||
|
||||
if test -n "$OBSOLETE_ALL_LINGUAS"; then
|
||||
test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
|
||||
fi
|
||||
if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
|
||||
# The LINGUAS file contains the set of available languages.
|
||||
ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
|
||||
POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
|
||||
else
|
||||
# Set ALL_LINGUAS to the value of the Makefile variable LINGUAS.
|
||||
sed_x_LINGUAS=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/LINGUAS/g'`
|
||||
ALL_LINGUAS_=`sed -n -e "$sed_x_LINGUAS" < "$ac_file"`
|
||||
fi
|
||||
# Hide the ALL_LINGUAS assignment from automake < 1.5.
|
||||
eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
|
||||
# Compute POFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
|
||||
# Compute UPDATEPOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
|
||||
# Compute DUMMYPOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
|
||||
# Compute GMOFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
|
||||
# Compute PROPERTIESFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).properties)
|
||||
# Compute CLASSFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).class)
|
||||
# Compute QMFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).qm)
|
||||
# Compute MSGFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang)).msg)
|
||||
# Compute RESOURCESDLLFILES
|
||||
# as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang))/$(DOMAIN).resources.dll)
|
||||
case "$ac_given_srcdir" in
|
||||
.) srcdirpre= ;;
|
||||
*) srcdirpre='$(srcdir)/' ;;
|
||||
esac
|
||||
POFILES=
|
||||
UPDATEPOFILES=
|
||||
DUMMYPOFILES=
|
||||
GMOFILES=
|
||||
PROPERTIESFILES=
|
||||
CLASSFILES=
|
||||
QMFILES=
|
||||
MSGFILES=
|
||||
RESOURCESDLLFILES=
|
||||
for lang in $ALL_LINGUAS; do
|
||||
POFILES="$POFILES $srcdirpre$lang.po"
|
||||
UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
|
||||
DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
|
||||
GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
|
||||
PROPERTIESFILES="$PROPERTIESFILES \$(top_srcdir)/\$(DOMAIN)_$lang.properties"
|
||||
CLASSFILES="$CLASSFILES \$(top_srcdir)/\$(DOMAIN)_$lang.class"
|
||||
QMFILES="$QMFILES $srcdirpre$lang.qm"
|
||||
frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
|
||||
MSGFILES="$MSGFILES $srcdirpre$frobbedlang.msg"
|
||||
frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
|
||||
RESOURCESDLLFILES="$RESOURCESDLLFILES $srcdirpre$frobbedlang/\$(DOMAIN).resources.dll"
|
||||
done
|
||||
# CATALOGS depends on both $ac_dir and the user's LINGUAS
|
||||
# environment variable.
|
||||
INST_LINGUAS=
|
||||
if test -n "$ALL_LINGUAS"; then
|
||||
for presentlang in $ALL_LINGUAS; do
|
||||
useit=no
|
||||
if test "%UNSET%" != "$LINGUAS"; then
|
||||
desiredlanguages="$LINGUAS"
|
||||
else
|
||||
desiredlanguages="$ALL_LINGUAS"
|
||||
fi
|
||||
for desiredlang in $desiredlanguages; do
|
||||
# Use the presentlang catalog if desiredlang is
|
||||
# a. equal to presentlang, or
|
||||
# b. a variant of presentlang (because in this case,
|
||||
# presentlang can be used as a fallback for messages
|
||||
# which are not translated in the desiredlang catalog).
|
||||
case "$desiredlang" in
|
||||
"$presentlang"*) useit=yes;;
|
||||
esac
|
||||
done
|
||||
if test $useit = yes; then
|
||||
INST_LINGUAS="$INST_LINGUAS $presentlang"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
CATALOGS=
|
||||
JAVACATALOGS=
|
||||
QTCATALOGS=
|
||||
TCLCATALOGS=
|
||||
CSHARPCATALOGS=
|
||||
if test -n "$INST_LINGUAS"; then
|
||||
for lang in $INST_LINGUAS; do
|
||||
CATALOGS="$CATALOGS $lang.gmo"
|
||||
JAVACATALOGS="$JAVACATALOGS \$(DOMAIN)_$lang.properties"
|
||||
QTCATALOGS="$QTCATALOGS $lang.qm"
|
||||
frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
|
||||
TCLCATALOGS="$TCLCATALOGS $frobbedlang.msg"
|
||||
frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
|
||||
CSHARPCATALOGS="$CSHARPCATALOGS $frobbedlang/\$(DOMAIN).resources.dll"
|
||||
done
|
||||
fi
|
||||
|
||||
sed -e "s|@POTFILES_DEPS@|$POTFILES_DEPS|g" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@PROPERTIESFILES@|$PROPERTIESFILES|g" -e "s|@CLASSFILES@|$CLASSFILES|g" -e "s|@QMFILES@|$QMFILES|g" -e "s|@MSGFILES@|$MSGFILES|g" -e "s|@RESOURCESDLLFILES@|$RESOURCESDLLFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@JAVACATALOGS@|$JAVACATALOGS|g" -e "s|@QTCATALOGS@|$QTCATALOGS|g" -e "s|@TCLCATALOGS@|$TCLCATALOGS|g" -e "s|@CSHARPCATALOGS@|$CSHARPCATALOGS|g" -e 's,^#distdir:,distdir:,' < "$ac_file" > "$ac_file.tmp"
|
||||
tab=`printf '\t'`
|
||||
if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then
|
||||
# Add dependencies that cannot be formulated as a simple suffix rule.
|
||||
for lang in $ALL_LINGUAS; do
|
||||
frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
|
||||
cat >> "$ac_file.tmp" <<EOF
|
||||
$frobbedlang.msg: $lang.po
|
||||
${tab}@echo "\$(MSGFMT) -c --tcl -d \$(srcdir) -l $lang $srcdirpre$lang.po"; \
|
||||
${tab}\$(MSGFMT) -c --tcl -d "\$(srcdir)" -l $lang $srcdirpre$lang.po || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
|
||||
EOF
|
||||
done
|
||||
fi
|
||||
if grep -l '@CSHARPCATALOGS@' "$ac_file" > /dev/null; then
|
||||
# Add dependencies that cannot be formulated as a simple suffix rule.
|
||||
for lang in $ALL_LINGUAS; do
|
||||
frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
|
||||
cat >> "$ac_file.tmp" <<EOF
|
||||
$frobbedlang/\$(DOMAIN).resources.dll: $lang.po
|
||||
${tab}@echo "\$(MSGFMT) -c --csharp -d \$(srcdir) -l $lang $srcdirpre$lang.po -r \$(DOMAIN)"; \
|
||||
${tab}\$(MSGFMT) -c --csharp -d "\$(srcdir)" -l $lang $srcdirpre$lang.po -r "\$(DOMAIN)" || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
|
||||
EOF
|
||||
done
|
||||
fi
|
||||
if test -n "$POMAKEFILEDEPS"; then
|
||||
cat >> "$ac_file.tmp" <<EOF
|
||||
Makefile: $POMAKEFILEDEPS
|
||||
EOF
|
||||
fi
|
||||
mv "$ac_file.tmp" "$ac_file"
|
||||
])
|
||||
|
||||
dnl Initializes the accumulator used by AM_XGETTEXT_OPTION.
|
||||
AC_DEFUN([AM_XGETTEXT_OPTION_INIT],
|
||||
[
|
||||
XGETTEXT_EXTRA_OPTIONS=
|
||||
])
|
||||
|
||||
dnl Registers an option to be passed to xgettext in the po subdirectory.
|
||||
AC_DEFUN([AM_XGETTEXT_OPTION],
|
||||
[
|
||||
AC_REQUIRE([AM_XGETTEXT_OPTION_INIT])
|
||||
XGETTEXT_EXTRA_OPTIONS="$XGETTEXT_EXTRA_OPTIONS $1"
|
||||
])
|
|
@ -0,0 +1,91 @@
|
|||
# progtest.m4 serial 7 (gettext-0.18.2)
|
||||
dnl Copyright (C) 1996-2003, 2005, 2008-2016 Free Software Foundation, Inc.
|
||||
dnl This file is free software; the Free Software Foundation
|
||||
dnl gives unlimited permission to copy and/or distribute it,
|
||||
dnl with or without modifications, as long as this notice is preserved.
|
||||
dnl
|
||||
dnl This file can be used in projects which are not available under
|
||||
dnl the GNU General Public License or the GNU Library General Public
|
||||
dnl License but which still want to provide support for the GNU gettext
|
||||
dnl functionality.
|
||||
dnl Please note that the actual code of the GNU gettext library is covered
|
||||
dnl by the GNU Library General Public License, and the rest of the GNU
|
||||
dnl gettext package is covered by the GNU General Public License.
|
||||
dnl They are *not* in the public domain.
|
||||
|
||||
dnl Authors:
|
||||
dnl Ulrich Drepper <drepper@cygnus.com>, 1996.
|
||||
|
||||
AC_PREREQ([2.50])
|
||||
|
||||
# Search path for a program which passes the given test.
|
||||
|
||||
dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,
|
||||
dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])
|
||||
AC_DEFUN([AM_PATH_PROG_WITH_TEST],
|
||||
[
|
||||
# Prepare PATH_SEPARATOR.
|
||||
# The user is always right.
|
||||
if test "${PATH_SEPARATOR+set}" != set; then
|
||||
# Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
|
||||
# contains only /bin. Note that ksh looks also at the FPATH variable,
|
||||
# so we have to set that as well for the test.
|
||||
PATH_SEPARATOR=:
|
||||
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
|
||||
&& { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
|
||||
|| PATH_SEPARATOR=';'
|
||||
}
|
||||
fi
|
||||
|
||||
# Find out how to test for executable files. Don't use a zero-byte file,
|
||||
# as systems may use methods other than mode bits to determine executability.
|
||||
cat >conf$$.file <<_ASEOF
|
||||
#! /bin/sh
|
||||
exit 0
|
||||
_ASEOF
|
||||
chmod +x conf$$.file
|
||||
if test -x conf$$.file >/dev/null 2>&1; then
|
||||
ac_executable_p="test -x"
|
||||
else
|
||||
ac_executable_p="test -f"
|
||||
fi
|
||||
rm -f conf$$.file
|
||||
|
||||
# Extract the first word of "$2", so it can be a program name with args.
|
||||
set dummy $2; ac_word=[$]2
|
||||
AC_MSG_CHECKING([for $ac_word])
|
||||
AC_CACHE_VAL([ac_cv_path_$1],
|
||||
[case "[$]$1" in
|
||||
[[\\/]]* | ?:[[\\/]]*)
|
||||
ac_cv_path_$1="[$]$1" # Let the user override the test with a path.
|
||||
;;
|
||||
*)
|
||||
ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR
|
||||
for ac_dir in ifelse([$5], , $PATH, [$5]); do
|
||||
IFS="$ac_save_IFS"
|
||||
test -z "$ac_dir" && ac_dir=.
|
||||
for ac_exec_ext in '' $ac_executable_extensions; do
|
||||
if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then
|
||||
echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD
|
||||
if [$3]; then
|
||||
ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext"
|
||||
break 2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
IFS="$ac_save_IFS"
|
||||
dnl If no 4th arg is given, leave the cache variable unset,
|
||||
dnl so AC_PATH_PROGS will keep looking.
|
||||
ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4"
|
||||
])dnl
|
||||
;;
|
||||
esac])dnl
|
||||
$1="$ac_cv_path_$1"
|
||||
if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then
|
||||
AC_MSG_RESULT([$][$1])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
AC_SUBST([$1])dnl
|
||||
])
|
|
@ -0,0 +1,215 @@
|
|||
#! /bin/sh
|
||||
# Common wrapper for a few potentially missing GNU programs.
|
||||
|
||||
scriptversion=2018-03-07.03; # UTC
|
||||
|
||||
# Copyright (C) 1996-2020 Free Software Foundation, Inc.
|
||||
# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
|
||||
|
||||
# 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, 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/>.
|
||||
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
if test $# -eq 0; then
|
||||
echo 1>&2 "Try '$0 --help' for more information"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case $1 in
|
||||
|
||||
--is-lightweight)
|
||||
# Used by our autoconf macros to check whether the available missing
|
||||
# script is modern enough.
|
||||
exit 0
|
||||
;;
|
||||
|
||||
--run)
|
||||
# Back-compat with the calling convention used by older automake.
|
||||
shift
|
||||
;;
|
||||
|
||||
-h|--h|--he|--hel|--help)
|
||||
echo "\
|
||||
$0 [OPTION]... PROGRAM [ARGUMENT]...
|
||||
|
||||
Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
|
||||
to PROGRAM being missing or too old.
|
||||
|
||||
Options:
|
||||
-h, --help display this help and exit
|
||||
-v, --version output version information and exit
|
||||
|
||||
Supported PROGRAM values:
|
||||
aclocal autoconf autoheader autom4te automake makeinfo
|
||||
bison yacc flex lex help2man
|
||||
|
||||
Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
|
||||
'g' are ignored when checking the name.
|
||||
|
||||
Send bug reports to <bug-automake@gnu.org>."
|
||||
exit $?
|
||||
;;
|
||||
|
||||
-v|--v|--ve|--ver|--vers|--versi|--versio|--version)
|
||||
echo "missing $scriptversion (GNU Automake)"
|
||||
exit $?
|
||||
;;
|
||||
|
||||
-*)
|
||||
echo 1>&2 "$0: unknown '$1' option"
|
||||
echo 1>&2 "Try '$0 --help' for more information"
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
# Run the given program, remember its exit status.
|
||||
"$@"; st=$?
|
||||
|
||||
# If it succeeded, we are done.
|
||||
test $st -eq 0 && exit 0
|
||||
|
||||
# Also exit now if we it failed (or wasn't found), and '--version' was
|
||||
# passed; such an option is passed most likely to detect whether the
|
||||
# program is present and works.
|
||||
case $2 in --version|--help) exit $st;; esac
|
||||
|
||||
# Exit code 63 means version mismatch. This often happens when the user
|
||||
# tries to use an ancient version of a tool on a file that requires a
|
||||
# minimum version.
|
||||
if test $st -eq 63; then
|
||||
msg="probably too old"
|
||||
elif test $st -eq 127; then
|
||||
# Program was missing.
|
||||
msg="missing on your system"
|
||||
else
|
||||
# Program was found and executed, but failed. Give up.
|
||||
exit $st
|
||||
fi
|
||||
|
||||
perl_URL=https://www.perl.org/
|
||||
flex_URL=https://github.com/westes/flex
|
||||
gnu_software_URL=https://www.gnu.org/software
|
||||
|
||||
program_details ()
|
||||
{
|
||||
case $1 in
|
||||
aclocal|automake)
|
||||
echo "The '$1' program is part of the GNU Automake package:"
|
||||
echo "<$gnu_software_URL/automake>"
|
||||
echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
|
||||
echo "<$gnu_software_URL/autoconf>"
|
||||
echo "<$gnu_software_URL/m4/>"
|
||||
echo "<$perl_URL>"
|
||||
;;
|
||||
autoconf|autom4te|autoheader)
|
||||
echo "The '$1' program is part of the GNU Autoconf package:"
|
||||
echo "<$gnu_software_URL/autoconf/>"
|
||||
echo "It also requires GNU m4 and Perl in order to run:"
|
||||
echo "<$gnu_software_URL/m4/>"
|
||||
echo "<$perl_URL>"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
give_advice ()
|
||||
{
|
||||
# Normalize program name to check for.
|
||||
normalized_program=`echo "$1" | sed '
|
||||
s/^gnu-//; t
|
||||
s/^gnu//; t
|
||||
s/^g//; t'`
|
||||
|
||||
printf '%s\n' "'$1' is $msg."
|
||||
|
||||
configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
|
||||
case $normalized_program in
|
||||
autoconf*)
|
||||
echo "You should only need it if you modified 'configure.ac',"
|
||||
echo "or m4 files included by it."
|
||||
program_details 'autoconf'
|
||||
;;
|
||||
autoheader*)
|
||||
echo "You should only need it if you modified 'acconfig.h' or"
|
||||
echo "$configure_deps."
|
||||
program_details 'autoheader'
|
||||
;;
|
||||
automake*)
|
||||
echo "You should only need it if you modified 'Makefile.am' or"
|
||||
echo "$configure_deps."
|
||||
program_details 'automake'
|
||||
;;
|
||||
aclocal*)
|
||||
echo "You should only need it if you modified 'acinclude.m4' or"
|
||||
echo "$configure_deps."
|
||||
program_details 'aclocal'
|
||||
;;
|
||||
autom4te*)
|
||||
echo "You might have modified some maintainer files that require"
|
||||
echo "the 'autom4te' program to be rebuilt."
|
||||
program_details 'autom4te'
|
||||
;;
|
||||
bison*|yacc*)
|
||||
echo "You should only need it if you modified a '.y' file."
|
||||
echo "You may want to install the GNU Bison package:"
|
||||
echo "<$gnu_software_URL/bison/>"
|
||||
;;
|
||||
lex*|flex*)
|
||||
echo "You should only need it if you modified a '.l' file."
|
||||
echo "You may want to install the Fast Lexical Analyzer package:"
|
||||
echo "<$flex_URL>"
|
||||
;;
|
||||
help2man*)
|
||||
echo "You should only need it if you modified a dependency" \
|
||||
"of a man page."
|
||||
echo "You may want to install the GNU Help2man package:"
|
||||
echo "<$gnu_software_URL/help2man/>"
|
||||
;;
|
||||
makeinfo*)
|
||||
echo "You should only need it if you modified a '.texi' file, or"
|
||||
echo "any other file indirectly affecting the aspect of the manual."
|
||||
echo "You might want to install the Texinfo package:"
|
||||
echo "<$gnu_software_URL/texinfo/>"
|
||||
echo "The spurious makeinfo call might also be the consequence of"
|
||||
echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
|
||||
echo "want to install GNU make:"
|
||||
echo "<$gnu_software_URL/make/>"
|
||||
;;
|
||||
*)
|
||||
echo "You might have modified some files without having the proper"
|
||||
echo "tools for further handling them. Check the 'README' file, it"
|
||||
echo "often tells you about the needed prerequisites for installing"
|
||||
echo "this package. You may also peek at any GNU archive site, in"
|
||||
echo "case some other package contains this missing '$1' program."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
give_advice "$1" | sed -e '1s/^/WARNING: /' \
|
||||
-e '2,$s/^/ /' >&2
|
||||
|
||||
# Propagate the correct exit status (expected to be 127 for a program
|
||||
# not found, 63 for a program that failed due to version mismatch).
|
||||
exit $st
|
||||
|
||||
# Local variables:
|
||||
# eval: (add-hook 'before-save-hook 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-time-zone: "UTC0"
|
||||
# time-stamp-end: "; # UTC"
|
||||
# End:
|
|
@ -0,0 +1,12 @@
|
|||
2019-12-26 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* Makefile.in.in: New file, from gettext-0.19.8.1.
|
||||
* Rules-quot: New file, from gettext-0.19.8.1.
|
||||
* boldquot.sed: New file, from gettext-0.19.8.1.
|
||||
* en@boldquot.header: New file, from gettext-0.19.8.1.
|
||||
* en@quot.header: New file, from gettext-0.19.8.1.
|
||||
* insert-header.sin: New file, from gettext-0.19.8.1.
|
||||
* quot.sed: New file, from gettext-0.19.8.1.
|
||||
* remove-potcdate.sin: New file, from gettext-0.19.8.1.
|
||||
* POTFILES.in: New file.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# Set of available languages.
|
||||
en@boldquot en@quot it
|
|
@ -0,0 +1,483 @@
|
|||
# Makefile for PO directory in any package using GNU gettext.
|
||||
# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification,
|
||||
# are permitted in any medium without royalty provided the copyright
|
||||
# notice and this notice are preserved. This file is offered as-is,
|
||||
# without any warranty.
|
||||
#
|
||||
# Origin: gettext-0.19.8
|
||||
GETTEXT_MACRO_VERSION = 0.19
|
||||
|
||||
PACKAGE = @PACKAGE@
|
||||
VERSION = @VERSION@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
|
||||
SED = @SED@
|
||||
SHELL = /bin/sh
|
||||
@SET_MAKE@
|
||||
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
datarootdir = @datarootdir@
|
||||
datadir = @datadir@
|
||||
localedir = @localedir@
|
||||
gettextsrcdir = $(datadir)/gettext/po
|
||||
|
||||
INSTALL = @INSTALL@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
|
||||
# We use $(mkdir_p).
|
||||
# In automake <= 1.9.x, $(mkdir_p) is defined either as "mkdir -p --" or as
|
||||
# "$(mkinstalldirs)" or as "$(install_sh) -d". For these automake versions,
|
||||
# @install_sh@ does not start with $(SHELL), so we add it.
|
||||
# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined
|
||||
# either as "/path/to/mkdir -p" or ".../install-sh -c -d". For these automake
|
||||
# versions, $(mkinstalldirs) and $(install_sh) are unused.
|
||||
mkinstalldirs = $(SHELL) @install_sh@ -d
|
||||
install_sh = $(SHELL) @install_sh@
|
||||
MKDIR_P = @MKDIR_P@
|
||||
mkdir_p = @mkdir_p@
|
||||
|
||||
# When building gettext-tools, we prefer to use the built programs
|
||||
# rather than installed programs. However, we can't do that when we
|
||||
# are cross compiling.
|
||||
CROSS_COMPILING = @CROSS_COMPILING@
|
||||
|
||||
GMSGFMT_ = @GMSGFMT@
|
||||
GMSGFMT_no = @GMSGFMT@
|
||||
GMSGFMT_yes = @GMSGFMT_015@
|
||||
GMSGFMT = $(GMSGFMT_$(USE_MSGCTXT))
|
||||
MSGFMT_ = @MSGFMT@
|
||||
MSGFMT_no = @MSGFMT@
|
||||
MSGFMT_yes = @MSGFMT_015@
|
||||
MSGFMT = $(MSGFMT_$(USE_MSGCTXT))
|
||||
XGETTEXT_ = @XGETTEXT@
|
||||
XGETTEXT_no = @XGETTEXT@
|
||||
XGETTEXT_yes = @XGETTEXT_015@
|
||||
XGETTEXT = $(XGETTEXT_$(USE_MSGCTXT))
|
||||
MSGMERGE = msgmerge
|
||||
MSGMERGE_UPDATE = @MSGMERGE@ --update
|
||||
MSGINIT = msginit
|
||||
MSGCONV = msgconv
|
||||
MSGFILTER = msgfilter
|
||||
|
||||
POFILES = @POFILES@
|
||||
GMOFILES = @GMOFILES@
|
||||
UPDATEPOFILES = @UPDATEPOFILES@
|
||||
DUMMYPOFILES = @DUMMYPOFILES@
|
||||
DISTFILES.common = Makefile.in.in remove-potcdate.sin \
|
||||
$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3)
|
||||
DISTFILES = $(DISTFILES.common) Makevars POTFILES.in \
|
||||
$(POFILES) $(GMOFILES) \
|
||||
$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)
|
||||
|
||||
POTFILES = \
|
||||
|
||||
CATALOGS = @CATALOGS@
|
||||
|
||||
POFILESDEPS_ = $(srcdir)/$(DOMAIN).pot
|
||||
POFILESDEPS_yes = $(POFILESDEPS_)
|
||||
POFILESDEPS_no =
|
||||
POFILESDEPS = $(POFILESDEPS_$(PO_DEPENDS_ON_POT))
|
||||
|
||||
DISTFILESDEPS_ = update-po
|
||||
DISTFILESDEPS_yes = $(DISTFILESDEPS_)
|
||||
DISTFILESDEPS_no =
|
||||
DISTFILESDEPS = $(DISTFILESDEPS_$(DIST_DEPENDS_ON_UPDATE_PO))
|
||||
|
||||
# Makevars gets inserted here. (Don't remove this line!)
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update
|
||||
|
||||
.po.mo:
|
||||
@echo "$(MSGFMT) -c -o $@ $<"; \
|
||||
$(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
|
||||
|
||||
.po.gmo:
|
||||
@lang=`echo $* | sed -e 's,.*/,,'`; \
|
||||
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
|
||||
echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o $${lang}.gmo $${lang}.po"; \
|
||||
cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
|
||||
|
||||
.sin.sed:
|
||||
sed -e '/^#/d' $< > t-$@
|
||||
mv t-$@ $@
|
||||
|
||||
|
||||
all: all-@USE_NLS@
|
||||
|
||||
all-yes: stamp-po
|
||||
all-no:
|
||||
|
||||
# Ensure that the gettext macros and this Makefile.in.in are in sync.
|
||||
CHECK_MACRO_VERSION = \
|
||||
test "$(GETTEXT_MACRO_VERSION)" = "@GETTEXT_MACRO_VERSION@" \
|
||||
|| { echo "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf macros are from gettext version @GETTEXT_MACRO_VERSION@" 1>&2; \
|
||||
exit 1; \
|
||||
}
|
||||
|
||||
# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no
|
||||
# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because
|
||||
# we don't want to bother translators with empty POT files). We assume that
|
||||
# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty.
|
||||
# In this case, stamp-po is a nop (i.e. a phony target).
|
||||
|
||||
# stamp-po is a timestamp denoting the last time at which the CATALOGS have
|
||||
# been loosely updated. Its purpose is that when a developer or translator
|
||||
# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,
|
||||
# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent
|
||||
# invocations of "make" will do nothing. This timestamp would not be necessary
|
||||
# if updating the $(CATALOGS) would always touch them; however, the rule for
|
||||
# $(POFILES) has been designed to not touch files that don't need to be
|
||||
# changed.
|
||||
stamp-po: $(srcdir)/$(DOMAIN).pot
|
||||
@$(CHECK_MACRO_VERSION)
|
||||
test ! -f $(srcdir)/$(DOMAIN).pot || \
|
||||
test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES)
|
||||
@test ! -f $(srcdir)/$(DOMAIN).pot || { \
|
||||
echo "touch stamp-po" && \
|
||||
echo timestamp > stamp-poT && \
|
||||
mv stamp-poT stamp-po; \
|
||||
}
|
||||
|
||||
# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
|
||||
# otherwise packages like GCC can not be built if only parts of the source
|
||||
# have been downloaded.
|
||||
|
||||
# This target rebuilds $(DOMAIN).pot; it is an expensive operation.
|
||||
# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.
|
||||
# The determination of whether the package xyz is a GNU one is based on the
|
||||
# heuristic whether some file in the top level directory mentions "GNU xyz".
|
||||
# If GNU 'find' is available, we avoid grepping through monster files.
|
||||
$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed
|
||||
package_gnu="$(PACKAGE_GNU)"; \
|
||||
test -n "$$package_gnu" || { \
|
||||
if { if (LC_ALL=C find --version) 2>/dev/null | grep GNU >/dev/null; then \
|
||||
LC_ALL=C find -L $(top_srcdir) -maxdepth 1 -type f \
|
||||
-size -10000000c -exec grep 'GNU @PACKAGE@' \
|
||||
/dev/null '{}' ';' 2>/dev/null; \
|
||||
else \
|
||||
LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null; \
|
||||
fi; \
|
||||
} | grep -v 'libtool:' >/dev/null; then \
|
||||
package_gnu=yes; \
|
||||
else \
|
||||
package_gnu=no; \
|
||||
fi; \
|
||||
}; \
|
||||
if test "$$package_gnu" = "yes"; then \
|
||||
package_prefix='GNU '; \
|
||||
else \
|
||||
package_prefix=''; \
|
||||
fi; \
|
||||
if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \
|
||||
msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \
|
||||
else \
|
||||
msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \
|
||||
fi; \
|
||||
case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \
|
||||
$(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
|
||||
--add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \
|
||||
--files-from=$(srcdir)/POTFILES.in \
|
||||
--copyright-holder='$(COPYRIGHT_HOLDER)' \
|
||||
--msgid-bugs-address="$$msgid_bugs_address" \
|
||||
;; \
|
||||
*) \
|
||||
$(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
|
||||
--add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \
|
||||
--files-from=$(srcdir)/POTFILES.in \
|
||||
--copyright-holder='$(COPYRIGHT_HOLDER)' \
|
||||
--package-name="$${package_prefix}@PACKAGE@" \
|
||||
--package-version='@VERSION@' \
|
||||
--msgid-bugs-address="$$msgid_bugs_address" \
|
||||
;; \
|
||||
esac
|
||||
test ! -f $(DOMAIN).po || { \
|
||||
if test -f $(srcdir)/$(DOMAIN).pot-header; then \
|
||||
sed -e '1,/^#$$/d' < $(DOMAIN).po > $(DOMAIN).1po && \
|
||||
cat $(srcdir)/$(DOMAIN).pot-header $(DOMAIN).1po > $(DOMAIN).po; \
|
||||
rm -f $(DOMAIN).1po; \
|
||||
fi; \
|
||||
if test -f $(srcdir)/$(DOMAIN).pot; then \
|
||||
sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \
|
||||
sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \
|
||||
if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \
|
||||
rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \
|
||||
else \
|
||||
rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \
|
||||
mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
|
||||
fi; \
|
||||
else \
|
||||
mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
|
||||
fi; \
|
||||
}
|
||||
|
||||
# This rule has no dependencies: we don't need to update $(DOMAIN).pot at
|
||||
# every "make" invocation, only create it when it is missing.
|
||||
# Only "make $(DOMAIN).pot-update" or "make dist" will force an update.
|
||||
$(srcdir)/$(DOMAIN).pot:
|
||||
$(MAKE) $(DOMAIN).pot-update
|
||||
|
||||
# This target rebuilds a PO file if $(DOMAIN).pot has changed.
|
||||
# Note that a PO file is not touched if it doesn't need to be changed.
|
||||
$(POFILES): $(POFILESDEPS)
|
||||
@lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
|
||||
if test -f "$(srcdir)/$${lang}.po"; then \
|
||||
test -f $(srcdir)/$(DOMAIN).pot || $(MAKE) $(srcdir)/$(DOMAIN).pot; \
|
||||
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
|
||||
echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot"; \
|
||||
cd $(srcdir) \
|
||||
&& { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
|
||||
$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po $(DOMAIN).pot;; \
|
||||
*) \
|
||||
$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot;; \
|
||||
esac; \
|
||||
}; \
|
||||
else \
|
||||
$(MAKE) $${lang}.po-create; \
|
||||
fi
|
||||
|
||||
|
||||
install: install-exec install-data
|
||||
install-exec:
|
||||
install-data: install-data-@USE_NLS@
|
||||
if test "$(PACKAGE)" = "gettext-tools"; then \
|
||||
$(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
|
||||
for file in $(DISTFILES.common) Makevars.template; do \
|
||||
$(INSTALL_DATA) $(srcdir)/$$file \
|
||||
$(DESTDIR)$(gettextsrcdir)/$$file; \
|
||||
done; \
|
||||
for file in Makevars; do \
|
||||
rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
|
||||
done; \
|
||||
else \
|
||||
: ; \
|
||||
fi
|
||||
install-data-no: all
|
||||
install-data-yes: all
|
||||
@catalogs='$(CATALOGS)'; \
|
||||
for cat in $$catalogs; do \
|
||||
cat=`basename $$cat`; \
|
||||
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
|
||||
dir=$(localedir)/$$lang/LC_MESSAGES; \
|
||||
$(mkdir_p) $(DESTDIR)$$dir; \
|
||||
if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \
|
||||
$(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \
|
||||
echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \
|
||||
for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
|
||||
if test -n "$$lc"; then \
|
||||
if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
|
||||
link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
|
||||
mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
|
||||
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
(cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
|
||||
for file in *; do \
|
||||
if test -f $$file; then \
|
||||
ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
|
||||
fi; \
|
||||
done); \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
|
||||
else \
|
||||
if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
|
||||
:; \
|
||||
else \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
fi; \
|
||||
fi; \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
|
||||
ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
|
||||
ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
|
||||
cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
|
||||
echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
install-strip: install
|
||||
|
||||
installdirs: installdirs-exec installdirs-data
|
||||
installdirs-exec:
|
||||
installdirs-data: installdirs-data-@USE_NLS@
|
||||
if test "$(PACKAGE)" = "gettext-tools"; then \
|
||||
$(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
|
||||
else \
|
||||
: ; \
|
||||
fi
|
||||
installdirs-data-no:
|
||||
installdirs-data-yes:
|
||||
@catalogs='$(CATALOGS)'; \
|
||||
for cat in $$catalogs; do \
|
||||
cat=`basename $$cat`; \
|
||||
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
|
||||
dir=$(localedir)/$$lang/LC_MESSAGES; \
|
||||
$(mkdir_p) $(DESTDIR)$$dir; \
|
||||
for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
|
||||
if test -n "$$lc"; then \
|
||||
if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
|
||||
link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
|
||||
mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
|
||||
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
(cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
|
||||
for file in *; do \
|
||||
if test -f $$file; then \
|
||||
ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
|
||||
fi; \
|
||||
done); \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
|
||||
else \
|
||||
if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
|
||||
:; \
|
||||
else \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
|
||||
fi; \
|
||||
fi; \
|
||||
fi; \
|
||||
done; \
|
||||
done
|
||||
|
||||
# Define this as empty until I found a useful application.
|
||||
installcheck:
|
||||
|
||||
uninstall: uninstall-exec uninstall-data
|
||||
uninstall-exec:
|
||||
uninstall-data: uninstall-data-@USE_NLS@
|
||||
if test "$(PACKAGE)" = "gettext-tools"; then \
|
||||
for file in $(DISTFILES.common) Makevars.template; do \
|
||||
rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
|
||||
done; \
|
||||
else \
|
||||
: ; \
|
||||
fi
|
||||
uninstall-data-no:
|
||||
uninstall-data-yes:
|
||||
catalogs='$(CATALOGS)'; \
|
||||
for cat in $$catalogs; do \
|
||||
cat=`basename $$cat`; \
|
||||
lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
|
||||
for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \
|
||||
rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
|
||||
done; \
|
||||
done
|
||||
|
||||
check: all
|
||||
|
||||
info dvi ps pdf html tags TAGS ctags CTAGS ID:
|
||||
|
||||
mostlyclean:
|
||||
rm -f remove-potcdate.sed
|
||||
rm -f stamp-poT
|
||||
rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po
|
||||
rm -fr *.o
|
||||
|
||||
clean: mostlyclean
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile Makefile.in POTFILES *.mo
|
||||
|
||||
maintainer-clean: distclean
|
||||
@echo "This command is intended for maintainers to use;"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
rm -f stamp-po $(GMOFILES)
|
||||
|
||||
distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
|
||||
dist distdir:
|
||||
test -z "$(DISTFILESDEPS)" || $(MAKE) $(DISTFILESDEPS)
|
||||
@$(MAKE) dist2
|
||||
# This is a separate target because 'update-po' must be executed before.
|
||||
dist2: stamp-po $(DISTFILES)
|
||||
dists="$(DISTFILES)"; \
|
||||
if test "$(PACKAGE)" = "gettext-tools"; then \
|
||||
dists="$$dists Makevars.template"; \
|
||||
fi; \
|
||||
if test -f $(srcdir)/$(DOMAIN).pot; then \
|
||||
dists="$$dists $(DOMAIN).pot stamp-po"; \
|
||||
fi; \
|
||||
if test -f $(srcdir)/ChangeLog; then \
|
||||
dists="$$dists ChangeLog"; \
|
||||
fi; \
|
||||
for i in 0 1 2 3 4 5 6 7 8 9; do \
|
||||
if test -f $(srcdir)/ChangeLog.$$i; then \
|
||||
dists="$$dists ChangeLog.$$i"; \
|
||||
fi; \
|
||||
done; \
|
||||
if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \
|
||||
for file in $$dists; do \
|
||||
if test -f $$file; then \
|
||||
cp -p $$file $(distdir) || exit 1; \
|
||||
else \
|
||||
cp -p $(srcdir)/$$file $(distdir) || exit 1; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
update-po: Makefile
|
||||
$(MAKE) $(DOMAIN).pot-update
|
||||
test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES)
|
||||
$(MAKE) update-gmo
|
||||
|
||||
# General rule for creating PO files.
|
||||
|
||||
.nop.po-create:
|
||||
@lang=`echo $@ | sed -e 's/\.po-create$$//'`; \
|
||||
echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \
|
||||
exit 1
|
||||
|
||||
# General rule for updating PO files.
|
||||
|
||||
.nop.po-update:
|
||||
@lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
|
||||
if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != "yes"; then PATH=`pwd`/../src:$$PATH; fi; \
|
||||
tmpdir=`pwd`; \
|
||||
echo "$$lang:"; \
|
||||
test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
|
||||
echo "$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \
|
||||
cd $(srcdir); \
|
||||
if { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
|
||||
$(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \
|
||||
*) \
|
||||
$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \
|
||||
esac; \
|
||||
}; then \
|
||||
if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
|
||||
rm -f $$tmpdir/$$lang.new.po; \
|
||||
else \
|
||||
if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
|
||||
:; \
|
||||
else \
|
||||
echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi; \
|
||||
else \
|
||||
echo "msgmerge for $$lang.po failed!" 1>&2; \
|
||||
rm -f $$tmpdir/$$lang.new.po; \
|
||||
fi
|
||||
|
||||
$(DUMMYPOFILES):
|
||||
|
||||
update-gmo: Makefile $(GMOFILES)
|
||||
@:
|
||||
|
||||
# Recreate Makefile by invoking config.status. Explicitly invoke the shell,
|
||||
# because execution permission bits may not work on the current file system.
|
||||
# Use @SHELL@, which is the shell determined by autoconf for the use by its
|
||||
# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient.
|
||||
Makefile: Makefile.in.in Makevars $(top_builddir)/config.status @POMAKEFILEDEPS@
|
||||
cd $(top_builddir) \
|
||||
&& @SHELL@ ./config.status $(subdir)/$@.in po-directories
|
||||
|
||||
force:
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make not to export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
|
@ -0,0 +1,78 @@
|
|||
# Makefile variables for PO directory in any package using GNU gettext.
|
||||
|
||||
# Usually the message domain is the same as the package name.
|
||||
DOMAIN = $(PACKAGE)
|
||||
|
||||
# These two variables depend on the location of this directory.
|
||||
subdir = po
|
||||
top_builddir = ..
|
||||
|
||||
# These options get passed to xgettext.
|
||||
XGETTEXT_OPTIONS = --keyword=_ --keyword=n_:1,2 --language=Lisp
|
||||
|
||||
# This is the copyright holder that gets inserted into the header of the
|
||||
# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
|
||||
# package. (Note that the msgstr strings, extracted from the package's
|
||||
# sources, belong to the copyright holder of the package.) Translators are
|
||||
# expected to transfer the copyright for their translations to this person
|
||||
# or entity, or to disclaim their copyright. The empty string stands for
|
||||
# the public domain; in this case the translators are expected to disclaim
|
||||
# their copyright.
|
||||
COPYRIGHT_HOLDER = cage
|
||||
|
||||
# This tells whether or not to prepend "GNU " prefix to the package
|
||||
# name that gets inserted into the header of the $(DOMAIN).pot file.
|
||||
# Possible values are "yes", "no", or empty. If it is empty, try to
|
||||
# detect it automatically by scanning the files in $(top_srcdir) for
|
||||
# "GNU packagename" string.
|
||||
PACKAGE_GNU =
|
||||
|
||||
# This is the email address or URL to which the translators shall report
|
||||
# bugs in the untranslated strings:
|
||||
# - Strings which are not entire sentences, see the maintainer guidelines
|
||||
# in the GNU gettext documentation, section 'Preparing Strings'.
|
||||
# - Strings which use unclear terms or require additional context to be
|
||||
# understood.
|
||||
# - Strings which make invalid assumptions about notation of date, time or
|
||||
# money.
|
||||
# - Pluralisation problems.
|
||||
# - Incorrect English spelling.
|
||||
# - Incorrect formatting.
|
||||
# It can be your email address, or a mailing list address where translators
|
||||
# can write to without being subscribed, or the URL of a web page through
|
||||
# which the translators can contact you.
|
||||
MSGID_BUGS_ADDRESS = https://notabug.org/cage/tinmop/
|
||||
|
||||
# This is the list of locale categories, beyond LC_MESSAGES, for which the
|
||||
# message catalogs shall be used. It is usually empty.
|
||||
EXTRA_LOCALE_CATEGORIES =
|
||||
|
||||
# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
|
||||
# context. Possible values are "yes" and "no". Set this to yes if the
|
||||
# package uses functions taking also a message context, like pgettext(), or
|
||||
# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
|
||||
USE_MSGCTXT = no
|
||||
|
||||
# These options get passed to msgmerge.
|
||||
# Useful options are in particular:
|
||||
# --previous to keep previous msgids of translated messages,
|
||||
# --quiet to reduce the verbosity.
|
||||
MSGMERGE_OPTIONS =
|
||||
|
||||
# These options get passed to msginit.
|
||||
# If you want to disable line wrapping when writing PO files, add
|
||||
# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
|
||||
# MSGINIT_OPTIONS.
|
||||
MSGINIT_OPTIONS =
|
||||
|
||||
# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
|
||||
# has changed. Possible values are "yes" and "no". Set this to no if
|
||||
# the POT file is checked in the repository and the version control
|
||||
# program ignores timestamps.
|
||||
PO_DEPENDS_ON_POT = yes
|
||||
|
||||
# This tells whether or not to forcibly update $(DOMAIN).pot and
|
||||
# regenerate PO files on "make dist". Possible values are "yes" and
|
||||
# "no". Set this to no if the POT file and PO files are maintained
|
||||
# externally.
|
||||
DIST_DEPENDS_ON_UPDATE_PO = yes
|
|
@ -0,0 +1,54 @@
|
|||
# List of source files which contain translatable strings.
|
||||
src/api-client.lisp
|
||||
src/box.lisp
|
||||
src/bs-tree.lisp
|
||||
src/command-line.lisp
|
||||
src/command-window.lisp
|
||||
src/complete-window.lisp
|
||||
src/complete.lisp
|
||||
src/conditions.lisp
|
||||
src/config.lisp
|
||||
src/config.lisp.in
|
||||
src/constants.lisp
|
||||
src/conversations-window.lisp
|
||||
src/date-formatter.lisp
|
||||
src/db-utils.lisp
|
||||
src/db.lisp
|
||||
src/emoji-shortcodes.lisp
|
||||
src/filesystem-utils.lisp
|
||||
src/follow-requests.lisp
|
||||
src/hooks.lisp
|
||||
src/html-utils.lisp
|
||||
src/interfaces.lisp
|
||||
src/keybindings-window.lisp
|
||||
src/keybindings.lisp
|
||||
src/line-oriented-window.lisp
|
||||
src/main-window.lisp
|
||||
src/main.lisp
|
||||
src/message-rendering-utils.lisp
|
||||
src/message-window.lisp
|
||||
src/misc-utils.lisp
|
||||
src/modeline-window.lisp
|
||||
src/modules.lisp
|
||||
src/mtree-utils.lisp
|
||||
src/notify-window.lisp
|
||||
src/num-utils.lisp
|
||||
src/os-utils.lisp
|
||||
src/package.lisp
|
||||
src/point-tracker.lisp
|
||||
src/priority-queue.lisp
|
||||
src/program-events.lisp
|
||||
src/queue.lisp
|
||||
src/rb-tree.lisp
|
||||
src/resources-utils.lisp
|
||||
src/sending-message.lisp
|
||||
src/software-configuration.lisp
|
||||
src/specials.lisp
|
||||
src/stack.lisp
|
||||
src/suggestions-window.lisp
|
||||
src/tags-window.lisp
|
||||
src/text-utils.lisp
|
||||
src/thread-window.lisp
|
||||
src/tui-utils.lisp
|
||||
src/ui-goodies.lisp
|
||||
src/windows.lisp
|
|
@ -0,0 +1,58 @@
|
|||
# This file, Rules-quot, can be copied and used freely without restrictions.
|
||||
# Special Makefile rules for English message catalogs with quotation marks.
|
||||
|
||||
DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot
|
||||
|
||||
.SUFFIXES: .insert-header .po-update-en
|
||||
|
||||
en@quot.po-create:
|
||||
$(MAKE) en@quot.po-update
|
||||
en@boldquot.po-create:
|
||||
$(MAKE) en@boldquot.po-update
|
||||
|
||||
en@quot.po-update: en@quot.po-update-en
|
||||
en@boldquot.po-update: en@boldquot.po-update-en
|
||||
|
||||
.insert-header.po-update-en:
|
||||
@lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \
|
||||
if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != "yes"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \
|
||||
tmpdir=`pwd`; \
|
||||
echo "$$lang:"; \
|
||||
ll=`echo $$lang | sed -e 's/@.*//'`; \
|
||||
LC_ALL=C; export LC_ALL; \
|
||||
cd $(srcdir); \
|
||||
if $(MSGINIT) $(MSGINIT_OPTIONS) -i $(DOMAIN).pot --no-translator -l $$lang -o - 2>/dev/null \
|
||||
| $(SED) -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | \
|
||||
{ case `$(MSGFILTER) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
|
||||
'' | 0.[0-9] | 0.[0-9].* | 0.1[0-8] | 0.1[0-8].*) \
|
||||
$(MSGFILTER) $(SED) -f `echo $$lang | sed -e 's/.*@//'`.sed \
|
||||
;; \
|
||||
*) \
|
||||
$(MSGFILTER) `echo $$lang | sed -e 's/.*@//'` \
|
||||
;; \
|
||||
esac } 2>/dev/null > $$tmpdir/$$lang.new.po \
|
||||
; then \
|
||||
if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
|
||||
rm -f $$tmpdir/$$lang.new.po; \
|
||||
else \
|
||||
if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
|
||||
:; \
|
||||
else \
|
||||
echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi; \
|
||||
else \
|
||||
echo "creation of $$lang.po failed!" 1>&2; \
|
||||
rm -f $$tmpdir/$$lang.new.po; \
|
||||
fi
|
||||
|
||||
en@quot.insert-header: insert-header.sin
|
||||
sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header
|
||||
|
||||
en@boldquot.insert-header: insert-header.sin
|
||||
sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header
|
||||
|
||||
mostlyclean: mostlyclean-quot
|
||||
mostlyclean-quot:
|
||||
rm -f *.insert-header
|
|
@ -0,0 +1,10 @@
|
|||
s/"\([^"]*\)"/“\1”/g
|
||||
s/`\([^`']*\)'/‘\1’/g
|
||||
s/ '\([^`']*\)' / ‘\1’ /g
|
||||
s/ '\([^`']*\)'$/ ‘\1’/g
|
||||
s/^'\([^`']*\)' /‘\1’ /g
|
||||
s/“”/""/g
|
||||
s/“/“[1m/g
|
||||
s/”/[0m”/g
|
||||
s/‘/‘[1m/g
|
||||
s/’/[0m’/g
|
|
@ -0,0 +1,25 @@
|
|||
# All this catalog "translates" are quotation characters.
|
||||
# The msgids must be ASCII and therefore cannot contain real quotation
|
||||
# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
|
||||
# and double quote (0x22). These substitutes look strange; see
|
||||
# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
|
||||
#
|
||||
# This catalog translates grave accent (0x60) and apostrophe (0x27) to
|
||||
# left single quotation mark (U+2018) and right single quotation mark (U+2019).
|
||||
# It also translates pairs of apostrophe (0x27) to
|
||||
# left single quotation mark (U+2018) and right single quotation mark (U+2019)
|
||||
# and pairs of quotation mark (0x22) to
|
||||
# left double quotation mark (U+201C) and right double quotation mark (U+201D).
|
||||
#
|
||||
# When output to an UTF-8 terminal, the quotation characters appear perfectly.
|
||||
# When output to an ISO-8859-1 terminal, the single quotation marks are
|
||||
# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
|
||||
# grave/acute accent (by libiconv), and the double quotation marks are
|
||||
# transliterated to 0x22.
|
||||
# When output to an ASCII terminal, the single quotation marks are
|
||||
# transliterated to apostrophes, and the double quotation marks are
|
||||
# transliterated to 0x22.
|
||||
#
|
||||
# This catalog furthermore displays the text between the quotation marks in
|
||||
# bold face, assuming the VT100/XTerm escape sequences.
|
||||
#
|
|
@ -0,0 +1,22 @@
|
|||
# All this catalog "translates" are quotation characters.
|
||||
# The msgids must be ASCII and therefore cannot contain real quotation
|
||||
# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
|
||||
# and double quote (0x22). These substitutes look strange; see
|
||||
# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
|
||||
#
|
||||
# This catalog translates grave accent (0x60) and apostrophe (0x27) to
|
||||
# left single quotation mark (U+2018) and right single quotation mark (U+2019).
|
||||
# It also translates pairs of apostrophe (0x27) to
|
||||
# left single quotation mark (U+2018) and right single quotation mark (U+2019)
|
||||
# and pairs of quotation mark (0x22) to
|
||||
# left double quotation mark (U+201C) and right double quotation mark (U+201D).
|
||||
#
|
||||
# When output to an UTF-8 terminal, the quotation characters appear perfectly.
|
||||
# When output to an ISO-8859-1 terminal, the single quotation marks are
|
||||
# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
|
||||
# grave/acute accent (by libiconv), and the double quotation marks are
|
||||
# transliterated to 0x22.
|
||||
# When output to an ASCII terminal, the single quotation marks are
|
||||
# transliterated to apostrophes, and the double quotation marks are
|
||||
# transliterated to 0x22.
|
||||
#
|
|
@ -0,0 +1,23 @@
|
|||
# Sed script that inserts the file called HEADER before the header entry.
|
||||
#
|
||||
# At each occurrence of a line starting with "msgid ", we execute the following
|
||||
# commands. At the first occurrence, insert the file. At the following
|
||||
# occurrences, do nothing. The distinction between the first and the following
|
||||
# occurrences is achieved by looking at the hold space.
|
||||
/^msgid /{
|
||||
x
|
||||
# Test if the hold space is empty.
|
||||
s/m/m/
|
||||
ta
|
||||
# Yes it was empty. First occurrence. Read the file.
|
||||
r HEADER
|
||||
# Output the file's contents by reading the next line. But don't lose the
|
||||
# current line while doing this.
|
||||
g
|
||||
N
|
||||
bb
|
||||
:a
|
||||
# The hold space was nonempty. Following occurrences. Do nothing.
|
||||
x
|
||||
:b
|
||||
}
|
|
@ -0,0 +1,801 @@
|
|||
# Italian translations for tinmop package
|
||||
# Traduzioni italiane per il pacchetto tinmop..
|
||||
# Copyright (C) 2020 cage
|
||||
# This file is distributed under the same license as the tinmop package.
|
||||
# cage <cage@invalid.org>, 2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: tinmop 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: https://notabug.org/cage/tinmop/\n"
|
||||
"POT-Creation-Date: 2020-05-08 15:15+0200\n"
|
||||
"PO-Revision-Date: 2020-05-07 16:32+0200\n"
|
||||
"Last-Translator: cage <cage@invalid.org>\n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/api-client.lisp:118
|
||||
msgid "Please visit the address below."
|
||||
msgstr "Per favore visita l'indirizzo mostrato piu' sotto."
|
||||
|
||||
#: src/api-client.lisp:176
|
||||
#, lisp-format
|
||||
msgid ""
|
||||
"Credential invalid. Try to remove ~a and restart the software to "
|
||||
"authenticate again."
|
||||
msgstr ""
|
||||
"Credenziali non più valide. Provare a rimuovere ~a e riavviare il programma "
|
||||
"per riautenticarsi."
|
||||
|
||||
#: src/api-client.lisp:187
|
||||
msgid "Save address"
|
||||
msgstr "Salva l'indirizzo"
|
||||
|
||||
#: src/api-client.lisp:188
|
||||
msgid "Open address"
|
||||
msgstr "Apri l'indirizzo"
|
||||
|
||||
#: src/api-client.lisp:197
|
||||
msgid "This client has been authorized"
|
||||
msgstr "Questo client è stato autorizzato."
|
||||
|
||||
#: src/api-client.lisp:201
|
||||
msgid "Got a generic error when registering client"
|
||||
msgstr "Errore generico provando a registare il client"
|
||||
|
||||
#: src/api-client.lisp:203
|
||||
#, lisp-format
|
||||
msgid "File ~a saved"
|
||||
msgstr "File ~a salvato"
|
||||
|
||||
#: src/api-client.lisp:205
|
||||
msgid "Please enter below the file where to save the address"
|
||||
msgstr "Inserisci piu' sotto il file dove salvare l'indirizzo"
|
||||
|
||||
#: src/api-client.lisp:225
|
||||
msgid ""
|
||||
"Error: was not able to create server socket to listen for authorization code"
|
||||
msgstr ""
|
||||
"Errore: non sono stato in grado di creare il socket per la cattura del "
|
||||
"codice di autorizzazione."
|
||||
|
||||
#: src/api-client.lisp:631
|
||||
#, lisp-format
|
||||
msgid "Initializing empty credentials file in ~a"
|
||||
msgstr "Inizializzo credenziali vuote (segnaposto) nel file ~a"
|
||||
|
||||
#: src/command-line.lisp:21
|
||||
#, lisp-format
|
||||
msgid "~a version ~a~%"
|
||||
msgstr "~a versione ~a~%"
|
||||
|
||||
#: src/command-line.lisp:26
|
||||
msgid "Print help and exit"
|
||||
msgstr "Stampa un aiuto ed termina il programma"
|
||||
|
||||
#: src/command-line.lisp:30
|
||||
msgid "Print program information and exit"
|
||||
msgstr "Stampa informazioni sul programma e termina"
|
||||
|
||||
#: src/command-line.lisp:34
|
||||
msgid "Starting folder"
|
||||
msgstr "Cartella di partenza"
|
||||
|
||||
#: src/command-line.lisp:37
|
||||
msgid "FOLDER-NAME"
|
||||
msgstr "NOME-CARTELLA"
|
||||
|
||||
#: src/command-line.lisp:40
|
||||
msgid "Starting timeline"
|
||||
msgstr "Timeline di partenza"
|
||||
|
||||
#: src/command-line.lisp:42
|
||||
msgid "TIMELINE-NAME"
|
||||
msgstr "NOME-TIMELINE"
|
||||
|
||||
#: src/command-line.lisp:46
|
||||
msgid "Update timeline"
|
||||
msgstr "Aggiorna la timeline"
|
||||
|
||||
#: src/command-line.lisp:50
|
||||
msgid "Check follows requests"
|
||||
msgstr "Controlla le richieste di poterti seguire"
|
||||
|
||||
#: src/command-line.lisp:54
|
||||
msgid "Execute script"
|
||||
msgstr "Esegui script"
|
||||
|
||||
#: src/command-line.lisp:57
|
||||
msgid "SCRIPT-FILE"
|
||||
msgstr "SCRIPT-FILE"
|
||||
|
||||
#: src/command-window.lisp:298
|
||||
#, lisp-format
|
||||
msgid "Error: command ~a not found"
|
||||
msgstr "Errore: comando ~a non trovato"
|
||||
|
||||
#: src/conditions.lisp:67 src/conditions.lisp:71 src/db.lisp:2113
|
||||
#: src/message-rendering-utils.lisp:132 src/message-rendering-utils.lisp:166
|
||||
#: src/message-rendering-utils.lisp:171
|
||||
msgid "unknown"
|
||||
msgstr "sconosciuto"
|
||||
|
||||
#: src/conversations-window.lisp:152
|
||||
msgid "Conversations"
|
||||
msgstr "Conversazioni"
|
||||
|
||||
#: src/db.lisp:149
|
||||
msgid "federated"
|
||||
msgstr "federata"
|
||||
|
||||
#: src/db.lisp:151
|
||||
msgid "local"
|
||||
msgstr "locale"
|
||||
|
||||
#: src/db.lisp:153
|
||||
msgid "direct"
|
||||
msgstr "diretta"
|
||||
|
||||
#: src/db.lisp:155
|
||||
msgid "home"
|
||||
msgstr "home"
|
||||
|
||||
#: src/follow-requests.lisp:68
|
||||
msgid ""
|
||||
"Please evaluate the following requests, only items shown below will be "
|
||||
"accepted, deleted ones will be rejected:"
|
||||
msgstr ""
|
||||
"Per favore, prendi in considerazione le richieste qui sotto riportate. Se "
|
||||
"cancelli un elemento la corrispondente richiesta sarà scartata altrimenti "
|
||||
"verrà accettata."
|
||||
|
||||
#: src/html-utils.lisp:104
|
||||
msgid "No address found"
|
||||
msgstr "nessun indirizzo trovato"
|
||||
|
||||
#: src/keybindings.lisp:396
|
||||
msgid "Enter"
|
||||
msgstr "Invio"
|
||||
|
||||
#: src/keybindings.lisp:398
|
||||
msgid "Delete"
|
||||
msgstr "Canc"
|
||||
|
||||
#: src/keybindings.lisp:400
|
||||
msgid "Page-up"
|
||||
msgstr "Pagina-su"
|
||||
|
||||
#: src/keybindings.lisp:402
|
||||
msgid "Page-down"
|
||||
msgstr "Pagina-giù"
|
||||
|
||||
#: src/keybindings.lisp:458
|
||||
msgid "No documentation available, you can help! :-)"
|
||||
msgstr "Nessuna documentazione disponbile, aiutaci! :-)"
|
||||
|
||||
#: src/keybindings.lisp:490
|
||||
msgid "Focused window keys"
|
||||
msgstr "Tasti finestra attiva"
|
||||
|
||||
#: src/keybindings.lisp:491
|
||||
msgid "Global keys"
|
||||
msgstr "Mappa tasti globale"
|
||||
|
||||
#: src/keybindings.lisp:507
|
||||
msgid "Quick help"
|
||||
msgstr "Aiuto rapido"
|
||||
|
||||
#: src/line-oriented-window.lisp:269 src/ui-goodies.lisp:74
|
||||
#: src/ui-goodies.lisp:88
|
||||
msgid "Information"
|
||||
msgstr "Informazione"
|
||||
|
||||
#: src/message-rendering-utils.lisp:69
|
||||
msgid "This message will *not* be crypted"
|
||||
msgstr "Questo messaggion *non* sarà cifrato"
|
||||
|
||||
#: src/message-rendering-utils.lisp:77
|
||||
#, lisp-format
|
||||
msgid "No key to crypt message for ~s found"
|
||||
msgstr "Non è stata trovata una chiave per cifrare il messaggio destinato a ~s"
|
||||
|
||||
#: src/message-rendering-utils.lisp:114
|
||||
#, lisp-format
|
||||
msgid "Unable to find the crypto key for user ~s."
|
||||
msgstr "Non trovo chiave crittografica per l'utente ~s"
|
||||
|
||||
#: src/message-rendering-utils.lisp:129
|
||||
msgid "invalid type"
|
||||
msgstr "tipo non valido"
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "image"
|
||||
msgstr "immagine"
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "gifv"
|
||||
msgstr "gifv"
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "video"
|
||||
msgstr "video"
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "audio"
|
||||
msgstr "audio"
|
||||
|
||||
#: src/message-rendering-utils.lisp:138
|
||||
#, lisp-format
|
||||
msgid "description: ~a~%"
|
||||
msgstr "descrizione: ~a~%"
|
||||
|
||||
#: src/message-rendering-utils.lisp:141
|
||||
#, lisp-format
|
||||
msgid "size: ~aX~a pixels~%"
|
||||
msgstr "dimensioni: ~aX~a pixels~%"
|
||||
|
||||
#: src/message-rendering-utils.lisp:148
|
||||
#, lisp-format
|
||||
msgid "duration: ~a~%"
|
||||
msgstr "durata ~a~%"
|
||||
|
||||
#: src/message-rendering-utils.lisp:159 src/sending-message.lisp:132
|
||||
msgid "Attachments"
|
||||
msgstr "Allegati"
|
||||
|
||||
#: src/message-rendering-utils.lisp:168
|
||||
#, lisp-format
|
||||
msgid "type: ~a~%metadata~%~a~%address: ~a~2%"
|
||||
msgstr "tipo: ~a~%metadati~%~a~%indirizzo: ~a~2%"
|
||||
|
||||
#: src/message-rendering-utils.lisp:210
|
||||
msgid "From: "
|
||||
msgstr "Scritto da: "
|
||||
|
||||
#: src/message-rendering-utils.lisp:211
|
||||
msgid "Boosted: "
|
||||
msgstr "Rilanciato per:"
|
||||
|
||||
#: src/message-rendering-utils.lisp:215
|
||||
msgid "Date: "
|
||||
msgstr "Scritto il: "
|
||||
|
||||
#: src/message-window.lisp:218
|
||||
msgid "Messages"
|
||||
msgstr "Messaggi"
|
||||
|
||||
#: src/modeline-window.lisp:29
|
||||
msgid "modeline"
|
||||
msgstr "modeline"
|
||||
|
||||
#: src/modules.lisp:33
|
||||
#, lisp-format
|
||||
msgid ""
|
||||
"Unrecoverable error: file ~a not found in any of the directory ~a ~a ~a ~a"
|
||||
msgstr ""
|
||||
"Errore non rimediabile: file ~a non trovato in nessuna dellle seguenti "
|
||||
"directory: ~a ~a ~a ~a"
|
||||
|
||||
#: src/notify-window.lisp:63
|
||||
#, lisp-format
|
||||
msgid "~a pending"
|
||||
msgid_plural "~a pending"
|
||||
msgstr[0] "~a in attesa"
|
||||
msgstr[1] "Altre ~a in attesa"
|
||||
|
||||
#: src/os-utils.lisp:55
|
||||
msgid ""
|
||||
"No editor found, please configure the 'editor' directive in your "
|
||||
"configuration file"
|
||||
msgstr ""
|
||||
"Nessun editor trovato, per favore configura la direttiva 'editor' nel tuo "
|
||||
"file di configurazione."
|
||||
|
||||
#: src/program-events.lisp:41
|
||||
#, lisp-format
|
||||
msgid "Error: ~a"
|
||||
msgstr "Errore: ~a"
|
||||
|
||||
#: src/program-events.lisp:413
|
||||
msgid "No message selected!"
|
||||
msgstr "Nessun messaggio selezionato!"
|
||||
|
||||
#: src/program-events.lisp:563
|
||||
msgid "Message sent."
|
||||
msgstr "Messaggio spedito"
|
||||
|
||||
#: src/program-events.lisp:618
|
||||
#, lisp-format
|
||||
msgid "Downloaded new messages for tag ~a"
|
||||
msgstr "Scaricati nuovi messaggi per l'etichetta ~a."
|
||||
|
||||
#: src/resources-utils.lisp:55
|
||||
#, lisp-format
|
||||
msgid "Unrecoverable error: cannot find ~s in either ~s or ~s."
|
||||
msgstr "Errore non rimediabile: non trovo ~s né in ~s né ~s."
|
||||
|
||||
#: src/sending-message.lisp:114
|
||||
msgid "none"
|
||||
msgstr "nessuno"
|
||||
|
||||
#: src/sending-message.lisp:115
|
||||
msgid "Reply to: "
|
||||
msgstr "Risposta per: "
|
||||
|
||||
#: src/sending-message.lisp:116
|
||||
msgid "Subject:"
|
||||
msgstr "Oggetto del messaggio: "
|
||||
|
||||
#: src/sending-message.lisp:117
|
||||
msgid "Visibility:"
|
||||
msgstr "Visibilità:"
|
||||
|
||||
#: src/software-configuration.lisp:415
|
||||
msgid "This message was crypted."
|
||||
msgstr "Questo messaggion era cifrato."
|
||||
|
||||
#: src/suggestions-window.lisp:33
|
||||
#, lisp-format
|
||||
msgid "Page ~a of ~a"
|
||||
msgstr "Pagina ~a di ~a"
|
||||
|
||||
#: src/tags-window.lisp:140
|
||||
msgid "Subscribed tags"
|
||||
msgstr "Sottoscrizioni"
|
||||
|
||||
#: src/text-utils.lisp:456
|
||||
#, lisp-format
|
||||
msgid "Can not fit column of width of ~a in a box of width ~a"
|
||||
msgstr ""
|
||||
"Non posso adattare una colonna di larghezza ~a in una scatola di larghezza ~a"
|
||||
|
||||
#: src/text-utils.lisp:570
|
||||
#, lisp-format
|
||||
msgid "Unrecoverable error: ~a can not be fitted in a box of width ~a"
|
||||
msgstr ""
|
||||
"Errore non rimediabile: ~a non può adattarsi ad una scatola di larghezza ~a"
|
||||
|
||||
#: src/thread-window.lisp:134
|
||||
msgid "no timeline selected"
|
||||
msgstr "Nessuna timeline selezionata"
|
||||
|
||||
#: src/thread-window.lisp:142
|
||||
msgid "no folder selected"
|
||||
msgstr "Nessuna cartella selezionata"
|
||||
|
||||
#: src/thread-window.lisp:397
|
||||
msgid "Missing subject"
|
||||
msgstr "Oggetto mancante"
|
||||
|
||||
#: src/thread-window.lisp:729
|
||||
#, lisp-format
|
||||
msgid "No message with index ~a exists."
|
||||
msgstr "Nessun messaggio esiste alla posizione ~a."
|
||||
|
||||
#: src/thread-window.lisp:838 src/thread-window.lisp:872
|
||||
#, lisp-format
|
||||
msgid "No next message that contains ~s exists."
|
||||
msgstr "Nessun messaggio successivo che contenga ~s esiste."
|
||||
|
||||
#: src/thread-window.lisp:844 src/thread-window.lisp:878
|
||||
#, lisp-format
|
||||
msgid "No previous message that contains ~s exists."
|
||||
msgstr "Nessun messaggio precedente che contenga ~s esiste."
|
||||
|
||||
#: src/thread-window.lisp:894
|
||||
msgid "No others unread messages exist."
|
||||
msgstr "Non ci sono altri messaggi non letti."
|
||||
|
||||
#: src/thread-window.lisp:905
|
||||
msgid "Threads"
|
||||
msgstr "Discussioni"
|
||||
|
||||
#: src/tui-utils.lisp:104
|
||||
#, lisp-format
|
||||
msgid "Unknown event ~a"
|
||||
msgstr "Evento sconosciuto ~a"
|
||||
|
||||
#: src/ui-goodies.lisp:21
|
||||
msgid "y"
|
||||
msgstr "s"
|
||||
|
||||
#: src/ui-goodies.lisp:43
|
||||
#, lisp-format
|
||||
msgid "Delete ~a message? [y/N] "
|
||||
msgid_plural "Delete ~a messages? [y/N] "
|
||||
msgstr[0] "Cancella ~a messaggio? [s/N] "
|
||||
msgstr[1] "Cancella ~a messaggi? [s/N] "
|
||||
|
||||
#: src/ui-goodies.lisp:58 src/ui-goodies.lisp:67
|
||||
msgid "Task completed"
|
||||
msgstr "Compito completato"
|
||||
|
||||
#: src/ui-goodies.lisp:81 src/ui-goodies.lisp:95
|
||||
msgid "Error"
|
||||
msgstr "Errore"
|
||||
|
||||
#: src/ui-goodies.lisp:117
|
||||
#, lisp-format
|
||||
msgid "File \"~a\" exists, overwrite?"
|
||||
msgstr "Il file \"~a\" esiste. Lo sovrascrivo?"
|
||||
|
||||
#: src/ui-goodies.lisp:119 src/ui-goodies.lisp:124 src/windows.lisp:507
|
||||
#: src/windows.lisp:563
|
||||
msgid "Cancel"
|
||||
msgstr "Annulla"
|
||||
|
||||
#: src/ui-goodies.lisp:129
|
||||
#, lisp-format
|
||||
msgid "Request failed: error code ~d message \"~a\""
|
||||
msgstr "Richiesta fallita codice di errore: ~d, messaggio: ~a\""
|
||||
|
||||
#: src/ui-goodies.lisp:165
|
||||
msgid "Jump to message: "
|
||||
msgstr "Vai al messaggio: "
|
||||
|
||||
#: src/ui-goodies.lisp:182 src/ui-goodies.lisp:206 src/ui-goodies.lisp:306
|
||||
msgid "Search key: "
|
||||
msgstr "Criterio di ricerca: "
|
||||
|
||||
#: src/ui-goodies.lisp:263
|
||||
msgid "Subscribe to: "
|
||||
msgstr "Abbonati a: "
|
||||
|
||||
#: src/ui-goodies.lisp:279
|
||||
msgid "Unsubscribe to: "
|
||||
msgstr "Rimuovi la sottoscrizione a: "
|
||||
|
||||
#: src/ui-goodies.lisp:319
|
||||
msgid "Focus changed"
|
||||
msgstr "Il focus e' cambiato"
|
||||
|
||||
#: src/ui-goodies.lisp:329
|
||||
msgid "Focus passed on threads window"
|
||||
msgstr "Il focus è passato alla finestra delle discussioni."
|
||||
|
||||
#: src/ui-goodies.lisp:339
|
||||
msgid "Focus passed on message window"
|
||||
msgstr "Il focus è passato alla finestra del messaggio."
|
||||
|
||||
#: src/ui-goodies.lisp:350
|
||||
msgid "Focus passed on send message window"
|
||||
msgstr "Il focus è passato alla finestra di spedizione dei messaggi."
|
||||
|
||||
#: src/ui-goodies.lisp:361
|
||||
msgid "Focus passed on follow requests window"
|
||||
msgstr "Il focus è passato alla finestra delle richieste di seguirti."
|
||||
|
||||
#: src/ui-goodies.lisp:372
|
||||
msgid "Focus passed on tags window"
|
||||
msgstr "Il focus è passato alla finestra delle sottoscrizioni."
|
||||
|
||||
#: src/ui-goodies.lisp:382
|
||||
msgid "Focus passed on conversation window"
|
||||
msgstr "Il focus è passato alla finestra delle conversazioni."
|
||||
|
||||
#: src/ui-goodies.lisp:393
|
||||
msgid "Focus passed on attach window"
|
||||
msgstr "Il focus è passato alla finestra degli allegati."
|
||||
|
||||
#: src/ui-goodies.lisp:413
|
||||
#, lisp-format
|
||||
msgid "Saving messages in ~s"
|
||||
msgstr "Salvo i messaggi in ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:414
|
||||
#, lisp-format
|
||||
msgid "Saved message in ~s"
|
||||
msgstr "Salvati i messaggi in ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:417 src/ui-goodies.lisp:435
|
||||
msgid "No folder specified."
|
||||
msgstr "Nessuna cartella indicata."
|
||||
|
||||
#: src/ui-goodies.lisp:419
|
||||
msgid "Move to folder: "
|
||||
msgstr "Sposta nella cartella: "
|
||||
|
||||
#: src/ui-goodies.lisp:433
|
||||
#, lisp-format
|
||||
msgid "Folder ~s does not exists."
|
||||
msgstr "La cartella ~s non esiste."
|
||||
|
||||
#: src/ui-goodies.lisp:437
|
||||
msgid "Change folder: "
|
||||
msgstr "Spostati nella cartella: "
|
||||
|
||||
#: src/ui-goodies.lisp:447
|
||||
msgid "No timeline specified."
|
||||
msgstr "Nessuna timeline indicata."
|
||||
|
||||
#: src/ui-goodies.lisp:450
|
||||
msgid "Change timeline: "
|
||||
msgstr "Spostati nella timeline: "
|
||||
|
||||
#: src/ui-goodies.lisp:480
|
||||
msgid "Downloading messages."
|
||||
msgstr "Scarico i messaggi."
|
||||
|
||||
#: src/ui-goodies.lisp:481 src/ui-goodies.lisp:502
|
||||
msgid "Messages downloaded."
|
||||
msgstr "Messaggi scaricati"
|
||||
|
||||
#: src/ui-goodies.lisp:501
|
||||
msgid "Downloading tags messages."
|
||||
msgstr "Scarico i messaggi dell'etichetta."
|
||||
|
||||
#: src/ui-goodies.lisp:514
|
||||
msgid "Favorite this message?"
|
||||
msgstr "Conservare tra i favoriti questo messaggio?"
|
||||
|
||||
#: src/ui-goodies.lisp:523
|
||||
msgid "Favouring message."
|
||||
msgstr "Conservo il messaggio tra i favoriti."
|
||||
|
||||
#: src/ui-goodies.lisp:524
|
||||
msgid "Favoured message."
|
||||
msgstr "Messaggio conservato tra i favoriti."
|
||||
|
||||
#: src/ui-goodies.lisp:529
|
||||
msgid "Remove this message from your favourites?"
|
||||
msgstr "Rimuovere dai preferiti questo messaggio?"
|
||||
|
||||
#: src/ui-goodies.lisp:538
|
||||
msgid "Unfavouring message."
|
||||
msgstr "Rimuovo messaggio dai favoriti."
|
||||
|
||||
#: src/ui-goodies.lisp:539
|
||||
msgid "Unfavoured message."
|
||||
msgstr "Rimuosso messaggio dai favoriti."
|
||||
|
||||
#: src/ui-goodies.lisp:544
|
||||
msgid "Boost this message?"
|
||||
msgstr "Rilancia questo messaggio?"
|
||||
|
||||
#: src/ui-goodies.lisp:553
|
||||
msgid "Boosting message."
|
||||
msgstr "Rilancio il messaggio."
|
||||
|
||||
#: src/ui-goodies.lisp:554
|
||||
msgid "Boosted message."
|
||||
msgstr "Messaggio rilanciato."
|
||||
|
||||
#: src/ui-goodies.lisp:559
|
||||
msgid "Unboost this message?"
|
||||
msgstr "Ritira il rilancio del messaggio?"
|
||||
|
||||
#: src/ui-goodies.lisp:568
|
||||
msgid "Uboosting message."
|
||||
msgstr "Ritiro il rilancio del messaggio."
|
||||
|
||||
#: src/ui-goodies.lisp:569
|
||||
msgid "Unboosted message."
|
||||
msgstr "Ritirato il rilancio del messaggio."
|
||||
|
||||
#: src/ui-goodies.lisp:577
|
||||
#, lisp-format
|
||||
msgid "Ignore ~s?"
|
||||
msgstr "Ignorare ~s?"
|
||||
|
||||
#: src/ui-goodies.lisp:580
|
||||
#, lisp-format
|
||||
msgid "Ignoring ~s"
|
||||
msgstr "Ignoro ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:581
|
||||
#, lisp-format
|
||||
msgid "User ~s ignored"
|
||||
msgstr "Utente ~s ignorato"
|
||||
|
||||
#: src/ui-goodies.lisp:591
|
||||
msgid "No username specified."
|
||||
msgstr "Nessun nome utente indicato."
|
||||
|
||||
#: src/ui-goodies.lisp:593
|
||||
msgid "Unignore username: "
|
||||
msgstr "Riprendere a leggere i messaggi di:"
|
||||
|
||||
#: src/ui-goodies.lisp:621
|
||||
#, lisp-format
|
||||
msgid "File ~s does not exists."
|
||||
msgstr "Il file ~s non esiste."
|
||||
|
||||
#: src/ui-goodies.lisp:624
|
||||
msgid "Add attachment: "
|
||||
msgstr "Aggiungi allegato: "
|
||||
|
||||
#: src/ui-goodies.lisp:634
|
||||
msgid "New subject: "
|
||||
msgstr "Nuovo oggetto del messaggio: "
|
||||
|
||||
#: src/ui-goodies.lisp:643
|
||||
msgid "New visibility: "
|
||||
msgstr "Nuovo livello di visibilità:"
|
||||
|
||||
#: src/ui-goodies.lisp:690
|
||||
#, lisp-format
|
||||
msgid "Your message is ~a character too long."
|
||||
msgid_plural "Your message is ~a characters too long."
|
||||
msgstr[0] "Il tuo messaggio e più lungo del limite ammesso di ~a carattere."
|
||||
msgstr[1] "Il tuo messaggio e più lungo del limite ammesso di ~a caratteri."
|
||||
|
||||
#: src/ui-goodies.lisp:714
|
||||
msgid "Add subject: "
|
||||
msgstr "Oggetto del messaggio: "
|
||||
|
||||
#: src/ui-goodies.lisp:771
|
||||
#, lisp-format
|
||||
msgid "The maximum allowed number of media is ~a."
|
||||
msgstr "Il numero massimo di file da allegare è ~a."
|
||||
|
||||
#: src/ui-goodies.lisp:774
|
||||
msgid "Sending message"
|
||||
msgstr "Spedisco il messaggio"
|
||||
|
||||
#: src/ui-goodies.lisp:823
|
||||
msgid "Follow: "
|
||||
msgstr "Segui: "
|
||||
|
||||
#: src/ui-goodies.lisp:826
|
||||
#, lisp-format
|
||||
msgid "Following ~a"
|
||||
msgstr "Segui ~a"
|
||||
|
||||
#: src/ui-goodies.lisp:827
|
||||
#, lisp-format
|
||||
msgid "Followed ~a"
|
||||
msgstr "Adesso segui ~a "
|
||||
|
||||
#: src/ui-goodies.lisp:831
|
||||
msgid "Unfollow: "
|
||||
msgstr "Abbandona: "
|
||||
|
||||
#: src/ui-goodies.lisp:834
|
||||
#, lisp-format
|
||||
msgid "Unfollowing ~a"
|
||||
msgstr "Abbandona ~a"
|
||||
|
||||
#: src/ui-goodies.lisp:835
|
||||
#, lisp-format
|
||||
msgid "Unfollowed ~a"
|
||||
msgstr "Hai abbandonato ~a"
|
||||
|
||||
#: src/ui-goodies.lisp:864
|
||||
msgid "Confirm operation?"
|
||||
msgstr "Confermi l'operazione?"
|
||||
|
||||
#: src/ui-goodies.lisp:902
|
||||
msgid "Updating conversations."
|
||||
msgstr "Aggiorno le conversazioni"
|
||||
|
||||
#: src/ui-goodies.lisp:903
|
||||
msgid "Conversations updated."
|
||||
msgstr "Conversazioni aggiornate"
|
||||
|
||||
#: src/ui-goodies.lisp:913
|
||||
msgid "Open conversation: "
|
||||
msgstr "Apri una conversazione: "
|
||||
|
||||
#: src/ui-goodies.lisp:948
|
||||
msgid "Old name: "
|
||||
msgstr "Nome precedente: "
|
||||
|
||||
#: src/ui-goodies.lisp:960
|
||||
#, lisp-format
|
||||
msgid "A conversation with name ~a already exists."
|
||||
msgstr "Una conversazione con nome ~a esiste già."
|
||||
|
||||
#: src/ui-goodies.lisp:964
|
||||
msgid "New name: "
|
||||
msgstr "Nuovo nome: "
|
||||
|
||||
#: src/ui-goodies.lisp:980
|
||||
#, lisp-format
|
||||
msgid "Ignore conversation ~s? [y/N] "
|
||||
msgstr "Ignorare la conversazione ~s? [s/N] "
|
||||
|
||||
#: src/ui-goodies.lisp:996
|
||||
#, lisp-format
|
||||
msgid "Delete conversation ~s? [y/N] "
|
||||
msgstr "Ignorare la conversazione ~s? [s/N] "
|
||||
|
||||
#: src/ui-goodies.lisp:1015
|
||||
#, lisp-format
|
||||
msgid "Comment too long by ~a character"
|
||||
msgid_plural "Comment too long by ~a characters"
|
||||
msgstr[0] "Il commento è troppo lungo di ~a caratteri"
|
||||
msgstr[1] "Il commento è troppo lungo di ~a caratteri"
|
||||
|
||||
#: src/ui-goodies.lisp:1022
|
||||
#, lisp-format
|
||||
msgid "Reporting user: ~s"
|
||||
msgstr "Segnalo l'utente ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:1023
|
||||
msgid "Report trasmitted."
|
||||
msgstr "Segnalazione trasmessa."
|
||||
|
||||
#: src/ui-goodies.lisp:1026
|
||||
msgid "Comment on reports: "
|
||||
msgstr "Commento sulla segnalazione: "
|
||||
|
||||
#: src/ui-goodies.lisp:1042 src/ui-goodies.lisp:1075 src/ui-goodies.lisp:1093
|
||||
#, lisp-format
|
||||
msgid "User ~s does not exists in database"
|
||||
msgstr "L'utente ~s non esiste nel database"
|
||||
|
||||
#: src/ui-goodies.lisp:1045 src/ui-goodies.lisp:1078 src/ui-goodies.lisp:1096
|
||||
msgid "Username: "
|
||||
msgstr "Nome utente: "
|
||||
|
||||
#: src/ui-goodies.lisp:1055
|
||||
#, lisp-format
|
||||
msgid "Added crypto key for user ~s"
|
||||
msgstr "Aggiunta chiave crittografica per l'utente ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:1058
|
||||
msgid "Key: "
|
||||
msgstr "Chiave: "
|
||||
|
||||
#: src/ui-goodies.lisp:1072
|
||||
#, lisp-format
|
||||
msgid "Generated key for user ~s"
|
||||
msgstr "Generata chiave crittografica per l'utente ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:1073
|
||||
#, lisp-format
|
||||
msgid "user ~s key ~s"
|
||||
msgstr "utente ~s chiave ~s"
|
||||
|
||||
#: src/ui-goodies.lisp:1089
|
||||
#, lisp-format
|
||||
msgid "Added key for user ~s: ~a"
|
||||
msgstr "Aggiunta chiave crittografica per l'utente ~s: ~a"
|
||||
|
||||
#: src/windows.lisp:513 src/windows.lisp:569
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#~ msgid "The list is too big to be displayed entirely"
|
||||
#~ msgstr "La lista è troppo lunga per essere mostrata interamente"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Please visit the address below and then paste here the autorization code "
|
||||
#~ "you got from that website."
|
||||
#~ msgstr ""
|
||||
#~ "Per favore, visita l'indirizzo indicato più sotto e poi inserisci qui il "
|
||||
#~ "codice di autorizzazione che il sito ti ha fornito."
|
||||
|
||||
#~ msgid "Authorization code:"
|
||||
#~ msgstr "Codice di autorizzazione:"
|
||||
|
||||
#~ msgid "timeline unknown"
|
||||
#~ msgstr "timeline sconosciuta"
|
||||
|
||||
#~ msgid "Timeline ~s does not exists in folder ~s."
|
||||
#~ msgstr "La timeline ~s non esiste nella cartella ~s."
|
||||
|
||||
#~ msgid "Confirm accepting and rejecting the following requests?"
|
||||
#~ msgstr "Confermi l'operazione?"
|
||||
|
||||
#~ msgid "Fetching messages."
|
||||
#~ msgstr "Recupero i messaggi."
|
||||
|
||||
#~ msgid "~%---- attachments ----~%"
|
||||
#~ msgstr "~%---- allegati ----~%"
|
||||
|
||||
#~ msgid "No message"
|
||||
#~ msgstr "Nessun messaggio"
|
||||
|
||||
#~ msgid "Save link"
|
||||
#~ msgstr "Salva link"
|
||||
|
||||
#~ msgid "Open link"
|
||||
#~ msgstr "Apri il link"
|
||||
|
||||
#~ msgid "Unable to complete ~a"
|
||||
#~ msgstr "Non posso completare ~a"
|
|
@ -0,0 +1,6 @@
|
|||
s/"\([^"]*\)"/“\1”/g
|
||||
s/`\([^`']*\)'/‘\1’/g
|
||||
s/ '\([^`']*\)' / ‘\1’ /g
|
||||
s/ '\([^`']*\)'$/ ‘\1’/g
|
||||
s/^'\([^`']*\)' /‘\1’ /g
|
||||
s/“”/""/g
|
|
@ -0,0 +1,19 @@
|
|||
# Sed script that remove the POT-Creation-Date line in the header entry
|
||||
# from a POT file.
|
||||
#
|
||||
# The distinction between the first and the following occurrences of the
|
||||
# pattern is achieved by looking at the hold space.
|
||||
/^"POT-Creation-Date: .*"$/{
|
||||
x
|
||||
# Test if the hold space is empty.
|
||||
s/P/P/
|
||||
ta
|
||||
# Yes it was empty. First occurrence. Remove the line.
|
||||
g
|
||||
d
|
||||
bb
|
||||
:a
|
||||
# The hold space was nonempty. Following occurrences. Do nothing.
|
||||
x
|
||||
:b
|
||||
}
|
|
@ -0,0 +1,748 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR cage
|
||||
# This file is distributed under the same license as the tinmop package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: tinmop 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: https://notabug.org/cage/tinmop/\n"
|
||||
"POT-Creation-Date: 2020-05-08 15:15+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
|
||||
#: src/api-client.lisp:118
|
||||
msgid "Please visit the address below."
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:176
|
||||
#, lisp-format
|
||||
msgid ""
|
||||
"Credential invalid. Try to remove ~a and restart the software to "
|
||||
"authenticate again."
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:187
|
||||
msgid "Save address"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:188
|
||||
msgid "Open address"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:197
|
||||
msgid "This client has been authorized"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:201
|
||||
msgid "Got a generic error when registering client"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:203
|
||||
#, lisp-format
|
||||
msgid "File ~a saved"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:205
|
||||
msgid "Please enter below the file where to save the address"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:225
|
||||
msgid ""
|
||||
"Error: was not able to create server socket to listen for authorization code"
|
||||
msgstr ""
|
||||
|
||||
#: src/api-client.lisp:631
|
||||
#, lisp-format
|
||||
msgid "Initializing empty credentials file in ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:21
|
||||
#, lisp-format
|
||||
msgid "~a version ~a~%"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:26
|
||||
msgid "Print help and exit"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:30
|
||||
msgid "Print program information and exit"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:34
|
||||
msgid "Starting folder"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:37
|
||||
msgid "FOLDER-NAME"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:40
|
||||
msgid "Starting timeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:42
|
||||
msgid "TIMELINE-NAME"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:46
|
||||
msgid "Update timeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:50
|
||||
msgid "Check follows requests"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:54
|
||||
msgid "Execute script"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-line.lisp:57
|
||||
msgid "SCRIPT-FILE"
|
||||
msgstr ""
|
||||
|
||||
#: src/command-window.lisp:298
|
||||
#, lisp-format
|
||||
msgid "Error: command ~a not found"
|
||||
msgstr ""
|
||||
|
||||
#: src/conditions.lisp:67 src/conditions.lisp:71 src/db.lisp:2113
|
||||
#: src/message-rendering-utils.lisp:132 src/message-rendering-utils.lisp:166
|
||||
#: src/message-rendering-utils.lisp:171
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
#: src/conversations-window.lisp:152
|
||||
msgid "Conversations"
|
||||
msgstr ""
|
||||
|
||||
#: src/db.lisp:149
|
||||
msgid "federated"
|
||||
msgstr ""
|
||||
|
||||
#: src/db.lisp:151
|
||||
msgid "local"
|
||||
msgstr ""
|
||||
|
||||
#: src/db.lisp:153
|
||||
msgid "direct"
|
||||
msgstr ""
|
||||
|
||||
#: src/db.lisp:155
|
||||
msgid "home"
|
||||
msgstr ""
|
||||
|
||||
#: src/follow-requests.lisp:68
|
||||
msgid ""
|
||||
"Please evaluate the following requests, only items shown below will be "
|
||||
"accepted, deleted ones will be rejected:"
|
||||
msgstr ""
|
||||
|
||||
#: src/html-utils.lisp:104
|
||||
msgid "No address found"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:396
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:398
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:400
|
||||
msgid "Page-up"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:402
|
||||
msgid "Page-down"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:458
|
||||
msgid "No documentation available, you can help! :-)"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:490
|
||||
msgid "Focused window keys"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:491
|
||||
msgid "Global keys"
|
||||
msgstr ""
|
||||
|
||||
#: src/keybindings.lisp:507
|
||||
msgid "Quick help"
|
||||
msgstr ""
|
||||
|
||||
#: src/line-oriented-window.lisp:269 src/ui-goodies.lisp:74
|
||||
#: src/ui-goodies.lisp:88
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:69
|
||||
msgid "This message will *not* be crypted"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:77
|
||||
#, lisp-format
|
||||
msgid "No key to crypt message for ~s found"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:114
|
||||
#, lisp-format
|
||||
msgid "Unable to find the crypto key for user ~s."
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:129
|
||||
msgid "invalid type"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "image"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "gifv"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "video"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:132
|
||||
msgid "audio"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:138
|
||||
#, lisp-format
|
||||
msgid "description: ~a~%"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:141
|
||||
#, lisp-format
|
||||
msgid "size: ~aX~a pixels~%"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:148
|
||||
#, lisp-format
|
||||
msgid "duration: ~a~%"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:159 src/sending-message.lisp:132
|
||||
msgid "Attachments"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:168
|
||||
#, lisp-format
|
||||
msgid "type: ~a~%metadata~%~a~%address: ~a~2%"
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:210
|
||||
msgid "From: "
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:211
|
||||
msgid "Boosted: "
|
||||
msgstr ""
|
||||
|
||||
#: src/message-rendering-utils.lisp:215
|
||||
msgid "Date: "
|
||||
msgstr ""
|
||||
|
||||
#: src/message-window.lisp:218
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
#: src/modeline-window.lisp:29
|
||||
msgid "modeline"
|
||||
msgstr ""
|
||||
|
||||
#: src/modules.lisp:33
|
||||
#, lisp-format
|
||||
msgid ""
|
||||
"Unrecoverable error: file ~a not found in any of the directory ~a ~a ~a ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/notify-window.lisp:63
|
||||
#, lisp-format
|
||||
msgid "~a pending"
|
||||
msgid_plural "~a pending"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/os-utils.lisp:55
|
||||
msgid ""
|
||||
"No editor found, please configure the 'editor' directive in your "
|
||||
"configuration file"
|
||||
msgstr ""
|
||||
|
||||
#: src/program-events.lisp:41
|
||||
#, lisp-format
|
||||
msgid "Error: ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/program-events.lisp:413
|
||||
msgid "No message selected!"
|
||||
msgstr ""
|
||||
|
||||
#: src/program-events.lisp:563
|
||||
msgid "Message sent."
|
||||
msgstr ""
|
||||
|
||||
#: src/program-events.lisp:618
|
||||
#, lisp-format
|
||||
msgid "Downloaded new messages for tag ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/resources-utils.lisp:55
|
||||
#, lisp-format
|
||||
msgid "Unrecoverable error: cannot find ~s in either ~s or ~s."
|
||||
msgstr ""
|
||||
|
||||
#: src/sending-message.lisp:114
|
||||
msgid "none"
|
||||
msgstr ""
|
||||
|
||||
#: src/sending-message.lisp:115
|
||||
msgid "Reply to: "
|
||||
msgstr ""
|
||||
|
||||
#: src/sending-message.lisp:116
|
||||
msgid "Subject:"
|
||||
msgstr ""
|
||||
|
||||
#: src/sending-message.lisp:117
|
||||
msgid "Visibility:"
|
||||
msgstr ""
|
||||
|
||||
#: src/software-configuration.lisp:415
|
||||
msgid "This message was crypted."
|
||||
msgstr ""
|
||||
|
||||
#: src/suggestions-window.lisp:33
|
||||
#, lisp-format
|
||||
msgid "Page ~a of ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/tags-window.lisp:140
|
||||
msgid "Subscribed tags"
|
||||
msgstr ""
|
||||
|
||||
#: src/text-utils.lisp:456
|
||||
#, lisp-format
|
||||
msgid "Can not fit column of width of ~a in a box of width ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/text-utils.lisp:570
|
||||
#, lisp-format
|
||||
msgid "Unrecoverable error: ~a can not be fitted in a box of width ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:134
|
||||
msgid "no timeline selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:142
|
||||
msgid "no folder selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:397
|
||||
msgid "Missing subject"
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:729
|
||||
#, lisp-format
|
||||
msgid "No message with index ~a exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:838 src/thread-window.lisp:872
|
||||
#, lisp-format
|
||||
msgid "No next message that contains ~s exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:844 src/thread-window.lisp:878
|
||||
#, lisp-format
|
||||
msgid "No previous message that contains ~s exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:894
|
||||
msgid "No others unread messages exist."
|
||||
msgstr ""
|
||||
|
||||
#: src/thread-window.lisp:905
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: src/tui-utils.lisp:104
|
||||
#, lisp-format
|
||||
msgid "Unknown event ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:21
|
||||
msgid "y"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:43
|
||||
#, lisp-format
|
||||
msgid "Delete ~a message? [y/N] "
|
||||
msgid_plural "Delete ~a messages? [y/N] "
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/ui-goodies.lisp:58 src/ui-goodies.lisp:67
|
||||
msgid "Task completed"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:81 src/ui-goodies.lisp:95
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:117
|
||||
#, lisp-format
|
||||
msgid "File \"~a\" exists, overwrite?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:119 src/ui-goodies.lisp:124 src/windows.lisp:507
|
||||
#: src/windows.lisp:563
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:129
|
||||
#, lisp-format
|
||||
msgid "Request failed: error code ~d message \"~a\""
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:165
|
||||
msgid "Jump to message: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:182 src/ui-goodies.lisp:206 src/ui-goodies.lisp:306
|
||||
msgid "Search key: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:263
|
||||
msgid "Subscribe to: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:279
|
||||
msgid "Unsubscribe to: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:319
|
||||
msgid "Focus changed"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:329
|
||||
msgid "Focus passed on threads window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:339
|
||||
msgid "Focus passed on message window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:350
|
||||
msgid "Focus passed on send message window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:361
|
||||
msgid "Focus passed on follow requests window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:372
|
||||
msgid "Focus passed on tags window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:382
|
||||
msgid "Focus passed on conversation window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:393
|
||||
msgid "Focus passed on attach window"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:413
|
||||
#, lisp-format
|
||||
msgid "Saving messages in ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:414
|
||||
#, lisp-format
|
||||
msgid "Saved message in ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:417 src/ui-goodies.lisp:435
|
||||
msgid "No folder specified."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:419
|
||||
msgid "Move to folder: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:433
|
||||
#, lisp-format
|
||||
msgid "Folder ~s does not exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:437
|
||||
msgid "Change folder: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:447
|
||||
msgid "No timeline specified."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:450
|
||||
msgid "Change timeline: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:480
|
||||
msgid "Downloading messages."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:481 src/ui-goodies.lisp:502
|
||||
msgid "Messages downloaded."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:501
|
||||
msgid "Downloading tags messages."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:514
|
||||
msgid "Favorite this message?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:523
|
||||
msgid "Favouring message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:524
|
||||
msgid "Favoured message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:529
|
||||
msgid "Remove this message from your favourites?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:538
|
||||
msgid "Unfavouring message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:539
|
||||
msgid "Unfavoured message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:544
|
||||
msgid "Boost this message?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:553
|
||||
msgid "Boosting message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:554
|
||||
msgid "Boosted message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:559
|
||||
msgid "Unboost this message?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:568
|
||||
msgid "Uboosting message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:569
|
||||
msgid "Unboosted message."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:577
|
||||
#, lisp-format
|
||||
msgid "Ignore ~s?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:580
|
||||
#, lisp-format
|
||||
msgid "Ignoring ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:581
|
||||
#, lisp-format
|
||||
msgid "User ~s ignored"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:591
|
||||
msgid "No username specified."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:593
|
||||
msgid "Unignore username: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:621
|
||||
#, lisp-format
|
||||
msgid "File ~s does not exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:624
|
||||
msgid "Add attachment: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:634
|
||||
msgid "New subject: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:643
|
||||
msgid "New visibility: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:690
|
||||
#, lisp-format
|
||||
msgid "Your message is ~a character too long."
|
||||
msgid_plural "Your message is ~a characters too long."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/ui-goodies.lisp:714
|
||||
msgid "Add subject: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:771
|
||||
#, lisp-format
|
||||
msgid "The maximum allowed number of media is ~a."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:774
|
||||
msgid "Sending message"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:823
|
||||
msgid "Follow: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:826
|
||||
#, lisp-format
|
||||
msgid "Following ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:827
|
||||
#, lisp-format
|
||||
msgid "Followed ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:831
|
||||
msgid "Unfollow: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:834
|
||||
#, lisp-format
|
||||
msgid "Unfollowing ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:835
|
||||
#, lisp-format
|
||||
msgid "Unfollowed ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:864
|
||||
msgid "Confirm operation?"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:902
|
||||
msgid "Updating conversations."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:903
|
||||
msgid "Conversations updated."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:913
|
||||
msgid "Open conversation: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:948
|
||||
msgid "Old name: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:960
|
||||
#, lisp-format
|
||||
msgid "A conversation with name ~a already exists."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:964
|
||||
msgid "New name: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:980
|
||||
#, lisp-format
|
||||
msgid "Ignore conversation ~s? [y/N] "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:996
|
||||
#, lisp-format
|
||||
msgid "Delete conversation ~s? [y/N] "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1015
|
||||
#, lisp-format
|
||||
msgid "Comment too long by ~a character"
|
||||
msgid_plural "Comment too long by ~a characters"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: src/ui-goodies.lisp:1022
|
||||
#, lisp-format
|
||||
msgid "Reporting user: ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1023
|
||||
msgid "Report trasmitted."
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1026
|
||||
msgid "Comment on reports: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1042 src/ui-goodies.lisp:1075 src/ui-goodies.lisp:1093
|
||||
#, lisp-format
|
||||
msgid "User ~s does not exists in database"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1045 src/ui-goodies.lisp:1078 src/ui-goodies.lisp:1096
|
||||
msgid "Username: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1055
|
||||
#, lisp-format
|
||||
msgid "Added crypto key for user ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1058
|
||||
msgid "Key: "
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1072
|
||||
#, lisp-format
|
||||
msgid "Generated key for user ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1073
|
||||
#, lisp-format
|
||||
msgid "user ~s key ~s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui-goodies.lisp:1089
|
||||
#, lisp-format
|
||||
msgid "Added key for user ~s: ~a"
|
||||
msgstr ""
|
||||
|
||||
#: src/windows.lisp:513 src/windows.lisp:569
|
||||
msgid "OK"
|
||||
msgstr ""
|
|
@ -0,0 +1,135 @@
|
|||
#! @BASH@
|
||||
|
||||
# tinmop: an humble mastodon client
|
||||
# Copyright (C) 2020 cage
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program.
|
||||
# If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
QUICKLISP_INSTALL_DIR=$HOME/quicklisp/
|
||||
|
||||
QUICKLISP_URL="https://beta.quicklisp.org/quicklisp.lisp"
|
||||
QUICKLISP_SIG_URL="https://beta.quicklisp.org/quicklisp.lisp.asc"
|
||||
QUICKLISP_KEY_URL="https://beta.quicklisp.org/release-key.txt"
|
||||
|
||||
QUICKLISP="quicklisp.lisp"
|
||||
QUICKLISP_SIG="quicklisp.lisp.asc"
|
||||
QUICKLISP_KEY="release-key"
|
||||
|
||||
QUICKLISP_SIGNATURE="D7A3 +489D +DEFE +32B7 +D0E7 +CC61 +3079 +65AB +028B +5FF7";
|
||||
|
||||
LISP_SOURCE_REGISTRY_DIR="$HOME/.config/common-lisp/"
|
||||
|
||||
LISP_SOURCE_REGISTRY_FILE="$LISP_SOURCE_REGISTRY_DIR/source-registry.conf"
|
||||
|
||||
BOLD_TEXT="\033[1m"
|
||||
|
||||
NORMAL_TEXT="\033[0m"
|
||||
|
||||
VERIFY_OK_RES=2
|
||||
|
||||
echo_bold () {
|
||||
echo -e "${BOLD_TEXT}${1}${NORMAL_TEXT}";
|
||||
}
|
||||
|
||||
check_quicklisp () {
|
||||
if [ -d "$QUICKLISP_INSTALL_DIR" ]; then
|
||||
echo 0;
|
||||
else
|
||||
echo 1;
|
||||
fi
|
||||
}
|
||||
|
||||
check_quicklisp_signature () {
|
||||
chk1_prog='BEGIN {res=0} /Good signature.*release@quicklisp.org/ {res++; print res}'
|
||||
chk2_prog="BEGIN {res=0} /${QUICKLISP_SIGNATURE}/ {res++; print res}"
|
||||
res1=$(LC_MESSAGES="C" @GPG@ --verify quicklisp.lisp.asc quicklisp.lisp 2> >(@GAWK@ -- "${chk1_prog}"))
|
||||
res2=$(@GPG@ --verify quicklisp.lisp.asc quicklisp.lisp 2> >(@GAWK@ -- "${chk2_prog}"))
|
||||
res=$(expr $res1 + $res2)
|
||||
echo -n "$res"
|
||||
}
|
||||
|
||||
install_quicklisp () {
|
||||
echo_bold "Downloading quicklisp...";
|
||||
@CURL@ "$QUICKLISP_URL" > $QUICKLISP;
|
||||
@CURL@ "$QUICKLISP_SIG_URL" > $QUICKLISP_SIG;
|
||||
@CURL@ "$QUICKLISP_KEY_URL" > $QUICKLISP_KEY;
|
||||
echo_bold "Importing gpg key.";
|
||||
@GPG@ --import $QUICKLISP_KEY;
|
||||
echo_bold "Verifing key";
|
||||
signature_verified=$(check_quicklisp_signature);
|
||||
if [ "$signature_verified" -ne $VERIFY_OK_RES ]; then
|
||||
echo_bold "Key verification failed!"
|
||||
exit 1;
|
||||
else
|
||||
echo_bold "Key sucessfully verified.";
|
||||
@LISP_COMPILER@ --load $QUICKLISP \
|
||||
--eval "(quicklisp-quickstart:install)" \
|
||||
--eval "(ql:add-to-init-file)" \
|
||||
--eval "(sb-ext:quit)";
|
||||
@MKDIR_P@ -p $LISP_SOURCE_REGISTRY_DIR;
|
||||
PAR_PWD="${PWD%/*}";
|
||||
echo "(:source-registry" > $LISP_SOURCE_REGISTRY_FILE;
|
||||
echo " (:tree \"$PAR_PWD\")" >> $LISP_SOURCE_REGISTRY_FILE;
|
||||
echo ":inherit-configuration)" >> $LISP_SOURCE_REGISTRY_FILE;
|
||||
echo "quicklisp installed";
|
||||
fi
|
||||
}
|
||||
|
||||
install_dependency () {
|
||||
# add here the lisp dependency
|
||||
# e.g.
|
||||
# @LISP_COMPILER@ \ <- note the '\'
|
||||
# --eval "(ql:quickload \"swank\")" \ <- note the '\'
|
||||
# --eval "(sb-ext:quit)"; <- keep this line as latest
|
||||
@LISP_COMPILER@ \
|
||||
--eval "(ql:quickload \"alexandria\")" \
|
||||
--eval "(ql:quickload \"cl-ppcre\")" \
|
||||
--eval "(ql:quickload \"tooter\")" \
|
||||
--eval "(ql:quickload \"croatoan\")" \
|
||||
--eval "(ql:quickload \"osicat\")" \
|
||||
--eval "(ql:quickload \"cl-spark\")" \
|
||||
--eval "(ql:quickload \"access\")" \
|
||||
--eval "(ql:quickload \"sqlite\")" \
|
||||
--eval "(ql:quickload \"sxql\")" \
|
||||
--eval "(ql:quickload \"sxql-composer\")" \
|
||||
--eval "(ql:quickload \"marshal\")" \
|
||||
--eval "(ql:quickload \"bordeaux-threads\")" \
|
||||
--eval "(ql:quickload \"log4cl\")" \
|
||||
--eval "(ql:quickload \"local-time\")" \
|
||||
--eval "(ql:quickload \"cl-colors2\")" \
|
||||
--eval "(ql:quickload \"cl-i18n\")" \
|
||||
--eval "(ql:quickload \"clunit2\")" \
|
||||
--eval "(ql:quickload \"esrap\")" \
|
||||
--eval "(ql:quickload \"ieee-floats\")" \
|
||||
--eval "(ql:quickload \"parse-number\")" \
|
||||
--eval "(ql:quickload \"cl-html5-parser\")" \
|
||||
--eval "(ql:quickload \"unix-opts\")" \
|
||||
--eval "(ql:quickload \"crypto-shortcuts\")" \
|
||||
--eval "(ql:quickload \"drakma\")" \
|
||||
--eval "(ql:quickload \"usocket\")" \
|
||||
--eval "(sb-ext:quit)";
|
||||
}
|
||||
|
||||
quicklisp_installed_p=$(check_quicklisp);
|
||||
|
||||
if [ $quicklisp_installed_p -eq 0 ]; then
|
||||
echo_bold "Quicklisp already installed; fetching libraries...";
|
||||
install_dependency;
|
||||
else
|
||||
install_quicklisp;
|
||||
install_dependency;
|
||||
fi
|
||||
|
||||
echo_bold "Finished."
|
|
@ -0,0 +1,636 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :api-client)
|
||||
|
||||
(defparameter *client* nil
|
||||
"The tooter client, used to access mastodon")
|
||||
|
||||
(defparameter *credentials* nil
|
||||
"An istance of 'credentials' used to holds the intormation needed to
|
||||
access a mastodon instance")
|
||||
|
||||
(defparameter *client-lock* (bt:make-recursive-lock)
|
||||
"The Lock for prevent race conditions when accessing the mastodon server")
|
||||
|
||||
(define-constant +credentials-filename+ "client" :test #'string=
|
||||
:documentation "The name of the file where
|
||||
credentials are stored")
|
||||
|
||||
(define-constant +protocol-scheme+ "https://" :test #'string=
|
||||
:documentation "The scheme of the protocol that the
|
||||
mastodon server understand.")
|
||||
|
||||
(defun make-base-slot ()
|
||||
"Makes the 'base' slots for credential, used in credentials
|
||||
initform."
|
||||
(strcat +protocol-scheme+ (swconf:config-server-name)))
|
||||
|
||||
(defclass credentials ()
|
||||
((base
|
||||
:initform (make-base-slot)
|
||||
:initarg :base
|
||||
:accessor base
|
||||
:documentation "The url of the mastodon instance to be accessed")
|
||||
(key
|
||||
:initform nil
|
||||
:initarg :key
|
||||
:accessor key
|
||||
:documentation "API key to access the instance")
|
||||
(secret
|
||||
:initform nil
|
||||
:initarg :secret
|
||||
:accessor secret
|
||||
:documentation "Secret key to access the instance")
|
||||
(access-token
|
||||
:initform nil
|
||||
:initarg :access-token
|
||||
:accessor access-token
|
||||
:documentation "Access token to access the instance"))
|
||||
(:documentation "Represents the credentials to access a mastodon
|
||||
instance"))
|
||||
|
||||
(defmethod marshal:class-persistant-slots ((object credentials))
|
||||
"Serializer for class `credentials', see `cl-marshal'"
|
||||
(append '(base key secret access-token)
|
||||
(call-next-method)))
|
||||
|
||||
(defun dump-credentials ()
|
||||
"Serialize `*credential*' to disk"
|
||||
(let ((resource-file (res:get-data-file +credentials-filename+)))
|
||||
(fs:dump-sequence-to-file (serialize *credentials*)
|
||||
resource-file)))
|
||||
|
||||
(defun forget-credentials ()
|
||||
"Remove credentials data file"
|
||||
(conditions:with-default-on-error (nil)
|
||||
(let ((resource-file (res:get-data-file +credentials-filename+)))
|
||||
(fs:delete-file-if-exists resource-file))))
|
||||
|
||||
(defun credentials-complete-p ()
|
||||
"Returns non nil if *credentials* contains all necessary data to log
|
||||
into the mastodon server"
|
||||
(when *credentials*
|
||||
(with-accessors ((base base)
|
||||
(key key)
|
||||
(secret secret)
|
||||
(access-token access-token)) *credentials*
|
||||
(and base
|
||||
key
|
||||
secret
|
||||
access-token))))
|
||||
|
||||
(defclass api-client (tooter:client) ()
|
||||
(:documentation "A mastodon client instance"))
|
||||
|
||||
(defun copy-credentials-to-client ()
|
||||
"Copy credential data from `*credentials*' into `*client*'"
|
||||
(setf (tooter:base *client*) (base *credentials*))
|
||||
(setf (tooter:key *client*) (key *credentials*))
|
||||
(setf (tooter:secret *client*) (secret *credentials*))
|
||||
(setf (tooter:access-token *client*) (access-token *credentials*))
|
||||
(setf (tooter:name *client*) +program-name+)
|
||||
*client*)
|
||||
|
||||
(defun copy-credentials-from-client ()
|
||||
"Copy credential data from `*client*' to `*credentials*'"
|
||||
(setf (base *credentials*) (tooter:base *client*))
|
||||
(setf (key *credentials*) (tooter:key *client*))
|
||||
(setf (secret *credentials*) (tooter:secret *client*))
|
||||
(setf (access-token *credentials*) (tooter:access-token *client*))
|
||||
*credentials*)
|
||||
|
||||
(defun authorize-dialog-message ()
|
||||
"Message printed when asking user to visit the autorization URL"
|
||||
(_ "Please visit the address below."))
|
||||
|
||||
(defun open-catch-code-socket ()
|
||||
"Returns a server socket on an arbitrary port, used to get the
|
||||
authorization code from mastondo instance with its `return-url'
|
||||
parameter, returns nil if a socket can not be opened."
|
||||
(loop for port from 20000 to 50000 do
|
||||
(let ((server (ignore-errors
|
||||
(usocket:socket-listen "127.0.0.1" port))))
|
||||
(when server
|
||||
(return-from open-catch-code-socket
|
||||
(values server port)))))
|
||||
nil)
|
||||
|
||||
(defun catch-auth-code (socket)
|
||||
"When an user authorize a client to access mastodon the server send
|
||||
an http request to an arbitrary URI chosen by the user.
|
||||
|
||||
This URI contains the authorization code neede to make the client
|
||||
trusted by the server. When tinmop starts the authorization process
|
||||
opens a server on the local machine and asks the server to redirect
|
||||
the user's browser to an URI (which contains the autorization code on
|
||||
a query string) that points to the local machine. The server on the
|
||||
local machine read the data from the browser'srequeste and get the
|
||||
authorization code.
|
||||
|
||||
This function perfom the latest of this actions."
|
||||
(unwind-protect
|
||||
(let* ((stream (usocket:socket-stream (usocket:socket-accept socket)))
|
||||
(line (read-line stream)))
|
||||
(multiple-value-bind (matched query-string)
|
||||
(cl-ppcre:scan-to-strings "code=\(.+\)" line)
|
||||
(if matched
|
||||
(first (cl-ppcre:split "(&)|(\\p{White_Space})" (first-elt query-string)))
|
||||
nil)))
|
||||
(usocket:socket-close socket)))
|
||||
|
||||
(defun make-redirect-url (port)
|
||||
"This is part of the url where the browser will be redirect after
|
||||
authorizations was performed with success."
|
||||
(strcat "http://127.0.0.1:" (to-s port)))
|
||||
|
||||
(defun make-default-client ()
|
||||
"Convenience funtion to build a `api-client' instance"
|
||||
(make-instance 'api-client
|
||||
:base (make-base-slot)
|
||||
:name +program-name+))
|
||||
|
||||
(defun authorize ()
|
||||
"Perform all the steps to authorize this application"
|
||||
(setf *client* (make-default-client))
|
||||
(if (credentials-complete-p)
|
||||
(progn
|
||||
(copy-credentials-to-client)
|
||||
(tooter:authorize *client*)
|
||||
(when (null (application-credentials))
|
||||
(ui:error-dialog-immediate
|
||||
(format nil
|
||||
(_ "Credential invalid. Try to remove ~a and restart the software to authenticate again.")
|
||||
(res:get-data-file +credentials-filename+)))))
|
||||
(multiple-value-bind (server-socket server-port)
|
||||
(open-catch-code-socket)
|
||||
(setf *client* (make-default-client))
|
||||
(setf (tooter:redirect *client*) (make-redirect-url server-port))
|
||||
#+debug-mode (misc:dbg "Client ~a not authorized" *client*)
|
||||
(multiple-value-bind (a url)
|
||||
(tooter:authorize *client*)
|
||||
(declare (ignore a))
|
||||
(let* ((dialog-msg (authorize-dialog-message))
|
||||
(save-item (_ "Save address"))
|
||||
(open-item (_ "Open address"))
|
||||
(choosen (ui:info-dialog-immediate (format nil "~a~%~a" dialog-msg url)
|
||||
:buttons (list save-item open-item))))
|
||||
(labels ((on-got-authorization-code (value)
|
||||
(handler-case
|
||||
(progn
|
||||
(tooter:authorize *client* value)
|
||||
(copy-credentials-from-client)
|
||||
(dump-credentials)
|
||||
(ui:notify (_ "This client has been authorized")))
|
||||
(tooter:request-failed (error)
|
||||
(ui:request-error-window error))
|
||||
(error ()
|
||||
(ui:error-dialog-immediate (_ "Got a generic error when registering client")))))
|
||||
(notify-file-saved (filepath)
|
||||
(ui:notify (format nil (_ "File ~a saved") filepath)))
|
||||
(save-credentials ()
|
||||
(let* ((message (_ "Please enter below the file where to save the address"))
|
||||
(filepath (ui:input-dialog-immediate message)))
|
||||
(cond
|
||||
((null filepath)
|
||||
(save-credentials))
|
||||
((fs:file-exists-p filepath)
|
||||
(if (ui:confirm-file-overwrite-dialog-immediate filepath)
|
||||
(progn
|
||||
(fs:dump-sequence-to-file url filepath)
|
||||
(notify-file-saved filepath))
|
||||
(save-credentials)))
|
||||
(t
|
||||
(fs:dump-sequence-to-file url filepath)
|
||||
(notify-file-saved filepath))))))
|
||||
(cond
|
||||
((string= choosen open-item)
|
||||
(os-utils:xdg-open url)
|
||||
(if server-socket
|
||||
(let ((authcode (catch-auth-code server-socket)))
|
||||
(on-got-authorization-code authcode))
|
||||
(ui:error-dialog-immediate (_ "Error: was not able to create server socket to listen for authorization code"))))
|
||||
((string= choosen save-item)
|
||||
(save-credentials)))))))))
|
||||
|
||||
(defun-w-lock favourite-status (status-id)
|
||||
*client-lock*
|
||||
"Favourite a status identified by `status-id'"
|
||||
(tooter:favourite *client*
|
||||
status-id))
|
||||
|
||||
(defun-w-lock unfavourite-status (status-id)
|
||||
*client-lock*
|
||||
"Unfavourite a status identified by `status-id'"
|
||||
(tooter:unfavourite *client*
|
||||
status-id))
|
||||
|
||||
(defun-w-lock reblog-status (status-id)
|
||||
*client-lock*
|
||||
"Reblog a status identified by `status-id'"
|
||||
(tooter:reblog *client*
|
||||
status-id))
|
||||
|
||||
(defun-w-lock unreblog-status (status-id)
|
||||
*client-lock*
|
||||
"Reblog a status identified by `status-id'"
|
||||
(tooter:unreblog *client*
|
||||
status-id))
|
||||
|
||||
(defun-w-lock get-timeline (kind &key local only-media max-id since-id min-id (limit 20))
|
||||
*client-lock*
|
||||
"Get messages (status) belonging to a timeline
|
||||
|
||||
- kind: one of
|
||||
db:+federated-timeline+
|
||||
db:+home-timeline+
|
||||
|
||||
- local: get status local to the instance the client is connected to
|
||||
|
||||
- only-media get status with attachments only
|
||||
- max-id get status until this id
|
||||
- min-id starts getting messages newer than this id
|
||||
- since-id cut the messages got starting drom this id
|
||||
- limit gets a maimum of messages up to this value."
|
||||
(tooter:timeline *client*
|
||||
kind
|
||||
:local local
|
||||
:only-media only-media
|
||||
:max-id max-id
|
||||
:since-id since-id
|
||||
:min-id min-id
|
||||
:limit limit))
|
||||
|
||||
(defun-w-lock update-timeline (timeline kind
|
||||
folder
|
||||
&key
|
||||
local only-media max-id since-id
|
||||
min-id
|
||||
(limit 20))
|
||||
*client-lock*
|
||||
"Update a timeline, this function will fetch new messages and generate and event to
|
||||
update the program reflectings the changes in the timeline (saves
|
||||
messages in the database etc.)"
|
||||
(let* ((timeline-statuses (get-timeline kind
|
||||
:local local
|
||||
:only-media only-media
|
||||
:max-id max-id
|
||||
:since-id since-id
|
||||
:min-id min-id
|
||||
:limit limit))
|
||||
(trees (flatten (loop for node-status in timeline-statuses collect
|
||||
(expand-status-tree node-status))))
|
||||
(event (make-instance 'program-events:update-timeline-event
|
||||
:payload trees
|
||||
:timeline-type timeline
|
||||
:folder folder
|
||||
:localp local
|
||||
:min-id min-id)))
|
||||
(program-events:push-event event)))
|
||||
|
||||
(defun-w-lock get-timeline-tag (tag &key min-id (limit 20))
|
||||
*client-lock*
|
||||
"Gets messages that contains tags identitgied by parameter `tag'"
|
||||
(tooter:timeline-tag *client*
|
||||
tag
|
||||
:local nil
|
||||
:only-media nil
|
||||
:max-id nil
|
||||
:since-id nil
|
||||
:min-id min-id
|
||||
:limit limit))
|
||||
|
||||
(defun-w-lock update-timeline-tag (tag folder &key min-id (limit 20))
|
||||
*client-lock*
|
||||
"Update a tag timeline, this function will fetch new messages (that
|
||||
contains tag `tag') and generate and event to update the program
|
||||
reflectings the changes in the timeline (saves messages in the
|
||||
database etc.)"
|
||||
(when tag
|
||||
(let* ((timeline-statuses (get-timeline-tag tag
|
||||
:min-id min-id
|
||||
:limit limit))
|
||||
(trees (flatten (loop for node-status in timeline-statuses collect
|
||||
(expand-status-tree node-status))))
|
||||
(update-timeline-event (make-instance 'program-events:update-timeline-event
|
||||
:payload trees
|
||||
:timeline-type db:+federated-timeline+
|
||||
:folder folder
|
||||
:localp nil
|
||||
:min-id min-id)))
|
||||
(program-events:push-event update-timeline-event))))
|
||||
|
||||
(defun tag-name (tag &key (return-empty-string-if-nil nil))
|
||||
"Returns a convevient tag name from `tooter:tag'.
|
||||
|
||||
if `return-empty-string-if-nil' is non nil an apty tag (nil) will
|
||||
become an emty string (\"\")
|
||||
"
|
||||
(let ((name (tooter:name tag)))
|
||||
(or name
|
||||
(if return-empty-string-if-nil
|
||||
""
|
||||
nil))))
|
||||
|
||||
(defun-w-lock update-subscribed-tags (all-tags &key min-id (limit 20))
|
||||
*client-lock*
|
||||
"Update all tage in the list `all-tags'"
|
||||
(loop for tag in all-tags do
|
||||
(let ((tag-folder (db:tag->folder-name tag)))
|
||||
(update-timeline-tag tag
|
||||
tag-folder
|
||||
:limit limit
|
||||
:min-id min-id))))
|
||||
|
||||
(defun-w-lock fetch-remote-status (status-id)
|
||||
*client-lock*
|
||||
"Fetch a single status identified by status-id and generate an event
|
||||
`fetch-remote-status-event' that, in turn will save the status to the
|
||||
database."
|
||||
(when-let* ((status (tooter:find-status *client* status-id))
|
||||
(event (make-instance 'program-events:fetch-remote-status-event
|
||||
:payload status)))
|
||||
(program-events:push-event event)))
|
||||
|
||||
(defun-w-lock get-remote-status (status-id)
|
||||
*client-lock*
|
||||
"Get a single status identifird bu status-id"
|
||||
(ignore-errors
|
||||
(tooter:find-status *client* status-id)))
|
||||
|
||||
(defun-w-lock get-status-context (status-id) *client-lock*
|
||||
"Get aparent and a child of a status (identified by status-id), if exists"
|
||||
(tooter:context *client* status-id))
|
||||
|
||||
(defun-w-lock send-status (content in-reply-to-id attachments subject visibility)
|
||||
*client-lock*
|
||||
"Send a status
|
||||
- content the actual text of the message
|
||||
- in-reply-to-id status-id of the message you are replying to (or nil
|
||||
if this message is not a reply
|
||||
- attachments a list of file path to attach or nil il no attachments
|
||||
to this message exists
|
||||
- subject the subkec of this message
|
||||
- visibility one of `swconf:*allowed-status-visibility*'"
|
||||
(tooter:make-status *client*
|
||||
content
|
||||
:in-reply-to in-reply-to-id
|
||||
:media (mapcar #'fs:namestring->pathname attachments)
|
||||
:spoiler-text subject
|
||||
:visibility visibility))
|
||||
|
||||
(defun-w-lock follow-user (user-id)
|
||||
*client-lock*
|
||||
"Follow user identified by user-id"
|
||||
(tooter:follow *client* user-id))
|
||||
|
||||
(defun-w-lock unfollow-user (user-id)
|
||||
*client-lock*
|
||||
"Unfollow user identified by user-id"
|
||||
(tooter:unfollow *client* user-id))
|
||||
|
||||
(defun-w-lock follow-requests ()
|
||||
*client-lock*
|
||||
"Gets the request tio follow the user of this client"
|
||||
(let ((requests (tooter:follow-requests *client*)))
|
||||
(values requests
|
||||
(mapcar #'tooter:account-name requests))))
|
||||
|
||||
(defun-w-lock accept-follow-request (user-id)
|
||||
*client-lock*
|
||||
"Accept a follow request from user identified by `user-id'"
|
||||
(when user-id
|
||||
(tooter:accept-request *client* user-id)))
|
||||
|
||||
(defun-w-lock reject-follow-request (user-id)
|
||||
*client-lock*
|
||||
"Reject a follow request from user identified by `user-id'"
|
||||
(when user-id
|
||||
(tooter:reject-request *client* user-id)))
|
||||
|
||||
(defclass conversation-tree ()
|
||||
((id
|
||||
:initform nil
|
||||
:initarg :id
|
||||
:accessor id
|
||||
:type string
|
||||
:documentation "The conversation ID")
|
||||
(status-tree
|
||||
:initform nil
|
||||
:initarg :status-tree
|
||||
:accessor status-tree
|
||||
:type list
|
||||
:documentation "A flat list of statuses that forms the conversation tree")
|
||||
(last-status
|
||||
:initform nil
|
||||
:initarg :last-status
|
||||
:accessor last-status
|
||||
:type tooter:status
|
||||
:documentation "The lastest status that forms this conversation")
|
||||
(root
|
||||
:initform nil
|
||||
:initarg :root
|
||||
:accessor root
|
||||
:type tooter:status
|
||||
:documentation "The first status that forms this conversation"))
|
||||
(:documentation "Represents a tree of message belonging to a conversation"))
|
||||
|
||||
(defmethod print-object ((object conversation-tree) stream)
|
||||
(print-unreadable-object (object stream :type t)
|
||||
(with-accessors ((id id)
|
||||
(status-tree status-tree)
|
||||
(root root)) object
|
||||
(format stream "id: ~a tree: ~a root ~a" id status-tree root))))
|
||||
|
||||
(defgeneric conversation-root-id (object))
|
||||
|
||||
(defmethod conversation-root-id ((object conversation-tree))
|
||||
"Status id of the root of a conversation tree"
|
||||
(tooter:id (root object)))
|
||||
|
||||
(defun-w-lock conversations (&key
|
||||
(min-id nil)
|
||||
(since-id nil)
|
||||
(max-id nil)
|
||||
(limit 20)
|
||||
(root-only nil))
|
||||
*client-lock*
|
||||
"Get trees of conversations
|
||||
- max-id get status until this id
|
||||
- min-id starts getting messages newer than this id
|
||||
- since-id cut the messages got starting drom this id
|
||||
- limit gets a maimum of messages up to this value
|
||||
- root-only if non nil do not return the whole trees just the root of each."
|
||||
(let ((conversations (tooter:conversations *client*
|
||||
:min-id min-id
|
||||
:since-id since-id
|
||||
:max-id max-id
|
||||
:limit limit)))
|
||||
(loop for conversation in conversations collect
|
||||
(let* ((conversation-id (tooter:id conversation))
|
||||
(last-status (tooter:last-status conversation))
|
||||
(conversation-tree (and (not root-only)
|
||||
(expand-status-tree last-status)))
|
||||
(sorted-tree (sort conversation-tree
|
||||
(lambda (a b)
|
||||
(string< (tooter:id a)
|
||||
(tooter:id b)))))
|
||||
(root-message (if (not root-only)
|
||||
(first sorted-tree)
|
||||
(climb-fetch-statuses last-status))))
|
||||
(make-instance 'conversation-tree
|
||||
:last-status last-status
|
||||
:id conversation-id
|
||||
:status-tree sorted-tree
|
||||
:root root-message)))))
|
||||
|
||||
(defun expand-conversations-tree (message-root-id)
|
||||
"fetch all the tree that stars from `message-root-id',
|
||||
i.e. `message-root-id' is root for said tree."
|
||||
(expand-status-tree message-root-id))
|
||||
|
||||
(defun-w-lock delete-conversation (conversation-id)
|
||||
*client-lock*
|
||||
"Delete a conversation identified by `conversation-id'"
|
||||
(tooter:delete-conversation *client* conversation-id))
|
||||
|
||||
(defun-w-lock make-report (account-id status-id comment forward)
|
||||
*client-lock*
|
||||
"Report an user (identified by `account-id') and a
|
||||
status (identified by `status-id') to and instance admin, if `forward'
|
||||
is non nil the report will be forwarded to the non local admin where
|
||||
the account belongs."
|
||||
(tooter:make-report *client*
|
||||
account-id
|
||||
:statuses (list status-id)
|
||||
:comment comment
|
||||
:forward forward))
|
||||
|
||||
(defun-w-lock get-activity ()
|
||||
*client-lock*
|
||||
"Get instance stats"
|
||||
(tooter:get-activity *client*))
|
||||
|
||||
(defun-w-lock application-credentials ()
|
||||
*client-lock*
|
||||
"Verify the credentials to log into the server with the instance,
|
||||
returns nil if the credentials are invalid"
|
||||
(tooter:verify-app-credentials *client*))
|
||||
|
||||
(defun-w-lock bookmarks (&key
|
||||
(min-id nil)
|
||||
(since-id nil)
|
||||
(max-id nil)
|
||||
(limit 20))
|
||||
*client-lock*
|
||||
"List Bookmarked statuses.
|
||||
- max-id get status until this id
|
||||
- min-id starts getting messages newer than this id
|
||||
- since-id cut the messages got starting drom this id
|
||||
- limit gets a maimum of messages up to this value."
|
||||
(tooter:bookmarks *client*
|
||||
:min-id min-id
|
||||
:since-id since-id
|
||||
:max-id max-id
|
||||
:limit limit))
|
||||
|
||||
(defun-w-lock bookmark (id)
|
||||
*client-lock*
|
||||
"Bookmark a status identified by `id'"
|
||||
(assert (stringp id))
|
||||
(tooter:bookmark *client* id))
|
||||
|
||||
(defun-w-lock unbookmark (id)
|
||||
*client-lock*
|
||||
"Unbokmark a status identified by `id'"
|
||||
(assert (stringp id))
|
||||
(tooter:unbookmark *client* id))
|
||||
|
||||
(defun-w-lock polls (id)
|
||||
*client-lock*
|
||||
"Get a poll identified by `id'"
|
||||
(assert (stringp id))
|
||||
(tooter:polls *client* id))
|
||||
|
||||
(defgeneric climb-fetch-statuses (object &optional branch))
|
||||
|
||||
(defmethod climb-fetch-statuses ((object tooter:status) &optional (branch ()))
|
||||
(climb-fetch-statuses (tooter:id object) branch))
|
||||
|
||||
(defmethod climb-fetch-statuses ((object string) &optional (branch ()))
|
||||
"Starting from 'object' (a toot's ID) climbs messages tree, fetch
|
||||
parent status from the internet if needed until the message root is
|
||||
found, return two values the root of the tree and the statuses climbed
|
||||
while reaching the root (a branch, or portion of a branch if
|
||||
node-status-id is not a leaf)."
|
||||
(flet ((reply-id (status)
|
||||
(tooter:in-reply-to-id status)))
|
||||
(when-let ((status (get-remote-status object)))
|
||||
(if (null (reply-id status)) ; the root
|
||||
(values status (push status branch))
|
||||
(progn
|
||||
(climb-fetch-statuses (reply-id status)
|
||||
(push status branch)))))))
|
||||
|
||||
(defun make-id= (&optional (test #'string=))
|
||||
"Returns a comparator function that checks for id equality"
|
||||
(lambda (a b) (funcall test (tooter:id a) (tooter:id b))))
|
||||
|
||||
(defgeneric expand-status-tree (object))
|
||||
|
||||
(defmethod expand-status-tree ((object tooter:status))
|
||||
(expand-status-tree (tooter:id object)))
|
||||
|
||||
(defmethod expand-status-tree ((object string))
|
||||
"Given a status id returns the complete mesaages tree this status belong."
|
||||
(multiple-value-bind (root fetched-branch)
|
||||
(climb-fetch-statuses object)
|
||||
(let ((res (copy-list fetched-branch)))
|
||||
(labels ((descend (node-id)
|
||||
(when node-id
|
||||
(let* ((context (get-status-context node-id))
|
||||
(children (tooter:descendants context)))
|
||||
(loop for child in children do
|
||||
(when (not (find child res :test (make-id=)))
|
||||
(push child res)
|
||||
(descend (tooter:id child))))))))
|
||||
(descend root)
|
||||
res))))
|
||||
|
||||
(defun make-placeholder-tag-histogram ()
|
||||
"Make an empty `tooter:tag-history' (empty means all counts are 0
|
||||
and day is current time)"
|
||||
(make-instance 'tooter:tag-history
|
||||
:account-count 0
|
||||
:use-count 0
|
||||
:day (get-universal-time)))
|
||||
|
||||
(defun init ()
|
||||
"Initialize the client, prepare it for `authorize'."
|
||||
(flet ((credentials-filename ()
|
||||
(handler-bind ((error
|
||||
(lambda (e)
|
||||
(invoke-restart 'res:return-home-filename e))))
|
||||
(res:get-data-file +credentials-filename+))))
|
||||
(let ((resource-file (credentials-filename)))
|
||||
(if (not (fs:file-exists-p resource-file))
|
||||
(progn
|
||||
#+debug-mode (misc:dbg (_ "Initializing empty credentials file in ~a")
|
||||
resource-file)
|
||||
(fs:dump-sequence-to-file (serialize (make-instance 'credentials))
|
||||
resource-file)
|
||||
(init))
|
||||
(setf *credentials* (deserialize t resource-file))))))
|
|
@ -0,0 +1,66 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :box)
|
||||
|
||||
(defclass box ()
|
||||
((contents
|
||||
:initform nil
|
||||
:initarg :contents
|
||||
:accessor contents
|
||||
:documentation "the thing inside the box"))
|
||||
(:documentation "A generic object that contains a value"))
|
||||
|
||||
(defmethod print-object ((object box) stream)
|
||||
(print-unreadable-object (object stream :type t)
|
||||
(format stream "~a" (contents object))))
|
||||
|
||||
(defun boxp (thing)
|
||||
(typep thing 'box))
|
||||
|
||||
(defun box (thing)
|
||||
"Put `thing' in a box, if thing is aalready a box return `thing'."
|
||||
(if (boxp thing)
|
||||
thing
|
||||
(make-instance 'box :contents thing)))
|
||||
|
||||
(defun unbox (thing)
|
||||
"Unbox `thing' and returns the value contained, if `thing' is not a
|
||||
box return `thing'."
|
||||
(if (boxp thing)
|
||||
(contents thing)
|
||||
thing))
|
||||
|
||||
(defsetf unbox (object) (val)
|
||||
`(setf (contents ,object) ,val))
|
||||
|
||||
(defun dboxp (thing)
|
||||
"Return non nil id `thing' is a box that contains a box (double box)."
|
||||
(and (boxp thing)
|
||||
(boxp (contents thing))))
|
||||
|
||||
(defun dbox (thing)
|
||||
"Box `thing' in a double box."
|
||||
(if (dboxp thing)
|
||||
thing
|
||||
(make-instance 'box :contents (box thing))))
|
||||
|
||||
(defun dunbox (object)
|
||||
"Unbox a double box"
|
||||
(contents (contents object)))
|
||||
|
||||
(defsetf dunbox (object) (val)
|
||||
`(setf (contents (contents ,object)) ,val))
|
|
@ -0,0 +1,352 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :bs-tree)
|
||||
|
||||
(defclass node ()
|
||||
((parent
|
||||
:initarg :parent
|
||||
:initform nil
|
||||
:accessor parent)
|
||||
(data
|
||||
:initarg :data
|
||||
:initform nil
|
||||
:accessor data)
|
||||
(left
|
||||
:initarg :left
|
||||
:initform nil
|
||||
:accessor left)
|
||||
(right
|
||||
:initarg :right
|
||||
:initform nil
|
||||
:accessor right)))
|
||||
|
||||
(defgeneric node->string (object))
|
||||
|
||||
(defgeneric search (object datum &key key key-datum compare equal))
|
||||
|
||||
(defgeneric search-opt (object datum &key key key-datum compare equal candidate))
|
||||
|
||||
(defgeneric insert (object datum &key key key-datum compare equal &allow-other-keys))
|
||||
|
||||
(defgeneric leafp (object))
|
||||
|
||||
(defgeneric all-children-leaf-p (object))
|
||||
|
||||
(defgeneric map (object function))
|
||||
|
||||
(defgeneric map-node (object function))
|
||||
|
||||
(defgeneric %walk (object function args))
|
||||
|
||||
(defgeneric walk (object function &rest args))
|
||||
|
||||
(defgeneric bstp (object &key comp-fn key))
|
||||
|
||||
(defgeneric node->dot (object))
|
||||
|
||||
(defgeneric reconstruct-parent (object &optional parent))
|
||||
|
||||
(defgeneric find-max-node (object))
|
||||
|
||||
(defmethod data ((object (eql nil)))
|
||||
nil)
|
||||
|
||||
(defmethod parent ((object (eql nil)))
|
||||
nil)
|
||||
|
||||
(defmethod print-object ((object node) stream)
|
||||
(format stream "~a" (node->string object)))
|
||||
|
||||
(defmethod node->string ((object (eql nil)))
|
||||
"nil")
|
||||
|
||||
(defmethod node->string ((object node))
|
||||
(if (null (data object))
|
||||
""
|
||||
(format nil "~a ~% [~a] [~a]"
|
||||
(data object)
|
||||
(node->string (left object))
|
||||
(node->string (right object)))))
|
||||
|
||||
(defmethod leafp ((object node))
|
||||
(null (data object)))
|
||||
|
||||
(defmethod all-children-leaf-p ((object node))
|
||||
(and (leafp (left object))
|
||||
(leafp (right object))))
|
||||
|
||||
(defmethod search ((object node) datum &key (key #'identity)
|
||||
(key-datum #'identity) (compare #'<) (equal #'=))
|
||||
(if (leafp object)
|
||||
nil
|
||||
(cond
|
||||
((funcall equal (%key key (data object)) (%key key-datum datum))
|
||||
object)
|
||||
((funcall compare (%key key-datum datum) (%key key (data object)))
|
||||
(search (left object) datum :key key :key-datum key-datum
|
||||
:compare compare :equal equal))
|
||||
(t
|
||||
(search (right object) datum :key key :key-datum key-datum
|
||||
:compare compare :equal equal)))))
|
||||
|
||||
(defmethod search-opt ((object node) datum &key (key #'identity)
|
||||
(key-datum #'identity) (compare #'<) (equal #'=)
|
||||
(candidate nil))
|
||||
(if (leafp object)
|
||||
(if (and candidate (funcall equal (%key key (data candidate)) (%key key-datum datum)))
|
||||
candidate
|
||||
nil)
|
||||
(cond
|
||||
((funcall compare (%key key (data object)) (%key key-datum datum))
|
||||
(search-opt (left object) datum :key key :key-datum key-datum
|
||||
:compare compare :equal equal :candidate candidate))
|
||||
(t
|
||||
(search-opt (right object) datum :key key :key-datum key-datum
|
||||
:compare compare :equal equal :candidate object)))))
|
||||
|
||||
(defun make-node (data left right parent)
|
||||
(make-instance 'node :left left :right right :data data :parent parent))
|
||||
|
||||
(defun make-leaf (parent)
|
||||
(make-instance 'node :parent parent :left nil :right nil))
|
||||
|
||||
(defun make-root-node (datum)
|
||||
(let* ((tree (make-node datum nil nil nil))
|
||||
(l-leaf (make-leaf tree))
|
||||
(r-leaf (make-leaf tree)))
|
||||
(setf (left tree) l-leaf
|
||||
(right tree) r-leaf)
|
||||
tree))
|
||||
|
||||
(defun %key (key-fn a)
|
||||
(funcall key-fn a))
|
||||
|
||||
(alexandria:define-constant +data+ :data :test #'eq)
|
||||
|
||||
(alexandria:define-constant +left+ :left :test #'eq)
|
||||
|
||||
(alexandria:define-constant +right+ :right :test #'eq)
|
||||
|
||||
(alexandria:define-constant +parent+ :parent :test #'eq)
|
||||
|
||||
(defmethod to-sexp ((object node))
|
||||
(let ((*print-circle* t))
|
||||
(list +data+ (to-sexp (data object))
|
||||
+left+ (to-sexp (left object))
|
||||
+right+ (to-sexp (right object))
|
||||
+parent+ (to-sexp (data (parent object))))))
|
||||
|
||||
(defmethod from-sexp ((object node) sexp)
|
||||
(declare (ignorable object))
|
||||
(labels ((%from-sexp (sexp)
|
||||
(if (null sexp)
|
||||
(make-leaf nil)
|
||||
(make-node (getf sexp +data+)
|
||||
(from-sexp object (getf sexp +left+))
|
||||
(from-sexp object (getf sexp +right+)) nil))))
|
||||
(let ((new-tree (%from-sexp sexp)))
|
||||
(reconstruct-parent new-tree))))
|
||||
|
||||
(defmacro %make-new-node (make-node-fn node data left right parent args)
|
||||
`(,make-node-fn ,data ,left ,right ,parent
|
||||
,@(loop for i in args collect
|
||||
`(,i ,node))))
|
||||
|
||||
(defmacro with-insert-local-function ((make-left-node-fn
|
||||
make-right-node-fn
|
||||
make-leaf-node-fn
|
||||
make-leaf-fn
|
||||
left-descend-fn
|
||||
right-descend-fn)
|
||||
&body body)
|
||||
(let ((insert-fn (alexandria:format-symbol t "%INSERT")))
|
||||
`(labels ((,insert-fn (node datum key key-datum compare equal)
|
||||
(if (leafp node)
|
||||
(let* ((new-node (,make-leaf-node-fn
|
||||
datum nil nil (parent node)))
|
||||
(l-leaf (,make-leaf-fn new-node))
|
||||
(r-leaf (,make-leaf-fn new-node)))
|
||||
(setf (data new-node) datum
|
||||
(left new-node) l-leaf
|
||||
(right new-node) r-leaf)
|
||||
new-node)
|
||||
(cond
|
||||
((funcall equal (%key key (data node)) (%key key-datum datum))
|
||||
node)
|
||||
((funcall compare (%key key-datum datum) (%key key (data node)))
|
||||
,(let ((a `(let ((new-node (,make-left-node-fn
|
||||
(data node)
|
||||
(,insert-fn (left node) datum key
|
||||
key-datum compare equal)
|
||||
(right node)
|
||||
(parent node))))
|
||||
(setf (parent (right new-node)) new-node
|
||||
(parent (left new-node)) new-node)
|
||||
new-node)))
|
||||
(if left-descend-fn
|
||||
`(,left-descend-fn ,a)
|
||||
a)))
|
||||
(t
|
||||
,(let ((a `(let ((new-node (,make-right-node-fn
|
||||
(data node)
|
||||
(left node)
|
||||
(,insert-fn (right node) datum key
|
||||
key-datum compare equal)
|
||||
(parent node))))
|
||||
(setf (parent (right new-node)) new-node
|
||||
(parent (left new-node)) new-node)
|
||||
new-node)))
|
||||
(if right-descend-fn
|
||||
`(,right-descend-fn ,a)
|
||||
a)))))))
|
||||
,@body)))
|
||||
|
||||
(defmethod insert ((object node) datum &key (key #'identity) (key-datum #'identity)
|
||||
(compare #'<) (equal #'=))
|
||||
(with-insert-local-function (make-node make-node make-node make-leaf nil nil)
|
||||
(%insert object datum key key-datum compare equal)))
|
||||
|
||||
(defmethod map ((object node) function)
|
||||
(with-accessors ((data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(make-leaf nil)
|
||||
(make-node (funcall function data) (map left function)
|
||||
(map right function) nil))))
|
||||
|
||||
(defmethod map-node ((object node) function)
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(funcall function object (make-leaf object))
|
||||
(funcall function object (make-node data
|
||||
(map-node left function)
|
||||
(map-node right function) nil)))))
|
||||
|
||||
(defmethod %walk ((object node) function args)
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(when (not (leafp object))
|
||||
(apply function object args)
|
||||
(%walk left function args)
|
||||
(%walk right function args))))
|
||||
|
||||
(defmethod %walk ((object node) function (args (eql nil)))
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(when (not (leafp object))
|
||||
(funcall function object)
|
||||
(%walk left function args)
|
||||
(%walk right function args))))
|
||||
|
||||
(defmethod %walk ((object (eql nil)) function args)
|
||||
(apply function object args))
|
||||
|
||||
(defmethod walk ((object node) function &rest args)
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(when (not (leafp object))
|
||||
(apply function object args)
|
||||
(%walk left function args)
|
||||
(%walk right function args))))
|
||||
|
||||
(defmethod walk ((object (eql nil)) function &rest args)
|
||||
(apply function object args))
|
||||
|
||||
(defun gather-all (node &key (add-root t))
|
||||
(let ((res nil))
|
||||
(when (not (leafp node))
|
||||
(walk (left node) #'(lambda (n) (push (data n) res)))
|
||||
(walk (right node) #'(lambda (n) (push (data n) res)))
|
||||
(and add-root (push (data node) res)))
|
||||
res))
|
||||
|
||||
(defmethod bstp ((object node) &key (comp-fn #'<) (key #'identity))
|
||||
(labels ((balanced (node)
|
||||
(with-accessors ((left left) (right right)) node
|
||||
(if (not (leafp node))
|
||||
(let ((left-children (and left (gather-all left)))
|
||||
(right-children (and right (gather-all right)))
|
||||
(pivot (funcall key (data node))))
|
||||
(cond
|
||||
((and (null left-children) (null right-children))
|
||||
t);; leaf node, always balanced
|
||||
((and left-children right-children)
|
||||
(and
|
||||
(every #'(lambda (a) (funcall comp-fn (funcall key a) pivot))
|
||||
left-children)
|
||||
(every #'(lambda (a) (funcall comp-fn pivot (funcall key a)))
|
||||
right-children)
|
||||
'a))
|
||||
((null left-children)
|
||||
(and (every #'(lambda (a) (funcall comp-fn pivot (funcall key a)))
|
||||
right-children))
|
||||
'l)
|
||||
((null right-children)
|
||||
(every #'(lambda (a) (funcall comp-fn (funcall key a) pivot))
|
||||
left-children)
|
||||
'r)))
|
||||
(misc:dbg "leaf root ~a" (data node))))))
|
||||
(let ((res nil))
|
||||
(walk object #'(lambda (n) (push
|
||||
(cons (data n) (or (balanced n) '(nil)))
|
||||
res)))
|
||||
(every #'cdr res))))
|
||||
|
||||
(defmethod node->dot ((object node))
|
||||
(labels ((nodes ()
|
||||
(append
|
||||
(list
|
||||
`(:node ((:id ,(format nil "~a" (data object)))
|
||||
(:label ,(format nil "~ap~a" (data object)
|
||||
(data (parent object)))))))
|
||||
(if (not (leafp (left object)))
|
||||
(node->dot (left object))
|
||||
(list
|
||||
`(:node ((:id ,(format nil "nil-l~a" (data object)))
|
||||
(:label "nil")))))
|
||||
(if (not (leafp (right object)))
|
||||
(node->dot (right object))
|
||||
(list
|
||||
`(:node ((:id ,(format nil "nil-r~a" (data object)))
|
||||
(:label "nil")))))))
|
||||
(edges ()
|
||||
(append
|
||||
(if (data (left object))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "~a" (data (left object)))))))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "nil-l~a" (data object)))))))
|
||||
(if (data (right object))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "~a" (data (right object)))))))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "nil-r~a" (data object))))))))))
|
||||
(append (nodes) (edges))))
|
||||
|
||||
|
||||
(defmethod reconstruct-parent ((object node) &optional (parent (parent object)))
|
||||
(with-accessors ((data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(make-leaf parent)
|
||||
(make-node data
|
||||
(reconstruct-parent left object)
|
||||
(reconstruct-parent right object) parent))))
|
||||
|
||||
(defmethod find-max-node ((object node))
|
||||
(if (leafp (right object))
|
||||
object
|
||||
(find-max-node (right object))))
|
|
@ -0,0 +1,96 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :command-line)
|
||||
|
||||
(defun print-version ()
|
||||
(format t (_ "~a version ~a~%") +program-name+ +program-version+))
|
||||
|
||||
(defmacro gen-opts ()
|
||||
`(opts:define-opts
|
||||
(:name :help
|
||||
:description (_ "Print help and exit")
|
||||
:short #\h
|
||||
:long "help")
|
||||
(:name :version
|
||||
:description (_ "Print program information and exit")
|
||||
:short #\v
|
||||
:long "version")
|
||||
(:name :folder
|
||||
:description (_ "Starting folder")
|
||||
:short #\f
|
||||
:arg-parser #'identity
|
||||
:meta-var (_ "FOLDER-NAME")
|
||||
:long "folder")
|
||||
(:name :timeline
|
||||
:description (_ "Starting timeline")
|
||||
:short #\t
|
||||
:meta-var (_ "TIMELINE-NAME")
|
||||
:arg-parser #'identity
|
||||
:long "timeline")
|
||||
(:name :update-timeline
|
||||
:description (_ "Update timeline")
|
||||
:short #\u
|
||||
:long "update-timeline")
|
||||
(:name :check-follows-requests
|
||||
:description (_ "Check follows requests")
|
||||
:short #\c
|
||||
:long "check-follows-requests")
|
||||
(:name :execute
|
||||
:description (_ "Execute script")
|
||||
:short #\e
|
||||
:arg-parser #'identity
|
||||
:meta-var (_ "SCRIPT-FILE")
|
||||
:long "execute-script")))
|
||||
|
||||
(defparameter *start-folder* nil)
|
||||
|
||||
(defparameter *start-timeline* nil)
|
||||
|
||||
(defparameter *update-timeline* nil)
|
||||
|
||||
(defparameter *script-file* nil)
|
||||
|
||||
(defparameter *check-follow-requests* nil)
|
||||
|
||||
(defun exit-on-error (e)
|
||||
(format *error-output* "~a~%" e)
|
||||
(os-utils:exit-program 1))
|
||||
|
||||
(defun manage-opts ()
|
||||
(handler-bind ((opts:unknown-option #'exit-on-error)
|
||||
(opts:missing-arg #'exit-on-error)
|
||||
(opts:missing-required-option #'exit-on-error))
|
||||
(gen-opts)
|
||||
(let ((options (opts:get-opts)))
|
||||
(when (getf options :help)
|
||||
(print-version)
|
||||
(opts:describe :usage-of +program-name+)
|
||||
(os-utils:exit-program))
|
||||
(when (getf options :version)
|
||||
(print-version)
|
||||
(os-utils:exit-program))
|
||||
(when (getf options :folder)
|
||||
(setf *start-folder* (getf options :folder)))
|
||||
(when (getf options :timeline)
|
||||
(setf *start-timeline* (getf options :timeline)))
|
||||
(when (getf options :update-timeline)
|
||||
(setf *update-timeline* (getf options :update-timeline)))
|
||||
(when (getf options :execute)
|
||||
(setf *script-file* (getf options :execute)))
|
||||
(when (getf options :check-follows-requests)
|
||||
(setf *check-follow-requests* (getf options :check-follows-requests))))))
|
|
@ -0,0 +1,486 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :command-window)
|
||||
|
||||
(defclass command-window (wrapper-window point-tracker)
|
||||
((command-line
|
||||
:initform ()
|
||||
:initarg :command-line
|
||||
:accessor command-line
|
||||
:documentation "A list of keys so far inserted by the user")
|
||||
(error-message
|
||||
:initform nil
|
||||
:initarg :error-message
|
||||
:accessor error-message
|
||||
:documentation "Error message to be printed")
|
||||
(error-message-bg
|
||||
:initform nil
|
||||
:initarg :error-message-bg
|
||||
:accessor error-message-bg
|
||||
:documentation "Error message background color")
|
||||
(error-message-fg
|
||||
:initform nil
|
||||
:initarg :error-message-fg
|
||||
:accessor error-message-fg
|
||||
:documentation "Error message foreground color")
|
||||
(error-message-attributes
|
||||
:initform nil
|
||||
:initarg :error-message-attributes
|
||||
:accessor error-message-attributes
|
||||
:documentation "Error message attributes (bold, blink etc.)")
|
||||
(info-message
|
||||
:initform nil
|
||||
:initarg :info-message
|
||||
:accessor info-message
|
||||
:documentation "Information message to be printed")
|
||||
(info-message-bg
|
||||
:initform nil
|
||||
:initarg :info-message-bg
|
||||
:accessor info-message-bg
|
||||
:documentation "Info message background color")
|
||||
(info-message-fg
|
||||
:initform nil
|
||||
:initarg :info-message-fg
|
||||
:accessor info-message-fg
|
||||
:documentation "Info message foreground color")
|
||||
(info-message-attributes
|
||||
:initform nil
|
||||
:initarg :info-message-attributes
|
||||
:accessor info-message-attributes
|
||||
:documentation "Info message attributes (bold, blink etc.)")
|
||||
(commands-separator
|
||||
:initform " "
|
||||
:initarg :commands-separator
|
||||
:accessor commands-separator
|
||||
:documentation "The text printed to separates each key in command")
|
||||
(suggestions-win
|
||||
:initform nil
|
||||
:initarg :suggestions-win
|
||||
:accessor suggestions-win
|
||||
:documentation "The windows that print contect stuccesions to
|
||||
user (e.g. autocomplete path")
|
||||
(history-position
|
||||
:initarg :history-position
|
||||
:accessor history-position
|
||||
:documentation "Current positions in the history of commands")
|
||||
(event-to-answer
|
||||
:initform nil
|
||||
:initarg :event-to-answer
|
||||
:accessor event-to-answer
|
||||
:documentation "This is the event that was triggered by a function
|
||||
that instruct the command window to ask user for an input. This
|
||||
event is inpected to get the prompt and, after the input is
|
||||
complete, a slot is setted with such input, then another event
|
||||
`user-input-string-event' is generated to notify (via a condition
|
||||
variable) the thread that generated `event-to-answer' the fact
|
||||
that the input is complete.")
|
||||
(input-mode
|
||||
:initform :keybinding
|
||||
:initarg :input-mode
|
||||
:accessor input-mode
|
||||
:documentation "The mode of accepting input for this window. Can
|
||||
be either `:keybinding' or `:string'. the former for key command the latter for free input (e.g filepath, username, etc")))
|
||||
|
||||
(defmethod initialize-instance :after ((object command-window) &key &allow-other-keys)
|
||||
(with-accessors ((command-line command-line)
|
||||
(commands-separator commands-separator)
|
||||
(error-message error-message)
|
||||
(history-position history-position)
|
||||
(prompt prompt)
|
||||
(suggestions-win suggestions-win)) object
|
||||
;; poor man cache...
|
||||
(setf specials:*keybindings-suggestions-window* (keybindings-window:init))
|
||||
(setf specials:*strings-suggestions-window* (complete-window:init))
|
||||
(set-keybinding-mode object)
|
||||
object))
|
||||
|
||||
(defun set-history-most-recent (window prompt)
|
||||
(with-accessors ((command-line command-line)
|
||||
(history-position history-position)) window
|
||||
(setf history-position
|
||||
(1+ (db:most-recent-history-id prompt)))))
|
||||
|
||||
(defmethod refresh-config :after ((object command-window))
|
||||
(with-accessors ((error-message-bg error-message-bg)
|
||||
(error-message-fg error-message-fg)
|
||||
(error-message-attributes error-message-attributes)
|
||||
(info-message-bg info-message-bg)
|
||||
(info-message-fg info-message-fg)
|
||||
(info-message-attributes info-message-attributes)) object
|
||||
(let* ((w (win-width *main-window*))
|
||||
(h +command-window-height+)
|
||||
(x 0)
|
||||
(y (1- (win-height *main-window*))))
|
||||
(refresh-config-colors object swconf:+key-command-window+)
|
||||
(multiple-value-bind (bg fg value)
|
||||
(swconf:command-separator-config-values)
|
||||
(multiple-value-bind (error-bg error-fg error-attributes)
|
||||
(swconf:command-error-message-colors)
|
||||
(multiple-value-bind (info-bg info-fg info-attributes)
|
||||
(swconf:command-info-message-colors)
|
||||
(setf error-message-bg error-bg)
|
||||
(setf error-message-fg error-fg)
|
||||
(setf error-message-attributes error-attributes)
|
||||
(setf info-message-bg info-bg)
|
||||
(setf info-message-fg info-fg)
|
||||
(setf info-message-attributes info-attributes)
|
||||
(setf (point-fg object) (win-bgcolor object))
|
||||
(setf (point-bg object) (win-fgcolor object))
|
||||
(setf (commands-separator object)
|
||||
(make-tui-string value
|
||||
:fgcolor fg
|
||||
:bgcolor bg))
|
||||
(win-resize object w h)
|
||||
(win-move object x y)
|
||||
object))))))
|
||||
|
||||
(defmethod calculate ((object command-window) dt)
|
||||
(with-accessors ((suggestions-win suggestions-win)) object
|
||||
(when suggestions-win
|
||||
(calculate suggestions-win dt))))
|
||||
|
||||
(defun draw-keybinding-mode (win)
|
||||
"Draw window `win' accepting key commands"
|
||||
(with-accessors ((command-line command-line)
|
||||
(point-position point-position)
|
||||
(point-bg point-bg)
|
||||
(point-fg point-fg)
|
||||
(prompt prompt)) win
|
||||
(let* ((length-cmd-line (length command-line))
|
||||
(no-prompt-point-pos (no-prompt-point-pos win))
|
||||
(cursor-value (if (and (> length-cmd-line 0)
|
||||
(< no-prompt-point-pos
|
||||
length-cmd-line))
|
||||
(elt command-line no-prompt-point-pos)
|
||||
#\Space)))
|
||||
(print-text win prompt 0 0)
|
||||
(when command-line
|
||||
(print-text win command-line (length prompt) 0))
|
||||
(print-text win
|
||||
cursor-value
|
||||
point-position
|
||||
0
|
||||
:fgcolor point-fg
|
||||
:bgcolor point-bg))))
|
||||
|
||||
(defmethod draw ((object command-window))
|
||||
(with-accessors ((command-line command-line)
|
||||
(commands-separator commands-separator)
|
||||
(error-message-bg error-message-bg)
|
||||
(error-message-fg error-message-fg)
|
||||
(error-message-attributes error-message-attributes)
|
||||
(error-message error-message)
|
||||
(info-message-bg info-message-bg)
|
||||
(info-message-fg info-message-fg)
|
||||
(info-message info-message)
|
||||
(info-message-attributes info-message-attributes)
|
||||
(suggestions-win suggestions-win)) object
|
||||
(when suggestions-win
|
||||
(draw suggestions-win))
|
||||
(win-clear object :redraw nil)
|
||||
(cond
|
||||
(error-message
|
||||
(print-text object error-message 0 0
|
||||
:bgcolor error-message-bg
|
||||
:fgcolor error-message-fg
|
||||
:attributes error-message-attributes))
|
||||
(info-message
|
||||
(print-text object info-message 0 0
|
||||
:bgcolor info-message-bg
|
||||
:fgcolor info-message-fg
|
||||
:attributes info-message-attributes))
|
||||
(t
|
||||
(if (keybindings-mode-p object)
|
||||
(when command-line
|
||||
(let ((advance 0))
|
||||
(loop for (command . rest) on command-line while rest do
|
||||
(print-text object command advance 0)
|
||||
(incf advance (length command))
|
||||
(print-text object commands-separator advance 0)
|
||||
(incf advance (text-width commands-separator)))
|
||||
(print-text object (last-elt command-line) advance 0)))
|
||||
(draw-keybinding-mode object))))
|
||||
(win-refresh object)))
|
||||
|
||||
(defgeneric enqueue-command (object command decode-key))
|
||||
|
||||
(defgeneric complete-at-point (object))
|
||||
|
||||
(defgeneric show-candidate-completion (object))
|
||||
|
||||
(defgeneric add-error-message (object message))
|
||||
|
||||
(defgeneric add-info-message (object message))
|
||||
|
||||
(defun manage-command-event (command-window event)
|
||||
"Intercept UI events in keybindg mode"
|
||||
(with-accessors ((command-line command-line)
|
||||
(suggestions-win suggestions-win)) command-window
|
||||
;; some envents should by intercepted by command window
|
||||
(cond
|
||||
((eq :control-left event) ; suggestion win pagination
|
||||
(move-suggestion-page-left command-window))
|
||||
((eq :control-right event) ; suggestion win pagination
|
||||
(move-suggestion-page-right command-window))
|
||||
((eq :backspace event) ; delete last command or char
|
||||
(setf command-line (safe-all-but-last-elt command-line))
|
||||
(when-let ((last-command (safe-last-elt command-line)))
|
||||
(setf command-line (safe-all-but-last-elt command-line))
|
||||
(enqueue-command command-window last-command nil)))
|
||||
(t
|
||||
(enqueue-command command-window event t)))))
|
||||
|
||||
(defun update-suggestions (window key-decoded)
|
||||
"Update suggestion window"
|
||||
(with-accessors ((command-line command-line)
|
||||
(suggestions-win suggestions-win)) window
|
||||
;; if command-line is not null we are in the middle of a command
|
||||
;; so no need to update the slot of suggestion-win with a new tree
|
||||
(if command-line
|
||||
(suggestions-window:update-suggestions suggestions-win
|
||||
key-decoded
|
||||
:tree nil)
|
||||
(let* ((focused-keybindings (main-window:focused-keybindings specials:*main-window*))
|
||||
(found-subtree (and focused-keybindings
|
||||
(suggestions-window:update-suggestions suggestions-win
|
||||
key-decoded
|
||||
:tree
|
||||
focused-keybindings))))
|
||||
(or found-subtree
|
||||
(suggestions-window:update-suggestions suggestions-win
|
||||
key-decoded
|
||||
:tree *global-keymap*))))))
|
||||
|
||||
(defmethod enqueue-command ((object command-window) command decode-key-p)
|
||||
"Enqueue and process, if possibl,e `command` object, if decode-key
|
||||
is not null decode key to something more human readable."
|
||||
(with-accessors ((command-line command-line)
|
||||
(info-message info-message)
|
||||
(error-message error-message)
|
||||
(suggestions-win suggestions-win)) object
|
||||
(when (null suggestions-win)
|
||||
(setf suggestions-win (keybindings-window:init)))
|
||||
(win-show suggestions-win)
|
||||
(let* ((key-decoded (if decode-key-p
|
||||
(decode-key-event command)
|
||||
command))
|
||||
(found-subtree (update-suggestions object key-decoded)))
|
||||
(setf error-message nil)
|
||||
(setf info-message nil)
|
||||
(cond
|
||||
((null found-subtree)
|
||||
(let ((missing-command (format nil "~s" (lcat command-line
|
||||
(list key-decoded)))))
|
||||
(restart-case
|
||||
(error 'conditions:command-not-found
|
||||
:command missing-command)
|
||||
(print-error (e)
|
||||
(declare (ignore e))
|
||||
(win-hide suggestions-win)
|
||||
(setf suggestions-win nil)
|
||||
(setf command-line nil)
|
||||
(setf error-message
|
||||
(format nil
|
||||
(_ "Error: command ~a not found")
|
||||
missing-command))))))
|
||||
((functionp found-subtree)
|
||||
(win-hide suggestions-win)
|
||||
(setf suggestions-win nil)
|
||||
(setf command-line nil)
|
||||
(funcall found-subtree))
|
||||
(t
|
||||
(setf command-line (reverse command-line))
|
||||
(push key-decoded command-line)
|
||||
(setf command-line (reverse command-line))))))
|
||||
object)
|
||||
|
||||
(defmethod complete-at-point ((object command-window))
|
||||
"Complete input at point (string mode only)"
|
||||
(with-accessors ((command-line command-line)
|
||||
(suggestions-win suggestions-win)) object
|
||||
(when (null suggestions-win)
|
||||
(setf suggestions-win (complete-window:init)))
|
||||
(win-show suggestions-win)
|
||||
(multiple-value-bind (candidates common-prefix)
|
||||
(suggestions-window:update-suggestions suggestions-win
|
||||
command-line)
|
||||
(if candidates
|
||||
(progn
|
||||
(when (length= candidates 1)
|
||||
(win-hide suggestions-win))
|
||||
(if common-prefix
|
||||
(setf command-line common-prefix)
|
||||
(setf command-line (complete:shortest-candidate candidates)))
|
||||
(move-point-to-end object command-line))
|
||||
(win-hide suggestions-win))))
|
||||
object)
|
||||
|
||||
(defmethod show-candidate-completion ((object command-window))
|
||||
(with-accessors ((command-line command-line)
|
||||
(suggestions-win suggestions-win)) object
|
||||
(when (null suggestions-win)
|
||||
(setf suggestions-win (complete-window:init)))
|
||||
(let ((candidates (suggestions-window:update-suggestions suggestions-win
|
||||
command-line)))
|
||||
(if candidates
|
||||
(win-show suggestions-win)
|
||||
(win-hide suggestions-win)))))
|
||||
|
||||
(defmethod add-error-message ((object command-window) message)
|
||||
(setf (error-message object) message)
|
||||
(draw object))
|
||||
|
||||
(defmethod add-info-message ((object command-window) message)
|
||||
(setf (info-message object) message)
|
||||
(draw object))
|
||||
|
||||
(defun move-suggestion-page (win offset)
|
||||
"Paginate win (suggestion window) by offset, will not go past the numer of pages."
|
||||
(with-accessors ((suggestions-win suggestions-win)) win
|
||||
(when suggestions-win
|
||||
(with-accessors ((current-page suggestions-window:current-page)
|
||||
(paginated-info suggestions-window:paginated-info)) suggestions-win
|
||||
|
||||
(setf current-page (clamp (+ offset current-page)
|
||||
0
|
||||
(1- (length paginated-info))))))))
|
||||
|
||||
(defun move-suggestion-page-left (win)
|
||||
(move-suggestion-page win -1))
|
||||
|
||||
(defun move-suggestion-page-right (win)
|
||||
(move-suggestion-page win 1))
|
||||
|
||||
(defun fire-user-input-event (win)
|
||||
"Generates an event to notify that the user inserted an input on the
|
||||
command line."
|
||||
(with-accessors ((event-to-answer event-to-answer)
|
||||
(command-line command-line)) win
|
||||
(assert event-to-answer)
|
||||
(assert (typep event-to-answer
|
||||
'program-events:program-event))
|
||||
(let ((input-done-event (make-instance 'program-events:user-input-string-event
|
||||
:payload
|
||||
(program-events:payload event-to-answer)
|
||||
:lock
|
||||
(program-events:lock event-to-answer)
|
||||
:condition-variable
|
||||
(program-events:condition-variable event-to-answer))))
|
||||
(setf (box:dunbox (program-events:payload input-done-event))
|
||||
command-line)
|
||||
(program-events:push-event input-done-event))))
|
||||
|
||||
(defun manage-string-event (command-window event)
|
||||
"Manage UI events when `command-window` is in string mode"
|
||||
(with-accessors ((command-line command-line)
|
||||
(prompt prompt)
|
||||
(history-position history-position)
|
||||
(suggestions-win suggestions-win)) command-window
|
||||
(flet ((set-history (new-id new-input)
|
||||
(when (and new-id
|
||||
new-input)
|
||||
(setf history-position new-id)
|
||||
(setf command-line new-input)))
|
||||
(insert-in-history (prompt command-line)
|
||||
(db:insert-in-history prompt command-line)
|
||||
(set-history-most-recent command-window prompt)))
|
||||
(cond
|
||||
((eq :control-left event)
|
||||
(move-suggestion-page-left command-window))
|
||||
((eq :control-right event)
|
||||
(move-suggestion-page-right command-window))
|
||||
((eq :backspace event)
|
||||
(setf command-line (delete-at-point command-window command-line :direction :left))
|
||||
(show-candidate-completion command-window))
|
||||
((eq :dc event)
|
||||
(setf command-line (delete-at-point command-window command-line :direction :right))
|
||||
(show-candidate-completion command-window))
|
||||
((eq :left event)
|
||||
(move-point-left command-window))
|
||||
((eq :right event)
|
||||
(move-point-right command-window (length command-line)))
|
||||
((eq :end event)
|
||||
(move-point-to-end command-window command-line))
|
||||
((eq :home event)
|
||||
(move-point-to-start command-window))
|
||||
((eq :up event)
|
||||
(multiple-value-bind (new-id new-input)
|
||||
(db:previous-in-history history-position prompt)
|
||||
(set-history new-id new-input)))
|
||||
((eq :down event)
|
||||
(multiple-value-bind (new-id new-input)
|
||||
(db:next-in-history history-position prompt)
|
||||
(set-history new-id new-input)))
|
||||
((characterp event)
|
||||
(cond
|
||||
((char= #\Newline event)
|
||||
(insert-in-history prompt command-line)
|
||||
(fire-user-input-event command-window)
|
||||
(setf command-line nil)
|
||||
(move-point-to-start command-window)
|
||||
(set-keybinding-mode command-window))
|
||||
((char= #\Tab event)
|
||||
(complete-at-point command-window))
|
||||
(t
|
||||
(when (null suggestions-win)
|
||||
(setf suggestions-win (complete-window:init)))
|
||||
(win-show suggestions-win)
|
||||
(setf command-line
|
||||
(insert-at-point command-window event command-line))
|
||||
(show-candidate-completion command-window)))))))
|
||||
command-window)
|
||||
|
||||
(defun set-input-mode (win mode suggestions-cached-win)
|
||||
"Set win (command window) mode: keybindings or string mode"
|
||||
(assert (member mode '(:keybinding :string)))
|
||||
(with-accessors ((suggestions-win suggestions-win)
|
||||
(input-mode input-mode)) win
|
||||
(setf input-mode mode)
|
||||
(when suggestions-win
|
||||
(win-hide suggestions-win))
|
||||
(setf suggestions-win suggestions-cached-win)))
|
||||
|
||||
(defmacro gen-set-mode-function (fn-name mode suggestions-cached-win)
|
||||
`(defun ,(format-fn-symbol t "set-~a-mode" fn-name) (win)
|
||||
(set-input-mode win ,mode ,suggestions-cached-win)))
|
||||
|
||||
(gen-set-mode-function keybinding :keybinding specials:*keybindings-suggestions-window*)
|
||||
|
||||
(gen-set-mode-function string :string specials:*strings-suggestions-window*)
|
||||
|
||||
(defun keybindings-mode-p (win)
|
||||
"Non nil if win is in keybings mode"
|
||||
(eq (input-mode win)
|
||||
:keybinding))
|
||||
|
||||
(defun manage-event (event)
|
||||
"Manage UI event, these are not program events but events fired by
|
||||
the curses library (croatoan)"
|
||||
(if (keybindings-mode-p *command-window*)
|
||||
(manage-command-event *command-window* event)
|
||||
(manage-string-event *command-window* event))
|
||||
(draw *command-window*))
|
||||
|
||||
(defun init ()
|
||||
"Initialize the window"
|
||||
(with-croatoan-window (croatoan-main-window *main-window*)
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *command-window*
|
||||
(make-instance 'command-window
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *command-window*)
|
||||
*command-window*)))
|
|
@ -0,0 +1,70 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :complete-window)
|
||||
|
||||
(defclass complete-window (suggestions-window)
|
||||
()
|
||||
(:documentation "A window to shows the possible completion for an
|
||||
user input"))
|
||||
|
||||
(defmethod calculate ((object complete-window) dt)
|
||||
(declare (ignore object dt)))
|
||||
|
||||
(defmethod update-suggestions ((object complete-window) hint &key &allow-other-keys)
|
||||
"List the possible expansion of `hint' using the function
|
||||
`complete:*complete-function*'."
|
||||
(with-accessors ((paginated-info paginated-info)) object
|
||||
(multiple-value-bind (candidates common-prefix)
|
||||
(funcall complete:*complete-function* hint)
|
||||
(when candidates
|
||||
(when-let ((batches (text-utils:box-fit-multiple-column candidates
|
||||
(- (win-width object) 2)
|
||||
(- (win-height object)
|
||||
+box-height-diff+))))
|
||||
(setf paginated-info batches)
|
||||
(values candidates common-prefix))))))
|
||||
|
||||
(defmethod draw :after ((object complete-window))
|
||||
(with-accessors ((keybindings-tree keybindings-tree)
|
||||
(paginated-info paginated-info)
|
||||
(current-page current-page)) object
|
||||
(when-window-shown (object)
|
||||
(win-clear object :redraw nil)
|
||||
(win-box object)
|
||||
(when paginated-info
|
||||
(loop
|
||||
for column in (elt paginated-info current-page)
|
||||
with column-count = 1
|
||||
do
|
||||
(let ((column-size (length (first column))))
|
||||
(loop
|
||||
for row in column
|
||||
with row-count = 1 do
|
||||
(print-text object row column-count row-count)
|
||||
(incf row-count))
|
||||
(incf column-count column-size)))
|
||||
(draw-pagination-info object))
|
||||
(win-refresh object))))
|
||||
|
||||
(defun init ()
|
||||
"Initialize a complete window"
|
||||
(let* ((low-level-window (make-croatoan-window :draw-border t))
|
||||
(high-level-window (make-instance 'complete-window
|
||||
:croatoan-window low-level-window)))
|
||||
(refresh-config high-level-window)
|
||||
(win-hide high-level-window)
|
||||
high-level-window))
|
|
@ -0,0 +1,149 @@
|
|||
;;;; Copyright (c) 2003, 2004 Nikodemus Siivola, Julian Squires
|
||||
;;;;
|
||||
;;;; Permission is hereby granted, free of charge, to any person obtaining
|
||||
;;;; a copy of this software and associated documentation files (the
|
||||
;;;; "Software"), to deal in the Software without restriction, including
|
||||
;;;; without limitation the rights to use, copy, modify, merge, publish,
|
||||
;;;; distribute, sublicense, and/or sell copies of the Software, and to
|
||||
;;;; permit persons to whom the Software is furnished to do so, subject to
|
||||
;;;; the following conditions:
|
||||
;;;;
|
||||
;;;; The above copyright notice and this permission notice shall be included
|
||||
;;;; in all copies or substantial portions of the Software.
|
||||
;;;;
|
||||
;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
;;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
;;;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
;;;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
;;;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
;;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(in-package :complete)
|
||||
|
||||
(defparameter *complete-function* nil
|
||||
"A function that get an hint and return two values:
|
||||
- a list of entries that match that hint
|
||||
- the common prefix of such candidates.
|
||||
See: complete:directory-complete")
|
||||
|
||||
(defun shortest-candidate (candidates)
|
||||
"candidates is a sorted list (by length) -> first of the list,
|
||||
otherwise return candidates"
|
||||
(and candidates
|
||||
(if (listp candidates)
|
||||
(first candidates)
|
||||
candidates)))
|
||||
|
||||
(defun reduce-to-common-prefix (items)
|
||||
(reduce #'text-utils:common-prefix items))
|
||||
|
||||
(defun pathname-directory-pathname (pathname)
|
||||
"convenience function to make a pathname object to a directory"
|
||||
(make-pathname :name nil :type nil
|
||||
:defaults pathname))
|
||||
|
||||
(defun underlying-directory-p (pathname)
|
||||
"Find the actual directory of pathname (i.e. resolve file link"
|
||||
(case (file-kind pathname)
|
||||
(:directory t)
|
||||
(:symbolic-link
|
||||
(file-kind (merge-pathnames (read-link pathname) pathname)))))
|
||||
|
||||
;;; We can't easily do zsh-style tab-completion of ~us into ~user, but
|
||||
;;; at least we can expand ~ and ~user. The other bug here at the
|
||||
;;; moment is that ~nonexistant will complete to the same as ~.
|
||||
(defun tilde-expand-string (string)
|
||||
"Returns the supplied string, with a prefix of ~ or ~user expanded
|
||||
to the appropriate home directory."
|
||||
(if (and (> (length string) 0)
|
||||
(eql (schar string 0) #\~))
|
||||
(flet ((chop (s)
|
||||
(subseq s 0 (1- (length s)))))
|
||||
(let* ((slash-index (loop for i below (length string)
|
||||
when (eql (schar string i) #\/)
|
||||
return i))
|
||||
(suffix (and slash-index (subseq string slash-index)))
|
||||
(uname (subseq string 1 slash-index))
|
||||
(homedir (or (cdr (assoc :home (user-info uname)))
|
||||
(chop (namestring
|
||||
(or (probe-file (user-homedir-pathname))
|
||||
(return-from tilde-expand-string
|
||||
string)))))))
|
||||
(concatenate 'string homedir (or suffix ""))))
|
||||
string))
|
||||
|
||||
(defun directory-complete (string)
|
||||
"Return two values completion of 'string' (non nil if can be completed) and "
|
||||
(declare (simple-string string))
|
||||
(let* ((string (tilde-expand-string string))
|
||||
(dir (pathname-directory-pathname string))
|
||||
(namefun (if (relative-pathname-p string)
|
||||
#'namestring
|
||||
(lambda (x) (namestring (merge-pathnames x))))))
|
||||
(unless (and (underlying-directory-p dir)
|
||||
(not (wild-pathname-p dir)))
|
||||
(return-from directory-complete (values nil 0)))
|
||||
(with-directory-iterator (next dir)
|
||||
(when-let* ((all (loop
|
||||
for entry = (next)
|
||||
while entry collect
|
||||
(funcall namefun entry)))
|
||||
(re (text-utils:strcat "^" string))
|
||||
(candidates (sort (remove-if-not (lambda (a) (cl-ppcre:scan re a))
|
||||
all)
|
||||
(lambda (a b) (< (length a)
|
||||
(length b))))))
|
||||
(values candidates
|
||||
(reduce-to-common-prefix candidates))))))
|
||||
|
||||
(defun starts-with-clsr (hint)
|
||||
(lambda (a)
|
||||
(cl-ppcre:scan (text-utils:strcat "^" hint) a)))
|
||||
|
||||
(defun folder-complete (hint)
|
||||
"Virtual messages folder in db not filesystem directory"
|
||||
(when-let ((matching-folders (remove-if-not (starts-with-clsr hint)
|
||||
(db:all-folders))))
|
||||
(values matching-folders
|
||||
(reduce-to-common-prefix matching-folders))))
|
||||
|
||||
(defun timeline-complete-fn (folder)
|
||||
"Complete a messages timeline prefix"
|
||||
(lambda (hint)
|
||||
(let* ((all-timelines (if folder
|
||||
(db:all-timelines-in-folder folder
|
||||
:include-default-timelines t)
|
||||
(db:default-timelines)))
|
||||
(matching-timelines (remove-if-not (starts-with-clsr hint)
|
||||
all-timelines)))
|
||||
(values matching-timelines
|
||||
(reduce-to-common-prefix matching-timelines)))))
|
||||
|
||||
(defmacro with-simple-complete (function-name all-choices-list-fn)
|
||||
"Generate a complete function using function-name to build the name
|
||||
the function and `all-choices-list-fn' as a function that returns a
|
||||
list af all possible candidtae for completion."
|
||||
(with-gensyms (matched)
|
||||
`(defun ,(misc:format-fn-symbol t "~a" function-name) (hint)
|
||||
(when-let ((,matched (remove-if-not (starts-with-clsr hint)
|
||||
(funcall (function ,all-choices-list-fn)))))
|
||||
(values ,matched
|
||||
(reduce-to-common-prefix ,matched))))))
|
||||
|
||||
(with-simple-complete ignored-username-complete db:all-ignored-usernames)
|
||||
|
||||
(with-simple-complete username-complete db:all-usernames)
|
||||
|
||||
(with-simple-complete visibility-complete (lambda () swconf:*allowed-status-visibility*))
|
||||
|
||||
(with-simple-complete unfollowed-user-complete
|
||||
(lambda () (db:all-unfollowed-usernames :remove-ignored t)))
|
||||
|
||||
(with-simple-complete followed-user-complete db:all-followed-usernames)
|
||||
|
||||
(with-simple-complete tags-complete (lambda ()
|
||||
(mapcar #'db:tag->folder-name
|
||||
(db:all-subscribed-tags-name))))
|
||||
|
||||
(with-simple-complete conversation-folder db:all-conversation-folders)
|
|
@ -0,0 +1,98 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package conditions)
|
||||
|
||||
(defmacro defcond (type)
|
||||
`(define-condition ,(alexandria:format-symbol t "TEXT-~a" (string-upcase type))
|
||||
(,type)
|
||||
((text
|
||||
:initarg :text
|
||||
:reader text))
|
||||
(:documentation "Error that set text")))
|
||||
|
||||
(defcond error)
|
||||
|
||||
(defcond warning)
|
||||
|
||||
(define-condition not-implemented-error (text-error)
|
||||
()
|
||||
(:documentation "Error for not-implemented features"))
|
||||
|
||||
(define-condition null-reference (text-error)
|
||||
()
|
||||
(:documentation "Null reference"))
|
||||
|
||||
(define-condition out-of-bounds (error)
|
||||
((seq
|
||||
:initarg :seq
|
||||
:reader seq)
|
||||
(idx
|
||||
:initarg :idx
|
||||
:reader idx))
|
||||
(:documentation "Error when you go out of bound"))
|
||||
|
||||
(define-condition length-error (text-error)
|
||||
((seq
|
||||
:initarg :seq
|
||||
:reader seq))
|
||||
(:documentation "Length error"))
|
||||
|
||||
(define-condition different-length-error (error)
|
||||
((seq1
|
||||
:initarg :seq1
|
||||
:reader seq1)
|
||||
(seq2
|
||||
:initarg :seq2
|
||||
:reader seq2))
|
||||
(:report (lambda (condition stream)
|
||||
(format stream "~a ~a" (seq1 condition) (seq2 condition))))
|
||||
(:documentation "Length error"))
|
||||
|
||||
(define-condition column-not-found (error)
|
||||
((table
|
||||
:initform (_ "unknown")
|
||||
:initarg :table
|
||||
:reader table)
|
||||
(row
|
||||
:initform (_ "unknown")
|
||||
:initarg :row
|
||||
:reader row)
|
||||
(column
|
||||
:initarg :column
|
||||
:reader column))
|
||||
(:report (lambda (condition stream)
|
||||
(format stream
|
||||
"table ~s column ~s row ~s"
|
||||
(table condition)
|
||||
(column condition)
|
||||
(row condition))))
|
||||
(:documentation "Condition signalled when a database column does exists in table."))
|
||||
|
||||
(define-condition command-not-found (error)
|
||||
((command
|
||||
:initarg :command
|
||||
:reader command))
|
||||
(:report (lambda (condition stream)
|
||||
(format stream "~s" (command condition))))
|
||||
(:documentation "Condition signalled when a command the user inputed
|
||||
was not found in keybindigs tree."))
|
||||
|
||||
(defmacro with-default-on-error ((default) &body body)
|
||||
"Well i think it is the same as `ignore-error'"
|
||||
`(handler-case
|
||||
(progn ,@body)
|
||||
(error () ,default)))
|
|
@ -0,0 +1,45 @@
|
|||
;; tinmop: an a mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(defmacro with-return-untranslated ((untranslated) &body body)
|
||||
`(handler-bind ((i18n-conditions:no-translation-table-error
|
||||
(lambda (e)
|
||||
(declare (ignore e))
|
||||
(invoke-restart 'cl-i18n:return-untranslated))))
|
||||
(handler-case
|
||||
(progn ,@body)
|
||||
(i18n-conditions:no-translation (e)
|
||||
(declare (ignorable e))
|
||||
#+debug-mode
|
||||
(progn
|
||||
(warn e)
|
||||
,untranslated)
|
||||
#-debug-mode ,untranslated))))
|
||||
|
||||
(defun _ (a)
|
||||
"get translated string"
|
||||
(with-return-untranslated (a)
|
||||
(cl-i18n:translate a)))
|
||||
|
||||
(defun n_ (a b n)
|
||||
"Get stranslated string with plural forms
|
||||
- a the untranslated string template
|
||||
- b the string template to return if no translation was found
|
||||
- n the number of object mentioned in the string template"
|
||||
(declare (ignore b))
|
||||
(with-return-untranslated (a)
|
||||
(cl-i18n:ntranslate a a n)))
|
|
@ -0,0 +1,78 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :constants)
|
||||
|
||||
(define-constant +help-about-message-template+
|
||||
"~a
|
||||
Copyright (C) 2019 cage
|
||||
|
||||
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. "
|
||||
:test #'string=)
|
||||
|
||||
(define-constant +http-code-ok+ 200 :test #'=)
|
||||
|
||||
(define-constant +mime-type-jpg+ "image/jpeg" :test #'string=)
|
||||
|
||||
(define-constant +mime-type-png+ "image/png" :test #'string=)
|
||||
|
||||
(define-constant +mime-type-html+ "text/html" :test #'string=)
|
||||
|
||||
(define-constant +db-file+ "db.sqlite3" :test #'string=
|
||||
:documentation "the filename of the database")
|
||||
|
||||
(define-constant +json-true+ "true" :test #'string=)
|
||||
|
||||
(define-constant +json-false+ "false" :test #'string=)
|
||||
|
||||
(define-constant +fps+ 20 :test #'=
|
||||
:documentation "The redraw frequency in frame per second")
|
||||
|
||||
(define-constant +command-window-height+ 1 :test #'=)
|
||||
|
||||
(define-constant +starting-init-file+ "init.lisp" :test #'string=)
|
||||
|
||||
(define-constant +box-height-diff+ 3 :test #'=
|
||||
:documentation "When fitting columns of text in a box
|
||||
remove this rows from total height")
|
||||
|
||||
(define-constant +default-command-prompt+ "> " :test #'string=)
|
||||
|
||||
(define-constant +menu-button-ok+ "OK" :test #'string=)
|
||||
|
||||
(define-constant +status-public-visibility+ "public" :test #'string=)
|
||||
|
||||
(define-constant +status-direct-visibility+ "direct" :test #'string=)
|
||||
|
||||
(define-constant +folder-tag-prefix+ "#" :test #'string=
|
||||
:documentation "The prefix for messages hashtag")
|
||||
|
||||
(define-constant +folder-direct-message-prefix+ "@" :test #'string=
|
||||
:documentation "The prefix for direct message folder, unused")
|
||||
|
||||
(define-constant +mention-prefix+ "@" :test #'string=
|
||||
:documentation "The prefix for a mention in a message")
|
|
@ -0,0 +1,162 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :conversations-window)
|
||||
|
||||
(defclass conversations-window (focus-marked-window
|
||||
simple-line-navigation-window
|
||||
title-window
|
||||
border-window)
|
||||
((read-message-fg
|
||||
:initarg :read-message-fg
|
||||
:initform nil
|
||||
:accessor read-message-fg
|
||||
:documentation "Read message foreground color")
|
||||
(read-message-bg
|
||||
:initarg :read-message-bg
|
||||
:initform nil
|
||||
:accessor read-message-bg
|
||||
:documentation "Read message background color")
|
||||
(unread-message-fg
|
||||
:initarg :unread-message-fg
|
||||
:initform nil
|
||||
:accessor unread-message-fg
|
||||
:documentation "Unread message foreground color")
|
||||
(unread-message-bg
|
||||
:initarg :unread-message-bg
|
||||
:initform nil
|
||||
:accessor unread-message-bg
|
||||
:documentation "unread message background color"))
|
||||
(:documentation "The window that shows active conversation"))
|
||||
|
||||
(defmethod refresh-config :after ((object conversations-window))
|
||||
(with-accessors ((croatoan-window croatoan-window)
|
||||
(histogram-fg histogram-fg)
|
||||
(read-message-fg read-message-fg)
|
||||
(read-message-bg read-message-bg)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)
|
||||
(unread-message-fg unread-message-fg)
|
||||
(unread-message-bg unread-message-bg)) object
|
||||
(let* ((theme-style (swconf:form-style swconf:+key-conversations-window+))
|
||||
(fg (swconf:foreground theme-style))
|
||||
(bg (swconf:background theme-style))
|
||||
(selected-fg (swconf:selected-foreground theme-style))
|
||||
(selected-bg (swconf:selected-background theme-style))
|
||||
(width (- (win-width *main-window*)
|
||||
(win-width *thread-window*)))
|
||||
(y (win-height *tags-window*))
|
||||
(height (- (win-height *main-window*)
|
||||
(win-height *command-window*)
|
||||
(win-height *tags-window*)))
|
||||
(x 0))
|
||||
(multiple-value-bind (fg-read bg-read)
|
||||
(swconf:conversation-window-read-colors)
|
||||
(multiple-value-bind (fg-unread bg-unread)
|
||||
(swconf:conversation-window-unread-colors)
|
||||
(setf read-message-bg bg-read)
|
||||
(setf read-message-fg fg-read)
|
||||
(setf unread-message-bg bg-unread)
|
||||
(setf unread-message-fg fg-unread)
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background bg))
|
||||
(setf (bgcolor croatoan-window) bg)
|
||||
(setf (fgcolor croatoan-window) fg)
|
||||
(setf selected-line-fg selected-fg)
|
||||
(setf selected-line-bg selected-bg)
|
||||
(win-resize object width height)
|
||||
(win-move object x y)
|
||||
object)))))
|
||||
|
||||
(defmethod draw :before ((object conversations-window))
|
||||
(with-accessors ((rows rows)
|
||||
(single-row-height single-row-height)
|
||||
(top-row-padding top-row-padding)
|
||||
(read-message-fg read-message-fg)
|
||||
(read-message-bg read-message-bg)
|
||||
(unread-message-fg unread-message-fg)
|
||||
(unread-message-bg unread-message-bg)) object
|
||||
(win-clear object)
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(loop
|
||||
for y from (+ 2 top-row-padding) by single-row-height
|
||||
for row-fields in (mapcar #'fields rows) do
|
||||
(let ((attributes-to-read (if (= (db:messages-to-read row-fields)
|
||||
0)
|
||||
(attribute-dim)
|
||||
(attribute-bold))))
|
||||
(print-text object
|
||||
(text-utils:to-s (db:messages-to-read row-fields))
|
||||
1 y
|
||||
:bgcolor unread-message-bg
|
||||
:fgcolor unread-message-fg
|
||||
:attributes attributes-to-read)
|
||||
(print-text object "/" nil nil)
|
||||
(print-text object
|
||||
(text-utils:to-s (+ (db:messages-to-read row-fields)
|
||||
(db:messages-red row-fields)))
|
||||
nil nil
|
||||
:bgcolor read-message-bg
|
||||
:fgcolor read-message-fg))))))
|
||||
|
||||
|
||||
(defmethod resync-rows-db ((object conversations-window) &key
|
||||
(redraw t)
|
||||
(suggested-message-index nil))
|
||||
"Resync this window ehit he conversation in database, if
|
||||
`suggested-message-index' is not nil masrks as selected the message in
|
||||
position indicated by this variable."
|
||||
(with-accessors ((rows rows)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)) object
|
||||
(flet ((make-rows (line-fields bg fg)
|
||||
(mapcar (lambda (fields)
|
||||
(let ((name (db:conversation-name fields)))
|
||||
(make-instance 'line
|
||||
:fields fields
|
||||
:normal-text name
|
||||
:selected-text name
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg)))
|
||||
line-fields)))
|
||||
(let ((line-fields (db:all-conversation-stats)))
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(setf rows (make-rows line-fields
|
||||
selected-line-bg
|
||||
selected-line-fg))
|
||||
(when suggested-message-index
|
||||
(select-row object suggested-message-index))
|
||||
(when redraw
|
||||
(draw object)))))))
|
||||
|
||||
(defun init ()
|
||||
"Init the window"
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *conversations-window*
|
||||
(make-instance 'conversations-window
|
||||
:title (_ "Conversations")
|
||||
:single-row-height 3
|
||||
:uses-border-p t
|
||||
:keybindings keybindings:*conversations-keymap*
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *conversations-window*)
|
||||
(resync-rows-db *conversations-window* :redraw nil)
|
||||
(when (rows *conversations-window*)
|
||||
(select-row *conversations-window* 0))
|
||||
(draw *conversations-window*)
|
||||
*conversations-window*))
|
|
@ -0,0 +1,93 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :crypto-utils)
|
||||
|
||||
(define-constant +crypto-data-prefix+ "CRYPTO:" :test #'string=)
|
||||
|
||||
(define-constant +crypto-message-field-separator+ ":" :test #'string=)
|
||||
|
||||
(define-constant +crypto-data-prefix-re+ (text-utils:strcat "^" +crypto-data-prefix+)
|
||||
:test #'string=
|
||||
:documentation "The encrypted text must starts with this prefix")
|
||||
|
||||
(defun crypto-text-p (data)
|
||||
"Non nil if data starts with `+crypto-data-prefix+'"
|
||||
(scan +crypto-data-prefix-re+ data))
|
||||
|
||||
(defun add-crypto-prefix (data)
|
||||
(text-utils:strcat +crypto-data-prefix+ data))
|
||||
|
||||
(defun strip-crypto-prefix (data)
|
||||
(misc:safe-subseq data (length +crypto-data-prefix+)))
|
||||
|
||||
(defun decode-key (key)
|
||||
(base64:base64-string-to-usb8-array key))
|
||||
|
||||
(defun encode-key (key)
|
||||
(base64:usb8-array-to-base64-string key))
|
||||
|
||||
(defun decode-iv (iv)
|
||||
(base64:base64-string-to-usb8-array iv))
|
||||
|
||||
(defun encode-iv (iv)
|
||||
(base64:usb8-array-to-base64-string iv))
|
||||
|
||||
(defun generate-key (&optional (length 32))
|
||||
(with-open-file (stream "/dev/urandom" :element-type '(unsigned-byte 8))
|
||||
(let ((data (misc:make-fresh-array length 0 '(unsigned-byte 8) t)))
|
||||
(read-sequence data stream)
|
||||
(encode-key data))))
|
||||
|
||||
(defun encrypt (data key)
|
||||
"Encrypt `data' with `key', note that the initialization vector is autogenerated."
|
||||
(multiple-value-bind (encrypted-text x y z iv)
|
||||
(cryptos:encrypt data
|
||||
(decode-key key)
|
||||
:mode :cbc
|
||||
:cipher :aes)
|
||||
(declare (ignore x y z))
|
||||
(values encrypted-text
|
||||
(encode-iv iv))))
|
||||
|
||||
(defun decrypt (data key iv)
|
||||
"Decrypt `data' with `key' and iv (initialization vector)."
|
||||
(cryptos:decrypt data
|
||||
(decode-key key)
|
||||
:iv (decode-iv iv)
|
||||
:mode :cbc
|
||||
:cipher :aes))
|
||||
|
||||
(defun encrypt-message (data key)
|
||||
"encrypt a message and wrap it in a valid text to be sent by the
|
||||
program (add prefix, add iv, separates fields etc.)"
|
||||
(multiple-value-bind (encrypted-text iv)
|
||||
(encrypt data key)
|
||||
(text-utils:strcat +crypto-data-prefix+
|
||||
iv
|
||||
+crypto-message-field-separator+
|
||||
encrypted-text)))
|
||||
|
||||
(defun decrypt-message (encrypted-message key)
|
||||
"Extract iv and actual data from `encrypted-message' and try to
|
||||
decrypt the latter with key."
|
||||
(let* ((raw (strip-crypto-prefix encrypted-message))
|
||||
(iv-mesg (split +crypto-message-field-separator+ raw))
|
||||
(iv (first iv-mesg))
|
||||
(encrypted-body (second iv-mesg))
|
||||
(decrypted (decrypt encrypted-body key iv)))
|
||||
decrypted))
|
|
@ -0,0 +1,68 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :date-formatter)
|
||||
|
||||
;; FORMAT := (FIELD | TEXT)*
|
||||
;; FIELD := [%hour %minute %second %month %year %day %weekday %%]
|
||||
;; TEXT := (not percent)+
|
||||
|
||||
(defrule field
|
||||
(or "%hour"
|
||||
"%min"
|
||||
"%second"
|
||||
"%month"
|
||||
"%year"
|
||||
"%day"
|
||||
"%weekday"
|
||||
"%short-weekday"
|
||||
"%long-weekday"
|
||||
"%long-month"
|
||||
"%short-month"
|
||||
"%%")
|
||||
(:text t))
|
||||
|
||||
(defrule text (+ (not percent))
|
||||
(:text t))
|
||||
|
||||
(defrule format (* (or field text)))
|
||||
|
||||
(defun expand-date-formatter-spec (spec)
|
||||
"Expand a date spec like '%year %short-month %day %hour:%min'
|
||||
to a list like '(:year :dhort-month :day :hour \":\" (:min 2))
|
||||
|
||||
note that:
|
||||
- %% expands to \"%\"
|
||||
- %min expands to (:min 2)
|
||||
- %hour expands to (:hour 2)
|
||||
- %day expands to (:day 2)
|
||||
|
||||
This list can be passed to misc:format-time to get a time string"
|
||||
(let ((parsed (parse 'format spec)))
|
||||
(loop for element in parsed collect
|
||||
(cond
|
||||
((string= element "%%")
|
||||
"%")
|
||||
((string= element "%min")
|
||||
'(:min 2))
|
||||
((string= element "%hour")
|
||||
'(:hour 2))
|
||||
((string= element "%day")
|
||||
'(:day 2))
|
||||
((scan "^%+" element)
|
||||
(make-keyword (string-upcase (subseq element 1))))
|
||||
(t
|
||||
element)))))
|
|
@ -0,0 +1,445 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
;; derived from:
|
||||
|
||||
;; niccolo': a chemicals inventory
|
||||
;; Copyright (C) 2016 Universita' degli Studi di Palermo
|
||||
|
||||
;; 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 3 of the
|
||||
;; License, or (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :db-utils)
|
||||
|
||||
(define-constant +db-invalid-id-number+ 0 :test #'=)
|
||||
|
||||
(define-constant +characters-trouble-name+ '(#\-) :test #'equalp)
|
||||
|
||||
(define-constant +separator-re+ "\\." :test #'equalp)
|
||||
|
||||
(define-constant +separator+ "." :test #'equalp)
|
||||
|
||||
(define-constant +column-wildcard+ "*" :test #'equalp)
|
||||
|
||||
(define-constant +directive-no-journaling+ "PRAGMA journal_mode = MEMORY" :test #'string=)
|
||||
|
||||
(define-constant +directive-no-sync-os+ "PRAGMA synchronous = OFF" :test #'string=)
|
||||
|
||||
(define-constant +directive-foreign-keys+ "PRAGMA foreign_keys = ON" :test #'string=)
|
||||
|
||||
(define-constant +directive-foreign-keys-off+ "PRAGMA foreign_keys = OFF" :test #'string=)
|
||||
|
||||
(define-constant +sqlite3-db-scheme-table+ :sqlite_master :test #'eq)
|
||||
|
||||
(define-constant +sqlite3-db-scheme-table-type+ "table" :test #'string=)
|
||||
|
||||
(define-constant +sqlite3-db-scheme-type+ :type :test #'eq)
|
||||
|
||||
(define-constant +sqlite3-db-scheme-table-name+ :tbl_name :test #'eq)
|
||||
|
||||
(defmacro with-disabled-foreign (&body body)
|
||||
`(unwind-protect
|
||||
(progn
|
||||
(query-low-level +directive-foreign-keys-off+)
|
||||
,@body)
|
||||
(query-low-level +directive-foreign-keys+)))
|
||||
|
||||
(defparameter *connection* nil)
|
||||
|
||||
(defmacro with-db-transaction (&body body)
|
||||
`(sqlite:with-transaction *connection*
|
||||
,@body))
|
||||
|
||||
(defun connectedp ()
|
||||
"Non nil if the connection to db is alive"
|
||||
*connection*)
|
||||
|
||||
(defun close-db ()
|
||||
"Close the connection to database"
|
||||
(when (connectedp)
|
||||
(sqlite:disconnect *connection*)))
|
||||
|
||||
(defgeneric quote-symbol (s))
|
||||
|
||||
(defmethod quote-symbol ((s string))
|
||||
"Quote `s' to be usable as column name in database (e.g. \"a-b\" -> \\\"a-b\\\")"
|
||||
(if (scan +separator-re+ s)
|
||||
(let* ((splitted (split +separator-re+ s))
|
||||
(res (flatten (loop for i in splitted collect
|
||||
(if (string= i +column-wildcard+)
|
||||
i
|
||||
(format nil "\"~a\"" i))))))
|
||||
(join-with-strings res +separator+))
|
||||
(if (null (every #'(lambda (a) (null (find a s)))
|
||||
+characters-trouble-name+))
|
||||
(format nil "\"~(~a~)\"" s)
|
||||
(format nil "~(~a~)" s))))
|
||||
|
||||
(defmethod quote-symbol ((s symbol))
|
||||
(quote-symbol (symbol-name s)))
|
||||
|
||||
(defun prepare-query (sql)
|
||||
"Compile a query in a format suitable to be executed"
|
||||
#+debug-mode (misc:dbg "compiling ~a~%" sql)
|
||||
(sqlite:prepare-statement *connection* sql))
|
||||
|
||||
(defun execute-query (prepared-sql &optional (parameters nil))
|
||||
"Execute the prepared query with parameter `parameters'"
|
||||
(let* ((columns-name (mapcar (lambda (a) (make-keyword (string-upcase a)))
|
||||
(sqlite:statement-column-names prepared-sql))))
|
||||
(loop
|
||||
for param in parameters
|
||||
for i from 1 do
|
||||
(sqlite:bind-parameter prepared-sql i param))
|
||||
(let ((res (loop while (sqlite:step-statement prepared-sql) collect
|
||||
(loop
|
||||
for i from 0
|
||||
for column-name in columns-name
|
||||
append
|
||||
(list column-name (sqlite:statement-column-value prepared-sql i))))))
|
||||
(sqlite:finalize-statement prepared-sql)
|
||||
res)))
|
||||
|
||||
(defun fetch-all (executed-query)
|
||||
"Fetch all rows from an executed query"
|
||||
executed-query)
|
||||
|
||||
(defun fetch (executed-query)
|
||||
"Fetch a single row from an executed query"
|
||||
(first executed-query))
|
||||
|
||||
(defun query-low-level (sql &optional (parameters nil))
|
||||
"prepare and Execute a text in SQL format"
|
||||
#+debug-mode (misc:dbg "sql ~a parameters ~a~%" sql parameters)
|
||||
(execute-query (prepare-query sql) parameters))
|
||||
|
||||
(defun query (q)
|
||||
"Execute a sxql query (i.e. sql in s-expression format)"
|
||||
(multiple-value-bind (sql params)
|
||||
(sxql:yield q)
|
||||
(query-low-level sql params)))
|
||||
|
||||
(defun query->sql (q)
|
||||
"Convert sxql to SQL code"
|
||||
(sxql:yield q))
|
||||
|
||||
(defmacro do-rows ((row res) table &body body)
|
||||
"Iterate each row af a list of lists"
|
||||
`(let ((,res ,table))
|
||||
(loop for ,row from 0 below (length ,res) do ,@body)
|
||||
,res))
|
||||
|
||||
(defun prepare-for-sql-like (s)
|
||||
"Prepare s as an argument for LIKE SQL clause"
|
||||
(if (not (text-utils:string-empty-p s))
|
||||
(format nil "%~a%" s)
|
||||
"%"))
|
||||
|
||||
(defmacro object-exists-in-db-p (table clause)
|
||||
`(fetch (query (select :*
|
||||
(from ,table)
|
||||
(where ,clause)))))
|
||||
|
||||
(defmacro object-count-in-db (table clause)
|
||||
`(second (fetch (query (select ((:count :*))
|
||||
(from ,table)
|
||||
(where ,clause))))))
|
||||
|
||||
(defgeneric db-nil-p (a)
|
||||
(:documentation "Non nil if the column can be considered a null value in lisp
|
||||
example:
|
||||
|
||||
:nil -> T
|
||||
\"false\" -> T
|
||||
0 -> T
|
||||
\"0\" -> T
|
||||
\"no\" -> T
|
||||
\"null\" -> T
|
||||
"))
|
||||
|
||||
(defmethod db-nil-p ((a null))
|
||||
t)
|
||||
|
||||
(defmethod db-nil-p ((a symbol))
|
||||
(eq a :nil))
|
||||
|
||||
(defmethod db-nil-p ((a string))
|
||||
(or (string-empty-p a)
|
||||
(string-equal a "false")
|
||||
(string-equal a "null")
|
||||
(string-equal a "nil")
|
||||
(string-equal a "no")
|
||||
(string-equal a "0")))
|
||||
|
||||
(defmethod db-nil-p ((a number))
|
||||
(num:epsilon= a 0.0))
|
||||
|
||||
(defun db-not-nil-p (a)
|
||||
(not (db-nil-p a)))
|
||||
|
||||
(defun db-getf (row indicator &optional (default nil))
|
||||
"Try to find a value in a `row' (modeled as a plist), return
|
||||
`default' if indicator has a value of nil in row and signal a
|
||||
`conditions:column-not-found' if `indicator' does not exists in
|
||||
`row'."
|
||||
(let ((res (getf row indicator :not-found)))
|
||||
(cond
|
||||
((eq res :not-found)
|
||||
(error 'conditions:column-not-found :column indicator :row row))
|
||||
((db-nil-p res)
|
||||
default)
|
||||
(t
|
||||
res))))
|
||||
|
||||
(defmacro if-db-nil-else (expr else)
|
||||
`(if (not (db-nil-p ,expr))
|
||||
,expr
|
||||
,else))
|
||||
|
||||
(defun count-all (table)
|
||||
(getf (first (fetch-all (query (select ((:as (:count :*) :ct))
|
||||
(from table)))))
|
||||
:ct))
|
||||
|
||||
(defun db-path ()
|
||||
(uiop:unix-namestring (concatenate 'string
|
||||
(res:home-datadir)
|
||||
"/"
|
||||
+db-file+)))
|
||||
|
||||
(defun init-connection ()
|
||||
"Initialize a db connection (and create db file if does not exists)"
|
||||
(when (not (fs:file-exists-p (db-path)))
|
||||
(fs:create-file (db-path)))
|
||||
(setf *connection* (sqlite:connect (db-path))))
|
||||
|
||||
(defmacro with-ready-database ((&key (connect t)) &body body)
|
||||
"Ensure a valid connection to db exists, if `connect' is non
|
||||
nil (default T), start a new connection"
|
||||
`(let ((sxql:*sql-symbol-conversion* #'db-utils:quote-symbol))
|
||||
(when ,connect
|
||||
(init-connection)
|
||||
(query-low-level +directive-no-journaling+)
|
||||
(query-low-level +directive-no-sync-os+)
|
||||
(query-low-level +directive-foreign-keys+))
|
||||
(db:maybe-build-all-tables)
|
||||
(progn ,@body)))
|
||||
|
||||
(defun local-time-obj-now ()
|
||||
(local-time:now))
|
||||
|
||||
; db -> application
|
||||
(defun encode-datetime-string (d &optional (fallback nil))
|
||||
"Encode a datetime string from db"
|
||||
(handler-case
|
||||
(local-time:parse-timestring d)
|
||||
(error () fallback)))
|
||||
|
||||
;; application -> db
|
||||
(defgeneric decode-datetime-string (object)
|
||||
(:documentation "Decode object from application to a datetime format
|
||||
suitable for database."))
|
||||
|
||||
(defmethod decode-datetime-string ((object (eql nil)))
|
||||
"")
|
||||
|
||||
(defmethod decode-datetime-string ((object local-time:timestamp))
|
||||
(local-time:format-rfc3339-timestring nil object))
|
||||
|
||||
(defmethod decode-datetime-string ((object string))
|
||||
(decode-datetime-string (encode-datetime-string object)))
|
||||
|
||||
(defmethod decode-datetime-string ((object number))
|
||||
(decode-datetime-string (universal-to-timestamp object)))
|
||||
|
||||
(defgeneric decode-date-string (object)
|
||||
(:documentation "Decode object from application to a date format
|
||||
suitable for database."))
|
||||
|
||||
(defmethod decode-date-string ((object (eql nil)))
|
||||
"")
|
||||
|
||||
(defmethod decode-date-string ((object local-time:timestamp))
|
||||
(local-time:format-timestring nil object :format '(:year "-" (:month 2) "-"
|
||||
(:day 2))))
|
||||
|
||||
(defmethod decode-date-string ((object string))
|
||||
(decode-date-string (encode-datetime-string object)))
|
||||
|
||||
(defmethod decode-date-string ((object number))
|
||||
(decode-date-string (universal-to-timestamp object)))
|
||||
|
||||
(defgeneric decode-time-string (object))
|
||||
|
||||
(defmethod decode-time-string ((object local-time:timestamp))
|
||||
(local-time:format-timestring nil object :format '((:hour 2) ":" (:min 2))))
|
||||
|
||||
(defmethod decode-time-string ((object string))
|
||||
(decode-time-string (encode-datetime-string object)))
|
||||
|
||||
(defun encoded-datetime-year (decoded)
|
||||
(misc:extract-year-from-timestamp (encode-datetime-string decoded)))
|
||||
|
||||
(defmacro make-insert (table-name names values)
|
||||
"Generate an sxql insert statement
|
||||
|
||||
example
|
||||
|
||||
(make-insert :table-name
|
||||
(:col-a :col-b)
|
||||
(value-a value-b))
|
||||
"
|
||||
(assert (= (length names) (length values)))
|
||||
`(insert-into ,table-name
|
||||
(set= ,@(loop
|
||||
for name in names
|
||||
for value in values append
|
||||
(list name value)))))
|
||||
|
||||
(defmacro make-delete (table-name where-clause)
|
||||
"Generate an sxql delete statement
|
||||
|
||||
example
|
||||
|
||||
(make-delete :table-name
|
||||
(:col-a :col-b)
|
||||
(:and (:= col-a 1)
|
||||
(:= col-b 2)))
|
||||
"
|
||||
`(delete-from ,table-name
|
||||
(where ,where-clause)))
|
||||
|
||||
(defmacro make-update (table-name names values where-clause)
|
||||
"Generate an sxql update statement
|
||||
|
||||
example
|
||||
|
||||
(make-delete :table-name
|
||||
(:col-a :col-b)
|
||||
(1 2)
|
||||
(:and (:= col-a 1)
|
||||
(:= col-b 2)))
|
||||
"
|
||||
(assert (= (length names) (length values)))
|
||||
`(update ,table-name
|
||||
(set= ,@(loop
|
||||
for name in names
|
||||
for value in values append
|
||||
(list name value)))
|
||||
(where ,where-clause)))
|
||||
|
||||
(defun get-max-id (table)
|
||||
(or (second (fetch (query (select (fields (:max :id)) (from table)))))
|
||||
|
||||
0))
|
||||
|
||||
(defun get-min-id (table)
|
||||
(or (second (fetch (query (select (fields (:min :id)) (from table)))))
|
||||
|
||||
0))
|
||||
|
||||
(defun decode-blob (blob)
|
||||
(and blob
|
||||
(base64:usb8-array-to-base64-string blob)))
|
||||
|
||||
(defun rows->tsv (rows)
|
||||
(with-output-to-string (stream)
|
||||
(labels ((%escape (s)
|
||||
(regex-replace-all "\"" s "\"\""))
|
||||
(%fmt (tpl &rest args)
|
||||
(apply #'format
|
||||
stream
|
||||
(strcat tpl (coerce '(#\return #\linefeed) 'string))
|
||||
args))
|
||||
(%join (s)
|
||||
(join-with-strings s (string #\tab)))
|
||||
(%wrap (s)
|
||||
(wrap-with (%escape (to-s s)) "\""))
|
||||
(%filter-print (filter-fn row)
|
||||
(%join (mapcar #'%wrap
|
||||
(remove-if-not filter-fn row))))
|
||||
(%filter-header (a)
|
||||
(and (symbolp a)
|
||||
(not (eq :nil a))))
|
||||
(%filter-data (a)
|
||||
(cond
|
||||
((null a)
|
||||
t)
|
||||
((and (symbolp a)
|
||||
(not (eq :nil a)))
|
||||
nil)
|
||||
(t t))))
|
||||
(%fmt (%filter-print #'%filter-header (first-elt rows)))
|
||||
(loop for row in rows do
|
||||
(%fmt (%filter-print #'%filter-data row)
|
||||
:stream stream)))))
|
||||
|
||||
(defun table-exists-p (table-name)
|
||||
(fetch (query (select :*
|
||||
(from +sqlite3-db-scheme-table+)
|
||||
(where (:and (:= +sqlite3-db-scheme-table-name+ (quote-symbol table-name))
|
||||
(:= +sqlite3-db-scheme-type+ +sqlite3-db-scheme-table-type+)))))))
|
||||
|
||||
(defgeneric prepare-for-db (object &key &allow-other-keys)
|
||||
(:documentation "Prepare object to be inserted into database"))
|
||||
|
||||
(defmethod prepare-for-db (object &key (to-integer nil) &allow-other-keys)
|
||||
"Note that object is ignored for unspecialized method"
|
||||
(if to-integer
|
||||
1
|
||||
object))
|
||||
|
||||
(defmethod prepare-for-db ((object (eql t)) &key (to-integer nil) &allow-other-keys)
|
||||
"Note that object is ignored for unspecialized method"
|
||||
(declare (ignore object))
|
||||
(if to-integer
|
||||
1
|
||||
t))
|
||||
|
||||
(defmethod prepare-for-db ((object null)
|
||||
&key
|
||||
(to-integer nil)
|
||||
&allow-other-keys)
|
||||
(declare (ignorable object))
|
||||
(if to-integer
|
||||
0
|
||||
""))
|
||||
|
||||
(defmethod prepare-for-db ((object symbol) &key &allow-other-keys)
|
||||
(symbol-name object))
|
||||
|
||||
(defmethod prepare-for-db ((object string) &key &allow-other-keys)
|
||||
object)
|
||||
|
||||
(defmethod prepare-for-db ((object sequence) &key &allow-other-keys)
|
||||
(map 'list #'prepare-for-db object))
|
||||
|
||||
(defmethod prepare-for-db ((object local-time:timestamp) &key &allow-other-keys)
|
||||
(decode-datetime-string object))
|
||||
|
||||
(defun last-inserted-rowid ()
|
||||
"Maximum value of a primary key of a table so far"
|
||||
(sqlite:last-insert-rowid *connection*))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,960 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :emoji-shortcodes)
|
||||
|
||||
(defparameter *shortcodes-db*
|
||||
'((":brown_square:" . "🟫")
|
||||
(":purple_square:" . "🟪")
|
||||
(":blue_square:" . "🟦")
|
||||
(":green_square:" . "🟩")
|
||||
(":yellow_square:" . "🟨")
|
||||
(":orange_square:" . "🟧")
|
||||
(":red_square:" . "🟥")
|
||||
(":brown_circle:" . "🟤")
|
||||
(":purple_circle:" . "🟣")
|
||||
(":green_circle:" . "🟢")
|
||||
(":yellow_circle:" . "🟡")
|
||||
(":orange_circle:" . "🟠")
|
||||
(":razor:" . "🪒")
|
||||
(":chair:" . "🪑")
|
||||
(":stethoscope:" . "🩺")
|
||||
(":bandaid:" . "🩹")
|
||||
(":blood_drop:" . "🩸")
|
||||
(":probing_cane:" . "🦯")
|
||||
(":axe:" . "🪓")
|
||||
(":diya_lamp:" . "🪔")
|
||||
(":banjo:" . "🪕")
|
||||
(":ballet_shoes:" . "🩰")
|
||||
(":shorts:" . "🩳")
|
||||
(":briefs:" . "🩲")
|
||||
(":one_piece_swimsuit:" . "🩱")
|
||||
(":sari:" . "🥻")
|
||||
(":safety_vest:" . "🦺")
|
||||
(":kite:" . "🪁")
|
||||
(":yoyo:" . "🪀")
|
||||
(":diving_mask:" . "🤿")
|
||||
(":ringed_planet:" . "🪐")
|
||||
(":parachute:" . "🪂")
|
||||
(":auto_rickshaw:" . "🛺")
|
||||
(":motor_wheelchair:" . "🦼")
|
||||
(":wheelchair:" . "🦽")
|
||||
(":hindu_temple:" . "🛕")
|
||||
(":ice:" . "🧊")
|
||||
(":mate:" . "🧉")
|
||||
(":beverage_box:" . "🧃")
|
||||
(":oyster:" . "🦪")
|
||||
(":butter:" . "🧈")
|
||||
(":falafel:" . "🧆")
|
||||
(":waffle:" . "🧇")
|
||||
(":onion:" . "🧅")
|
||||
(":garlic:" . "🧄")
|
||||
(":flamingo:" . "🦩")
|
||||
(":skunk:" . "🦨")
|
||||
(":otter:" . "🦦")
|
||||
(":sloth:" . "🦥")
|
||||
(":guide_dog:" . "🦮")
|
||||
(":orangutan:" . "🦧")
|
||||
(":person_kneeling:" . "🧎")
|
||||
(":person_standing:" . "🧍")
|
||||
(":person_deaf:" . "🧏")
|
||||
(":hearing_aid:" . "🦻")
|
||||
(":mech_leg:" . "🦿")
|
||||
(":mech_arm:" . "🦾")
|
||||
(":pinch:" . "🤏")
|
||||
(":white_heart:" . "🤍")
|
||||
(":brown_heart:" . "🤎")
|
||||
(":yawn:" . "🥱")
|
||||
(":fire_extinguisher:" . "🧯")
|
||||
(":sponge:" . "🧽")
|
||||
(":soap:" . "🧼")
|
||||
(":toilet_paper:" . "🧻")
|
||||
(":basket:" . "🧺")
|
||||
(":broom:" . "🧹")
|
||||
(":safety_pin:" . "🧷")
|
||||
(":lotion:" . "🧴")
|
||||
(":dna:" . "🧬")
|
||||
(":petri_dish:" . "🧫")
|
||||
(":test_tube:" . "🧪")
|
||||
(":magnet:" . "🧲")
|
||||
(":toolbox:" . "🧰")
|
||||
(":receipt:" . "🧾")
|
||||
(":abacus:" . "🧮")
|
||||
(":flat_shoe:" . "🥿")
|
||||
(":hiking_boot:" . "🥾")
|
||||
(":lab_coat:" . "🥼")
|
||||
(":goggles:" . "🥽")
|
||||
(":yarn:" . "🧶")
|
||||
(":spool:" . "🧵")
|
||||
(":teddy_bear:" . "🧸")
|
||||
(":jigsaw:" . "🧩")
|
||||
(":nazar_amulet:" . "🧿")
|
||||
(":lacrosse:" . "🥍")
|
||||
(":flying_disc:" . "🥏")
|
||||
(":softball:" . "🥎")
|
||||
(":red_envelope:" . "🧧")
|
||||
(":firecracker:" . "🧨")
|
||||
(":luggage:" . "🧳")
|
||||
(":skateboard:" . "🛹")
|
||||
(":brick:" . "🧱")
|
||||
(":compass:" . "🧭")
|
||||
(":cupcake:" . "🧁")
|
||||
(":lobster:" . "🦞")
|
||||
(":moon_cake:" . "🥮")
|
||||
(":salt:" . "🧂")
|
||||
(":bagel:" . "🥯")
|
||||
(":leafy_green:" . "🥬")
|
||||
(":mango:" . "🥭")
|
||||
(":microbe:" . "🦠")
|
||||
(":mosquito:" . "🦟")
|
||||
(":parrot:" . "🦜")
|
||||
(":peacock:" . "🦚")
|
||||
(":swan:" . "🦢")
|
||||
(":badger:" . "🦡")
|
||||
(":kangaroo:" . "🦘")
|
||||
(":hippo:" . "🦛")
|
||||
(":llama:" . "🦙")
|
||||
(":raccoon:" . "🦝")
|
||||
(":bald:" . "🦲")
|
||||
(":white_hair:" . "🦳")
|
||||
(":curly_hair:" . "🦱")
|
||||
(":red_hair:" . "🦰")
|
||||
(":villain:" . "🦹")
|
||||
(":hero:" . "🦸")
|
||||
(":bone:" . "🦴")
|
||||
(":tooth:" . "🦷")
|
||||
(":foot:" . "🦶")
|
||||
(":leg:" . "🦵")
|
||||
(":pleading:" . "🥺")
|
||||
(":partying:" . "🥳")
|
||||
(":woozy:" . "🥴")
|
||||
(":freezing:" . "🥶")
|
||||
(":overheating:" . "🥵")
|
||||
(":love:" . "🥰")
|
||||
(":billed_cap:" . "🧢")
|
||||
(":socks:" . "🧦")
|
||||
(":coat:" . "🧥")
|
||||
(":gloves:" . "🧤")
|
||||
(":scarf:" . "🧣")
|
||||
(":curling_stone:" . "🥌")
|
||||
(":sled:" . "🛷")
|
||||
(":flying_saucer:" . "🛸")
|
||||
(":chopsticks:" . "🥢")
|
||||
(":cup_straw:" . "🥤")
|
||||
(":pie:" . "🥧")
|
||||
(":takeout_box:" . "🥡")
|
||||
(":fortune_cookie:" . "🥠")
|
||||
(":dumpling:" . "🥟")
|
||||
(":canned_food:" . "🥫")
|
||||
(":bowl_spoon:" . "🥣")
|
||||
(":sandwich:" . "🥪")
|
||||
(":cut_of_meat:" . "🥩")
|
||||
(":pretzel:" . "🥨")
|
||||
(":broccoli:" . "🥦")
|
||||
(":coconut:" . "🥥")
|
||||
(":cricket:" . "🦗")
|
||||
(":trex:" . "🦖")
|
||||
(":sauropod:" . "🦕")
|
||||
(":hedgehog:" . "🦔")
|
||||
(":giraffe:" . "🦒")
|
||||
(":zebra:" . "🦓")
|
||||
(":person_lotus_position:" . "🧘")
|
||||
(":person_climbing:" . "🧗")
|
||||
(":person_steamy_room:" . "🧖")
|
||||
(":zombie:" . "🧟")
|
||||
(":genie:" . "🧞")
|
||||
(":elf:" . "🧝")
|
||||
(":merperson:" . "🧜")
|
||||
(":vampire:" . "🧛")
|
||||
(":fairy:" . "🧚")
|
||||
(":mage:" . "🧙")
|
||||
(":breast_feeding:" . "🤱")
|
||||
(":woman_headscarf:" . "🧕")
|
||||
(":older_adult:" . "🧓")
|
||||
(":bearded_person:" . "🧔")
|
||||
(":adult:" . "🧑")
|
||||
(":child:" . "🧒")
|
||||
(":brain:" . "🧠")
|
||||
(":palms_up:" . "🤲")
|
||||
(":love_you_gesture:" . "🤟")
|
||||
(":orange_heart:" . "🧡")
|
||||
(":censored:" . "🤬")
|
||||
(":monocle:" . "🧐")
|
||||
(":shocked:" . "🤯")
|
||||
(":vomiting:" . "🤮")
|
||||
(":contempt:" . "🤨")
|
||||
(":shushing:" . "🤫")
|
||||
(":gasp:" . "🤭")
|
||||
(":crazy:" . "🤪")
|
||||
(":starstruck:" . "🤩")
|
||||
(":shopping_cart:" . "🛒")
|
||||
(":drum:" . "🥁")
|
||||
(":goal:" . "🥅")
|
||||
(":gi:" . "🥋")
|
||||
(":boxing_glove:" . "🥊")
|
||||
(":third_place:" . "🥉")
|
||||
(":second_place:" . "🥈")
|
||||
(":first_place:" . "🥇")
|
||||
(":canoe:" . "🛶")
|
||||
(":stop_sign:" . "🛑")
|
||||
(":scooter:" . "🛴")
|
||||
(":motor_scooter:" . "🛵")
|
||||
(":spoon:" . "🥄")
|
||||
(":tumbler_glass:" . "🥃")
|
||||
(":champagne_glass:" . "🥂")
|
||||
(":milk:" . "🥛")
|
||||
(":squid:" . "🦑")
|
||||
(":shrimp:" . "🦐")
|
||||
(":salad:" . "🥗")
|
||||
(":shallow_pan_of_food:" . "🥘")
|
||||
(":egg:" . "🥚")
|
||||
(":stuffed_flatbread:" . "🥙")
|
||||
(":bacon:" . "🥓")
|
||||
(":pancakes:" . "🥞")
|
||||
(":french_bread:" . "🥖")
|
||||
(":croissant:" . "🥐")
|
||||
(":peanuts:" . "🥜")
|
||||
(":cucumber:" . "🥒")
|
||||
(":carrot:" . "🥕")
|
||||
(":potato:" . "🥔")
|
||||
(":avocado:" . "🥑")
|
||||
(":kiwi:" . "🥝")
|
||||
(":wilted_rose:" . "🥀")
|
||||
(":butterfly:" . "🦋")
|
||||
(":shark:" . "🦈")
|
||||
(":lizard:" . "🦎")
|
||||
(":owl:" . "🦉")
|
||||
(":duck:" . "🦆")
|
||||
(":eagle:" . "🦅")
|
||||
(":bat:" . "🦇")
|
||||
(":rhino:" . "🦏")
|
||||
(":deer:" . "🦌")
|
||||
(":fox_face:" . "🦊")
|
||||
(":gorilla:" . "🦍")
|
||||
(":person_juggling:" . "🤹")
|
||||
(":person_handball:" . "🤾")
|
||||
(":person_water_polo:" . "🤽")
|
||||
(":people_wrestling:" . "🤼")
|
||||
(":person_cartwheel:" . "🤸")
|
||||
(":person_fencing:" . "🤺")
|
||||
(":man_dancing:" . "🕺")
|
||||
(":mrs_claus:" . "🤶")
|
||||
(":pregnant_woman:" . "🤰")
|
||||
(":man_tuxedo:" . "🤵")
|
||||
(":prince:" . "🤴")
|
||||
(":person_shrugging:" . "🤷")
|
||||
(":person_facepalming:" . "🤦")
|
||||
(":selfie:" . "🤳")
|
||||
(":handshake:" . "🤝")
|
||||
(":right_facing_fist:" . "🤜")
|
||||
(":left_facing_fist:" . "🤛")
|
||||
(":call_me:" . "🤙")
|
||||
(":fingers_crossed:" . "🤞")
|
||||
(":raised_backhand:" . "🤚")
|
||||
(":black_heart:" . "🖤")
|
||||
(":clown:" . "🤡")
|
||||
(":cowboy:" . "🤠")
|
||||
(":sneezing:" . "🤧")
|
||||
(":nauseated:" . "🤢")
|
||||
(":drooling:" . "🤤")
|
||||
(":lying:" . "🤥")
|
||||
(":entertained:" . "🤣")
|
||||
(":black_flag:" . "🏴")
|
||||
(":crossed_flags:" . "🎌")
|
||||
(":triangle_flag:" . "🚩")
|
||||
(":checkered_flag:" . "🏁")
|
||||
(":black_square_button:" . "🔲")
|
||||
(":white_square_button:" . "🔳")
|
||||
(":radio_button:" . "🔘")
|
||||
(":diamond_dot:" . "💠")
|
||||
(":down_red_triangle:" . "🔻")
|
||||
(":up_red_triangle:" . "🔺")
|
||||
(":small_blue_diamond:" . "🔹")
|
||||
(":small_orange_diamond:" . "🔸")
|
||||
(":large_blue_diamond:" . "🔷")
|
||||
(":large_orange_diamond:" . "🔶")
|
||||
(":blue_circle:" . "🔵")
|
||||
(":red_circle:" . "🔴")
|
||||
(":ja_no_vacancy:" . "🈵")
|
||||
(":ja_open_for_business:" . "🈺")
|
||||
(":ja_vacancy:" . "🈳")
|
||||
(":ja_passing_grade:" . "🈴")
|
||||
(":ja_application:" . "🈸")
|
||||
(":ja_acceptable:" . "🉑")
|
||||
(":ja_prohibited:" . "🈲")
|
||||
(":ja_discount:" . "🈹")
|
||||
(":ja_bargain:" . "🉐")
|
||||
(":ja_not_free_of_carge:" . "🈶")
|
||||
(":ja_here:" . "🈁")
|
||||
(":vs:" . "🆚")
|
||||
(":up:" . "🆙")
|
||||
(":sos:" . "🆘")
|
||||
(":ok:" . "🆗")
|
||||
(":ng:" . "🆖")
|
||||
(":new:" . "🆕")
|
||||
(":id:" . "🆔")
|
||||
(":free:" . "🆓")
|
||||
(":cool:" . "🆒")
|
||||
(":cl:" . "🆑")
|
||||
(":ab_blood:" . "🆎")
|
||||
(":abc:" . "🔤")
|
||||
(":symbols:" . "🔣")
|
||||
(":1234:" . "🔢")
|
||||
(":abcd:" . "🔡")
|
||||
(":upper_abcd:" . "🔠")
|
||||
(":ten:" . "🔟")
|
||||
(":white_exclamation:" . "❕")
|
||||
(":white_question:" . "❔")
|
||||
(":double_curly_loop:" . "➿")
|
||||
(":curly_loop:" . "➰")
|
||||
(":division:" . "➗")
|
||||
(":minus:" . "➖")
|
||||
(":plus:" . "➕")
|
||||
(":cross_mark_button:" . "❎")
|
||||
(":x:" . "❌")
|
||||
(":white_check_mark:" . "✅")
|
||||
(":ja_beginner:" . "🔰")
|
||||
(":name_badge:" . "📛")
|
||||
(":trident:" . "🔱")
|
||||
(":mobile_phone_off:" . "📴")
|
||||
(":vibration_mode:" . "📳")
|
||||
(":signal_strength:" . "📶")
|
||||
(":bright:" . "🔆")
|
||||
(":dim:" . "🔅")
|
||||
(":cinema:" . "🎦")
|
||||
(":fast_down_button:" . "⏬")
|
||||
(":down_button:" . "🔽")
|
||||
(":fast_up_button:" . "⏫")
|
||||
(":up_button:" . "🔼")
|
||||
(":repeat_single:" . "🔂")
|
||||
(":repeat:" . "🔁")
|
||||
(":shuffle:" . "🔀")
|
||||
(":ophiuchus:" . "⛎")
|
||||
(":six_pointed_star:" . "🔯")
|
||||
(":menorah:" . "🕎")
|
||||
(":place_of_worship:" . "🛐")
|
||||
(":top:" . "🔝")
|
||||
(":soon:" . "🔜")
|
||||
(":on:" . "🔛")
|
||||
(":end:" . "🔚")
|
||||
(":back:" . "🔙")
|
||||
(":counter_clockwise:" . "🔄")
|
||||
(":clockwise:" . "🔃")
|
||||
(":underage:" . "🔞")
|
||||
(":no_mobile_phones:" . "📵")
|
||||
(":no_pedestrians:" . "🚷")
|
||||
(":non_potable_water:" . "🚱")
|
||||
(":do_not_litter:" . "🚯")
|
||||
(":no_bicycles:" . "🚳")
|
||||
(":no_entry_sign:" . "🚫")
|
||||
(":children_crossing:" . "🚸")
|
||||
(":left_luggage:" . "🛅")
|
||||
(":baggage_claim:" . "🛄")
|
||||
(":customs:" . "🛃")
|
||||
(":passport_control:" . "🛂")
|
||||
(":wc:" . "🚾")
|
||||
(":restroom:" . "🚻")
|
||||
(":potable_water:" . "🚰")
|
||||
(":litter_bin:" . "🚮")
|
||||
(":atm:" . "🏧")
|
||||
(":moai:" . "🗿")
|
||||
(":cigarette:" . "🚬")
|
||||
(":bathtub:" . "🛁")
|
||||
(":shower:" . "🚿")
|
||||
(":toilet:" . "🚽")
|
||||
(":door:" . "🚪")
|
||||
(":pill:" . "💊")
|
||||
(":syringe:" . "💉")
|
||||
(":satellite_antenna:" . "📡")
|
||||
(":telescope:" . "🔭")
|
||||
(":microscope:" . "🔬")
|
||||
(":link:" . "🔗")
|
||||
(":nut_and_bolt:" . "🔩")
|
||||
(":wrench:" . "🔧")
|
||||
(":bow:" . "🏹")
|
||||
(":gun:" . "🔫")
|
||||
(":hammer:" . "🔨")
|
||||
(":key:" . "🔑")
|
||||
(":locked_key:" . "🔐")
|
||||
(":locked_pen:" . "🔏")
|
||||
(":triangular_ruler:" . "📐")
|
||||
(":straight_ruler:" . "📏")
|
||||
(":paperclip:" . "📎")
|
||||
(":round_pushpin:" . "📍")
|
||||
(":pushpin:" . "📌")
|
||||
(":bar_chart:" . "📊")
|
||||
(":chart_down:" . "📉")
|
||||
(":chart_up:" . "📈")
|
||||
(":card_index:" . "📇")
|
||||
(":torn_calendar:" . "📆")
|
||||
(":date:" . "📅")
|
||||
(":open_file_folder:" . "📂")
|
||||
(":file_folder:" . "📁")
|
||||
(":briefcase:" . "💼")
|
||||
(":memo:" . "📝")
|
||||
(":postbox:" . "📮")
|
||||
(":envelope_arrow:" . "📩")
|
||||
(":incoming_envelope:" . "📨")
|
||||
(":email:" . "📧")
|
||||
(":dollar_sign:" . "💲")
|
||||
(":currency_exchange:" . "💱")
|
||||
(":ja_chart:" . "💹")
|
||||
(":money_wings:" . "💸")
|
||||
(":pound:" . "💷")
|
||||
(":euro:" . "💶")
|
||||
(":dollar:" . "💵")
|
||||
(":yen:" . "💴")
|
||||
(":bookmark:" . "🔖")
|
||||
(":bookmark_tabs:" . "📑")
|
||||
(":newspaper:" . "📰")
|
||||
(":page_facing_up:" . "📄")
|
||||
(":scroll:" . "📜")
|
||||
(":page_curl:" . "📃")
|
||||
(":ledger:" . "📒")
|
||||
(":notebook:" . "📓")
|
||||
(":orange_book:" . "📙")
|
||||
(":blue_book:" . "📘")
|
||||
(":green_book:" . "📗")
|
||||
(":book:" . "📖")
|
||||
(":closed_book:" . "📕")
|
||||
(":decorative_notebook:" . "📔")
|
||||
(":red_lantern:" . "🏮")
|
||||
(":flashlight:" . "🔦")
|
||||
(":bulb:" . "💡")
|
||||
(":mag_right:" . "🔎")
|
||||
(":vhs:" . "📼")
|
||||
(":camera_flash:" . "📸")
|
||||
(":movie_camera:" . "🎥")
|
||||
(":dvd:" . "📀")
|
||||
(":floppy_disk:" . "💾")
|
||||
(":minidisc:" . "💽")
|
||||
(":electric_plug:" . "🔌")
|
||||
(":battery:" . "🔋")
|
||||
(":fax:" . "📠")
|
||||
(":telephone_receiver:" . "📞")
|
||||
(":mobile_calling:" . "📲")
|
||||
(":mobile:" . "📱")
|
||||
(":violin:" . "🎻")
|
||||
(":trumpet:" . "🎺")
|
||||
(":musical_keyboard:" . "🎹")
|
||||
(":guitar:" . "🎸")
|
||||
(":saxophone:" . "🎷")
|
||||
(":microphone:" . "🎤")
|
||||
(":musical_notes:" . "🎶")
|
||||
(":musical_note:" . "🎵")
|
||||
(":musical_score:" . "🎼")
|
||||
(":no_bell:" . "🔕")
|
||||
(":bell:" . "🔔")
|
||||
(":postal_horn:" . "📯")
|
||||
(":megaphone:" . "📣")
|
||||
(":loudspeaker:" . "📢")
|
||||
(":loud_sound:" . "🔊")
|
||||
(":sound:" . "🔉")
|
||||
(":mute:" . "🔇")
|
||||
(":gem:" . "💎")
|
||||
(":ring:" . "💍")
|
||||
(":lipstick:" . "💄")
|
||||
(":prayer_beads:" . "📿")
|
||||
(":top_hat:" . "🎩")
|
||||
(":womans_hat:" . "👒")
|
||||
(":crown:" . "👑")
|
||||
(":womans_boot:" . "👢")
|
||||
(":womans_sandal:" . "👡")
|
||||
(":high_heel:" . "👠")
|
||||
(":sneaker:" . "👟")
|
||||
(":dress_shoe:" . "👞")
|
||||
(":backpack:" . "🎒")
|
||||
(":pouch:" . "👝")
|
||||
(":handbag:" . "👜")
|
||||
(":purse:" . "👛")
|
||||
(":blouse:" . "👚")
|
||||
(":bikini:" . "👙")
|
||||
(":kimono:" . "👘")
|
||||
(":dress:" . "👗")
|
||||
(":jeans:" . "👖")
|
||||
(":shirt:" . "👕")
|
||||
(":necktie:" . "👔")
|
||||
(":art:" . "🎨")
|
||||
(":flower_cards:" . "🎴")
|
||||
(":black_joker:" . "🃏")
|
||||
(":game_die:" . "🎲")
|
||||
(":slot_machine:" . "🎰")
|
||||
(":crystal_ball:" . "🔮")
|
||||
(":8ball:" . "🎱")
|
||||
(":dart:" . "🎯")
|
||||
(":ski:" . "🎿")
|
||||
(":running_shirt:" . "🎽")
|
||||
(":fishing_pole:" . "🎣")
|
||||
(":badminton:" . "🏸")
|
||||
(":ping_pong:" . "🏓")
|
||||
(":hockey:" . "🏒")
|
||||
(":field_hockey:" . "🏑")
|
||||
(":cricket_game:" . "🏏")
|
||||
(":bowling:" . "🎳")
|
||||
(":tennis:" . "🎾")
|
||||
(":rugby:" . "🏉")
|
||||
(":football:" . "🏈")
|
||||
(":volleyball:" . "🏐")
|
||||
(":basketball:" . "🏀")
|
||||
(":medal:" . "🏅")
|
||||
(":ticket:" . "🎫")
|
||||
(":gift:" . "🎁")
|
||||
(":ribbon:" . "🎀")
|
||||
(":moon_ceremony:" . "🎑")
|
||||
(":wind_chime:" . "🎐")
|
||||
(":carp_streamer:" . "🎏")
|
||||
(":dolls:" . "🎎")
|
||||
(":bamboo:" . "🎍")
|
||||
(":tanabata_tree:" . "🎋")
|
||||
(":confetti_ball:" . "🎊")
|
||||
(":tada:" . "🎉")
|
||||
(":balloon:" . "🎈")
|
||||
(":sparkles:" . "✨")
|
||||
(":sparkler:" . "🎇")
|
||||
(":fireworks:" . "🎆")
|
||||
(":christmas_tree:" . "🎄")
|
||||
(":jack_o_lantern:" . "🎃")
|
||||
(":ocean:" . "🌊")
|
||||
(":droplet:" . "💧")
|
||||
(":fire:" . "🔥")
|
||||
(":closed_umbrella:" . "🌂")
|
||||
(":rainbow:" . "🌈")
|
||||
(":cyclone:" . "🌀")
|
||||
(":milky_way:" . "🌌")
|
||||
(":star3:" . "🌠")
|
||||
(":star2:" . "🌟")
|
||||
(":sun_face:" . "🌞")
|
||||
(":full_moon_face:" . "🌝")
|
||||
(":first_quarter_moon_face:" . "🌛")
|
||||
(":new_moon_face:" . "🌚")
|
||||
(":crescent_moon:" . "🌙")
|
||||
(":waning_crescent_moon:" . "🌘")
|
||||
(":last_quarter_moon:" . "🌗")
|
||||
(":waning_gibbous_moon:" . "🌖")
|
||||
(":waxing_gibbous_moon:" . "🌔")
|
||||
(":first_quarter_moon:" . "🌓")
|
||||
(":waxing_crescent_moon:" . "🌒")
|
||||
(":new_moon:" . "🌑")
|
||||
(":alarm_clock:" . "⏰")
|
||||
(":rocket:" . "🚀")
|
||||
(":aerial_tramway:" . "🚡")
|
||||
(":mountain_cableway:" . "🚠")
|
||||
(":suspension_railway:" . "🚟")
|
||||
(":helicopter:" . "🚁")
|
||||
(":seat:" . "💺")
|
||||
(":airplane_arriving:" . "🛬")
|
||||
(":airplane_departure:" . "🛫")
|
||||
(":ship:" . "🚢")
|
||||
(":speedboat:" . "🚤")
|
||||
(":construction:" . "🚧")
|
||||
(":vertical_traffic_light:" . "🚦")
|
||||
(":traffic_light:" . "🚥")
|
||||
(":rotating_light:" . "🚨")
|
||||
(":bus_stop:" . "🚏")
|
||||
(":tractor:" . "🚜")
|
||||
(":lorry:" . "🚛")
|
||||
(":truck:" . "🚚")
|
||||
(":blue_car:" . "🚙")
|
||||
(":red_car:" . "🚗")
|
||||
(":oncoming_taxi:" . "🚖")
|
||||
(":taxi:" . "🚕")
|
||||
(":police_car:" . "🚓")
|
||||
(":fire_engine:" . "🚒")
|
||||
(":minibus:" . "🚐")
|
||||
(":trolleybus:" . "🚎")
|
||||
(":bus:" . "🚌")
|
||||
(":tram_car:" . "🚋")
|
||||
(":mountain_railway:" . "🚞")
|
||||
(":monorail:" . "🚝")
|
||||
(":tram:" . "🚊")
|
||||
(":station:" . "🚉")
|
||||
(":light_rail:" . "🚈")
|
||||
(":train:" . "🚆")
|
||||
(":bullettrain:" . "🚅")
|
||||
(":bullettrain_side:" . "🚄")
|
||||
(":railway_car:" . "🚃")
|
||||
(":steam_locomotive:" . "🚂")
|
||||
(":circus_tent:" . "🎪")
|
||||
(":barber:" . "💈")
|
||||
(":roller_coaster:" . "🎢")
|
||||
(":ferris_wheel:" . "🎡")
|
||||
(":carousel_horse:" . "🎠")
|
||||
(":bridge_at_night:" . "🌉")
|
||||
(":sunset:" . "🌇")
|
||||
(":dusk:" . "🌆")
|
||||
(":sunrise:" . "🌅")
|
||||
(":sunrise_over_mountains:" . "🌄")
|
||||
(":night_stars:" . "🌃")
|
||||
(":foggy:" . "🌁")
|
||||
(":kaaba:" . "🕋")
|
||||
(":synagogue:" . "🕍")
|
||||
(":mosque:" . "🕌")
|
||||
(":statue_of_liberty:" . "🗽")
|
||||
(":tokyo_tower:" . "🗼")
|
||||
(":wedding:" . "💒")
|
||||
(":castle:" . "🏰")
|
||||
(":japanese_castle:" . "🏯")
|
||||
(":department_store:" . "🏬")
|
||||
(":school:" . "🏫")
|
||||
(":convenience_store:" . "🏪")
|
||||
(":love_hotel:" . "🏩")
|
||||
(":hotel:" . "🏨")
|
||||
(":bank:" . "🏦")
|
||||
(":hospital:" . "🏥")
|
||||
(":post_office:" . "🏤")
|
||||
(":ja_post_office:" . "🏣")
|
||||
(":office:" . "🏢")
|
||||
(":house_garden:" . "🏡")
|
||||
(":mount_fuji:" . "🗻")
|
||||
(":volcano:" . "🌋")
|
||||
(":japan:" . "🗾")
|
||||
(":globe:" . "🌐")
|
||||
(":amphora:" . "🏺")
|
||||
(":knife:" . "🔪")
|
||||
(":utensils:" . "🍴")
|
||||
(":beers:" . "🍻")
|
||||
(":beer:" . "🍺")
|
||||
(":tropical_drink:" . "🍹")
|
||||
(":wine_glass:" . "🍷")
|
||||
(":champagne:" . "🍾")
|
||||
(":sake:" . "🍶")
|
||||
(":tea:" . "🍵")
|
||||
(":baby_bottle:" . "🍼")
|
||||
(":honey_pot:" . "🍯")
|
||||
(":custard:" . "🍮")
|
||||
(":lollipop:" . "🍭")
|
||||
(":candy:" . "🍬")
|
||||
(":chocolate_bar:" . "🍫")
|
||||
(":cake:" . "🍰")
|
||||
(":birthday:" . "🎂")
|
||||
(":cookie:" . "🍪")
|
||||
(":doughnut:" . "🍩")
|
||||
(":ice_cream:" . "🍨")
|
||||
(":shaved_ice:" . "🍧")
|
||||
(":icecream:" . "🍦")
|
||||
(":crab:" . "🦀")
|
||||
(":dango:" . "🍡")
|
||||
(":fish_cake:" . "🍥")
|
||||
(":fried_shrimp:" . "🍤")
|
||||
(":sushi:" . "🍣")
|
||||
(":oden:" . "🍢")
|
||||
(":sweet_potato:" . "🍠")
|
||||
(":spaghetti:" . "🍝")
|
||||
(":ramen:" . "🍜")
|
||||
(":curry:" . "🍛")
|
||||
(":rice:" . "🍚")
|
||||
(":rice_ball:" . "🍙")
|
||||
(":rice_cracker:" . "🍘")
|
||||
(":bento:" . "🍱")
|
||||
(":popcorn:" . "🍿")
|
||||
(":stew:" . "🍲")
|
||||
(":cooking:" . "🍳")
|
||||
(":burrito:" . "🌯")
|
||||
(":taco:" . "🌮")
|
||||
(":hotdog:" . "🌭")
|
||||
(":pizza:" . "🍕")
|
||||
(":fries:" . "🍟")
|
||||
(":hamburger:" . "🍔")
|
||||
(":poultry_leg:" . "🍗")
|
||||
(":meat_on_bone:" . "🍖")
|
||||
(":cheese:" . "🧀")
|
||||
(":bread:" . "🍞")
|
||||
(":chestnut:" . "🌰")
|
||||
(":mushroom:" . "🍄")
|
||||
(":corn:" . "🌽")
|
||||
(":eggplant:" . "🍆")
|
||||
(":tomato:" . "🍅")
|
||||
(":strawberry:" . "🍓")
|
||||
(":cherries:" . "🍒")
|
||||
(":peach:" . "🍑")
|
||||
(":pear:" . "🍐")
|
||||
(":green_apple:" . "🍏")
|
||||
(":apple:" . "🍎")
|
||||
(":pineapple:" . "🍍")
|
||||
(":banana:" . "🍌")
|
||||
(":lemon:" . "🍋")
|
||||
(":tangerine:" . "🍊")
|
||||
(":watermelon:" . "🍉")
|
||||
(":melon:" . "🍈")
|
||||
(":grapes:" . "🍇")
|
||||
(":leaves:" . "🍃")
|
||||
(":fallen_leaf:" . "🍂")
|
||||
(":maple_leaf:" . "🍁")
|
||||
(":four_leaf_clover:" . "🍀")
|
||||
(":herb:" . "🌿")
|
||||
(":ear_of_rice:" . "🌾")
|
||||
(":cactus:" . "🌵")
|
||||
(":palm_tree:" . "🌴")
|
||||
(":deciduous_tree:" . "🌳")
|
||||
(":evergreen_tree:" . "🌲")
|
||||
(":seedling:" . "🌱")
|
||||
(":tulip:" . "🌷")
|
||||
(":blossom:" . "🌼")
|
||||
(":sunflower:" . "🌻")
|
||||
(":hibiscus:" . "🌺")
|
||||
(":rose:" . "🌹")
|
||||
(":white_flower:" . "💮")
|
||||
(":cherry_blossom:" . "🌸")
|
||||
(":bouquet:" . "💐")
|
||||
(":scorpion:" . "🦂")
|
||||
(":beetle:" . "🐞")
|
||||
(":bee:" . "🐝")
|
||||
(":ant:" . "🐜")
|
||||
(":bug:" . "🐛")
|
||||
(":snail:" . "🐌")
|
||||
(":shell:" . "🐚")
|
||||
(":octopus:" . "🐙")
|
||||
(":blowfish:" . "🐡")
|
||||
(":tropical_fish:" . "🐠")
|
||||
(":dolphin:" . "🐬")
|
||||
(":whale:" . "🐋")
|
||||
(":spouting_whale:" . "🐳")
|
||||
(":dragon:" . "🐉")
|
||||
(":dragon_face:" . "🐲")
|
||||
(":snake:" . "🐍")
|
||||
(":turtle:" . "🐢")
|
||||
(":crocodile:" . "🐊")
|
||||
(":frog_face:" . "🐸")
|
||||
(":penguin:" . "🐧")
|
||||
(":hatched_chick:" . "🐥")
|
||||
(":baby_chick:" . "🐤")
|
||||
(":hatching_chick:" . "🐣")
|
||||
(":rooster:" . "🐓")
|
||||
(":chicken:" . "🐔")
|
||||
(":turkey:" . "🦃")
|
||||
(":feet:" . "🐾")
|
||||
(":panda_face:" . "🐼")
|
||||
(":koala_face:" . "🐨")
|
||||
(":bear_face:" . "🐻")
|
||||
(":rabbit:" . "🐇")
|
||||
(":rabbit_face:" . "🐰")
|
||||
(":hamster_face:" . "🐹")
|
||||
(":rat:" . "🐀")
|
||||
(":mouse:" . "🐁")
|
||||
(":mouse_face:" . "🐭")
|
||||
(":elephant:" . "🐘")
|
||||
(":two_hump_camel:" . "🐫")
|
||||
(":camel:" . "🐪")
|
||||
(":goat:" . "🐐")
|
||||
(":sheep:" . "🐑")
|
||||
(":ram:" . "🐏")
|
||||
(":pig_nose:" . "🐽")
|
||||
(":boar:" . "🐗")
|
||||
(":pig:" . "🐖")
|
||||
(":pig_face:" . "🐷")
|
||||
(":cow:" . "🐄")
|
||||
(":water_buffalo:" . "🐃")
|
||||
(":ox:" . "🐂")
|
||||
(":cow_face:" . "🐮")
|
||||
(":unicorn_face:" . "🦄")
|
||||
(":horse:" . "🐎")
|
||||
(":horse_face:" . "🐴")
|
||||
(":leopard:" . "🐆")
|
||||
(":tiger:" . "🐅")
|
||||
(":tiger_face:" . "🐯")
|
||||
(":lion_face:" . "🦁")
|
||||
(":cat_face:" . "🐱")
|
||||
(":wolf_face:" . "🐺")
|
||||
(":poodle:" . "🐩")
|
||||
(":dog_face:" . "🐶")
|
||||
(":monkey:" . "🐒")
|
||||
(":monkey_face:" . "🐵")
|
||||
(":tone_dark:" . "🏿")
|
||||
(":tone_medium_dark:" . "🏾")
|
||||
(":tone_medium:" . "🏽")
|
||||
(":tone_medium_light:" . "🏼")
|
||||
(":tone_light:" . "🏻")
|
||||
(":footprints:" . "👣")
|
||||
(":busts_silhouette:" . "👥")
|
||||
(":bust_silhouette:" . "👤")
|
||||
(":couple_heart:" . "💑")
|
||||
(":couple:" . "💏")
|
||||
(":holding_hands_mm:" . "👬")
|
||||
(":holding_hands_mw:" . "👫")
|
||||
(":holding_hands_ww:" . "👭")
|
||||
(":in_bed:" . "🛌")
|
||||
(":bath:" . "🛀")
|
||||
(":person_mountain_biking:" . "🚵")
|
||||
(":person_biking:" . "🚴")
|
||||
(":person_rowing_boat:" . "🚣")
|
||||
(":horse_racing:" . "🏇")
|
||||
(":people_bunny_ears_partying:" . "👯")
|
||||
(":dancer:" . "💃")
|
||||
(":person_running:" . "🏃")
|
||||
(":person_walking:" . "🚶")
|
||||
(":person_getting_haircut:" . "💇")
|
||||
(":person_getting_massage:" . "💆")
|
||||
(":santa:" . "🎅")
|
||||
(":baby_angel:" . "👼")
|
||||
(":bride_veil:" . "👰")
|
||||
(":man_chinese_cap:" . "👲")
|
||||
(":person_turban:" . "👳")
|
||||
(":princess:" . "👸")
|
||||
(":construction_worker:" . "👷")
|
||||
(":guard:" . "💂")
|
||||
(":police_officer:" . "👮")
|
||||
(":person_bowing:" . "🙇")
|
||||
(":person_raising_hand:" . "🙋")
|
||||
(":person_tipping_hand:" . "💁")
|
||||
(":person_gesturing_ok:" . "🙆")
|
||||
(":person_gesturing_no:" . "🙅")
|
||||
(":person_pouting:" . "🙎")
|
||||
(":person_frowning:" . "🙍")
|
||||
(":older_woman:" . "👵")
|
||||
(":older_man:" . "👴")
|
||||
(":woman:" . "👩")
|
||||
(":man:" . "👨")
|
||||
(":blond_person:" . "👱")
|
||||
(":girl:" . "👧")
|
||||
(":boy:" . "👦")
|
||||
(":baby:" . "👶")
|
||||
(":lips:" . "👄")
|
||||
(":tongue:" . "👅")
|
||||
(":eyes:" . "👀")
|
||||
(":nose:" . "👃")
|
||||
(":muscle:" . "💪")
|
||||
(":nail_care:" . "💅")
|
||||
(":pray:" . "🙏")
|
||||
(":open_hands:" . "👐")
|
||||
(":raised_hands:" . "🙌")
|
||||
(":clap:" . "👏")
|
||||
(":punch:" . "👊")
|
||||
(":fist:" . "✊")
|
||||
(":middle_finger:" . "🖕")
|
||||
(":metal:" . "🤘")
|
||||
(":ok_hand:" . "👌")
|
||||
(":vulcan:" . "🖖")
|
||||
(":raised_hand:" . "✋")
|
||||
(":wave:" . "👋")
|
||||
(":zzz:" . "💤")
|
||||
(":thought:" . "💭")
|
||||
(":speech:" . "💬")
|
||||
(":dash:" . "💨")
|
||||
(":sweat_drops:" . "💦")
|
||||
(":dizzy_star:" . "💫")
|
||||
(":boom:" . "💥")
|
||||
(":anger:" . "💢")
|
||||
(":100:" . "💯")
|
||||
(":purple_heart:" . "💜")
|
||||
(":blue_heart:" . "💙")
|
||||
(":green_heart:" . "💚")
|
||||
(":yellow_heart:" . "💛")
|
||||
(":broken_heart:" . "💔")
|
||||
(":heart_decoration:" . "💟")
|
||||
(":two_hearts:" . "💕")
|
||||
(":revolving_hearts:" . "💞")
|
||||
(":heartbeat:" . "💓")
|
||||
(":heartpulse:" . "💗")
|
||||
(":sparkling_heart:" . "💖")
|
||||
(":heart_ribbon:" . "💝")
|
||||
(":cupid:" . "💘")
|
||||
(":love_letter:" . "💌")
|
||||
(":kiss_lips:" . "💋")
|
||||
(":speak_no_evil:" . "🙊")
|
||||
(":hear_no_evil:" . "🙉")
|
||||
(":see_no_evil:" . "🙈")
|
||||
(":pouting_cat:" . "😾")
|
||||
(":crying_cat:" . "😿")
|
||||
(":weary_cat:" . "🙀")
|
||||
(":kissing_cat:" . "😽")
|
||||
(":smirking_cat:" . "😼")
|
||||
(":lovestruck_cat:" . "😻")
|
||||
(":joyful_cat:" . "😹")
|
||||
(":grinning_cat:" . "😸")
|
||||
(":smiling_cat:" . "😺")
|
||||
(":robot:" . "🤖")
|
||||
(":alien_monster:" . "👾")
|
||||
(":ghost:" . "👻")
|
||||
(":goblin:" . "👺")
|
||||
(":ogre:" . "👹")
|
||||
(":poop:" . "💩")
|
||||
(":skull:" . "💀")
|
||||
(":angry_imp:" . "👿")
|
||||
(":imp:" . "😈")
|
||||
(":angry:" . "😠")
|
||||
(":enraged:" . "😡")
|
||||
(":annoyed:" . "😤")
|
||||
(":tired:" . "😫")
|
||||
(":weary:" . "😩")
|
||||
(":shamed:" . "😓")
|
||||
(":disappointed:" . "😞")
|
||||
(":persevered:" . "😣")
|
||||
(":confounded:" . "😖")
|
||||
(":frightened:" . "😱")
|
||||
(":distressed:" . "😭")
|
||||
(":upset:" . "😢")
|
||||
(":hopeful:" . "😥")
|
||||
(":cold_sweat:" . "😰")
|
||||
(":fearful:" . "😨")
|
||||
(":anguished:" . "😧")
|
||||
(":bored:" . "😦")
|
||||
(":flushed:" . "😳")
|
||||
(":astonished:" . "😲")
|
||||
(":hushed:" . "😯")
|
||||
(":surprised:" . "😮")
|
||||
(":cheerless:" . "🙁")
|
||||
(":worried:" . "😟")
|
||||
(":confused:" . "😕")
|
||||
(":nerd:" . "🤓")
|
||||
(":confident:" . "😎")
|
||||
(":dizzy:" . "😵")
|
||||
(":injured:" . "🤕")
|
||||
(":sick:" . "🤒")
|
||||
(":ill:" . "😷")
|
||||
(":exhausted:" . "😴")
|
||||
(":sleepy:" . "😪")
|
||||
(":pensive:" . "😔")
|
||||
(":relieved:" . "😌")
|
||||
(":grimaced:" . "😬")
|
||||
(":disbelief:" . "🙄")
|
||||
(":unamused:" . "😒")
|
||||
(":cocky:" . "😏")
|
||||
(":vacant:" . "😶")
|
||||
(":apathetic:" . "😑")
|
||||
(":silenced:" . "🤐")
|
||||
(":curious:" . "🤔")
|
||||
(":hugging:" . "🤗")
|
||||
(":pretentious:" . "🤑")
|
||||
(":facetious:" . "😝")
|
||||
(":mischievous:" . "😜")
|
||||
(":playful:" . "😛")
|
||||
(":yum:" . "😋")
|
||||
(":happy_kiss:" . "😙")
|
||||
(":loving_kiss:" . "😚")
|
||||
(":kiss:" . "😗")
|
||||
(":flirty:" . "😘")
|
||||
(":lovestruck:" . "😍")
|
||||
(":innocent:" . "😇")
|
||||
(":blush:" . "😊")
|
||||
(":coy:" . "😉")
|
||||
(":ecstatic:" . "🙃")
|
||||
(":pleased:" . "🙂")
|
||||
(":joyful:" . "😂")
|
||||
(":embarassed:" . "😅")
|
||||
(":amused:" . "😆")
|
||||
(":blissful:" . "😁")
|
||||
(":happy:" . "😄")
|
||||
(":glad:" . "😃")
|
||||
(":gleeful:" . "😀")))
|
||||
|
||||
(defun shortcode-lookup (key)
|
||||
(assoc key *shortcodes-db* :test #'string-equal))
|
||||
|
||||
(defun emojify (text)
|
||||
(loop for mapping in *shortcodes-db* do
|
||||
(setf text (cl-ppcre:regex-replace-all (car mapping) text (cdr mapping))))
|
||||
text)
|
|
@ -0,0 +1,368 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :filesystem-utils)
|
||||
|
||||
(define-constant +preprocess-include+ "^%include" :test #'string=)
|
||||
|
||||
(define-constant +file-path-regex+ "[\\p{L},\\/,\\\\,\\.]+" :test 'string=)
|
||||
|
||||
(defparameter *directory-sep-regexp*
|
||||
#+windows "\\"
|
||||
#-windows "\\/")
|
||||
|
||||
(defparameter *directory-sep*
|
||||
#+windows "\\"
|
||||
#-windows "/")
|
||||
|
||||
(defun copy-a-file (in out &key (overwrite nil))
|
||||
(if (and in
|
||||
(file-exists-p in)
|
||||
out
|
||||
(or (not (file-exists-p out))
|
||||
overwrite))
|
||||
(progn
|
||||
(uiop:copy-file in out)
|
||||
out)
|
||||
nil))
|
||||
|
||||
(defun file-size (filename)
|
||||
(with-open-file (stream filename :direction :input :element-type '(unsigned-byte 8)
|
||||
:if-does-not-exist nil)
|
||||
(if (null stream)
|
||||
0
|
||||
(file-length stream))))
|
||||
|
||||
(defun slurp-file (filename &key (convert-to-string t))
|
||||
"A simple way to slurp a file."
|
||||
(with-open-file (stream filename :direction :input :element-type '(unsigned-byte 8))
|
||||
(let ((seq (make-array (file-length stream) :element-type '(unsigned-byte 8))))
|
||||
(read-sequence seq stream)
|
||||
(if convert-to-string
|
||||
(babel:octets-to-string seq)
|
||||
seq))))
|
||||
|
||||
(defun dump-sequence-to-file (seq file)
|
||||
(with-open-file (stream file
|
||||
:direction :output
|
||||
:if-exists :supersede
|
||||
:if-does-not-exist :create)
|
||||
(write-sequence seq stream)))
|
||||
|
||||
(defun create-file (file &key (skip-if-exists nil))
|
||||
"create file and parent dir, if necessary"
|
||||
(when (not (and skip-if-exists
|
||||
(file-exists-p file)))
|
||||
(let ((path-splitted (fs:split-path-elements file)))
|
||||
(when (and path-splitted
|
||||
(> (length path-splitted) 1))
|
||||
(do* ((path-rest (subseq path-splitted 0
|
||||
(1- (length path-splitted)))
|
||||
(rest path-rest))
|
||||
(path-so-far "" (if (and path-rest
|
||||
(not (string= "" (first-elt path-rest))))
|
||||
(concatenate 'string
|
||||
path-so-far
|
||||
*directory-sep*
|
||||
(first-elt path-rest)
|
||||
*directory-sep*)
|
||||
path-so-far)))
|
||||
((null path-rest))
|
||||
(when (not (directory-exists-p path-so-far))
|
||||
(make-directory path-so-far)))
|
||||
(with-open-file (stream file
|
||||
:direction :output
|
||||
:if-exists :supersede
|
||||
:if-does-not-exist :create))))))
|
||||
|
||||
(defun has-extension (path ext)
|
||||
(let ((re (concatenate 'string ext "$")))
|
||||
(cl-ppcre:scan re path)))
|
||||
|
||||
(defun strip-extension (file &key (strip-all nil))
|
||||
(let ((new (cl-ppcre:regex-replace "(?i)[a-z0-9]\\.[^.]+$" file "")))
|
||||
(if (string= file new)
|
||||
new
|
||||
(if strip-all
|
||||
(strip-extension new :strip-all t)
|
||||
new))))
|
||||
|
||||
(defun get-extension (file)
|
||||
(multiple-value-bind (matchedp res)
|
||||
(cl-ppcre:scan-to-strings "(?i)[a-z0-9]\(\\.[^.]+$\)" file)
|
||||
(when matchedp
|
||||
(first-elt res))))
|
||||
|
||||
(defun add-extension (file extension)
|
||||
(text-utils:strcat file "." extension))
|
||||
|
||||
(defun cat-parent-dir (parent direntry)
|
||||
(format nil "~a~a~a" parent *directory-sep* direntry))
|
||||
|
||||
(defmacro do-directory ((var) root &body body)
|
||||
(with-gensyms (dir)
|
||||
`(let ((,dir (nix:opendir ,root)))
|
||||
(unwind-protect
|
||||
(handler-case
|
||||
(do ((,var (cat-parent-dir ,root (nix:readdir ,dir))
|
||||
(cat-parent-dir ,root (nix:readdir ,dir))))
|
||||
((cl-ppcre:scan "NIL$" ,var))
|
||||
,@body)
|
||||
(nix::enotdir () 0)
|
||||
(nix:eacces () 0)
|
||||
(nix:eloop () 0))
|
||||
(nix:closedir ,dir)))))
|
||||
|
||||
(defun search-matching-file (root-directory &key (name ".*"))
|
||||
"Scan a filesystem saving files that match the provided criteria,
|
||||
does not follow symlinks."
|
||||
(let ((matched '())
|
||||
(scanner (cl-ppcre:create-scanner name)))
|
||||
(labels ((match (dir)
|
||||
(do-directory (path) dir
|
||||
(let ((filename (path-last-element path)))
|
||||
(cond
|
||||
((regular-file-p path)
|
||||
(when (cl-ppcre:scan scanner filename)
|
||||
(push path matched)))
|
||||
((and (not (cl-ppcre:scan "^\\.\\." filename))
|
||||
(not (cl-ppcre:scan "^\\." filename))
|
||||
(dirp path))
|
||||
(match path)))))))
|
||||
(match root-directory)
|
||||
matched)))
|
||||
|
||||
(defun regular-file-p (path)
|
||||
(nix:s-isreg (nix:stat-mode (nix:stat path))))
|
||||
|
||||
(defun dirp (path)
|
||||
(nix:s-isdir (nix:stat-mode (nix:stat path))))
|
||||
|
||||
(defun split-path-elements (path)
|
||||
(cl-ppcre:split *directory-sep-regexp* path))
|
||||
|
||||
(defun path-last-element (path)
|
||||
(let ((elements (cl-ppcre:split *directory-sep-regexp* path)))
|
||||
(and elements
|
||||
(last-elt elements))))
|
||||
|
||||
(defun path-first-element (path)
|
||||
(let ((elements (cl-ppcre:split *directory-sep-regexp* path)))
|
||||
(and elements
|
||||
(first-elt elements))))
|
||||
|
||||
(defun path-to-hidden-file-p (path)
|
||||
"unix-like only"
|
||||
(let ((last-element (path-last-element path)))
|
||||
(and path (cl-ppcre:scan "^\\." last-element))))
|
||||
|
||||
(defun strip-dirs-from-path (p)
|
||||
(multiple-value-bind (all registers)
|
||||
(cl-ppcre:scan-to-strings (concatenate 'string
|
||||
*directory-sep*
|
||||
"([^"
|
||||
*directory-sep*
|
||||
"]+)$")
|
||||
p)
|
||||
(declare (ignore all))
|
||||
(and (> (length registers) 0)
|
||||
(elt registers 0))))
|
||||
|
||||
(defun parent-dir-path (path)
|
||||
(let ((splitted (remove-if #'(lambda (a) (string= "" a))
|
||||
(split-path-elements path))))
|
||||
(cond
|
||||
((> (length splitted) 1)
|
||||
(let ((res (if (string= (string (elt path 0)) *directory-sep*)
|
||||
(concatenate 'string *directory-sep* (first splitted))
|
||||
(first splitted))))
|
||||
(loop for i in (subseq splitted 1 (1- (length splitted))) do
|
||||
(setf res (concatenate 'string res *directory-sep* i)))
|
||||
(setf res (concatenate 'string res *directory-sep*))
|
||||
res))
|
||||
((null splitted)
|
||||
*directory-sep*)
|
||||
(t
|
||||
path))))
|
||||
|
||||
(defmacro define-stat-time (slot-name)
|
||||
(with-gensyms (stat)
|
||||
`(defun ,(format-symbol t "~:@(get-stat-~a~)" slot-name) (file)
|
||||
(restart-case
|
||||
(let ((,stat (nix:stat file)))
|
||||
(when ,stat
|
||||
(misc:time-unix->universal (,(format-symbol :nix "~:@(stat-~a~)" slot-name)
|
||||
,stat))))
|
||||
(use-value (value) value)))))
|
||||
|
||||
(define-stat-time mtime)
|
||||
|
||||
(define-stat-time ctime)
|
||||
|
||||
(define-stat-time atime)
|
||||
|
||||
(defun file-hash (file)
|
||||
(num:fnv-hash-32 (slurp-file file :convert-to-string nil)))
|
||||
|
||||
(defun file-outdated-p (file &rest dependencies)
|
||||
(handler-bind ((nix:enoent #'(lambda (c)
|
||||
(declare (ignore c))
|
||||
(invoke-restart 'use-value nil))))
|
||||
(let ((mtime-file (get-stat-mtime file))
|
||||
(mtimes-deps (remove-if #'null (mapcar #'get-stat-mtime dependencies))))
|
||||
(if mtime-file
|
||||
(remove-if #'(lambda (mtime) (<= mtime mtime-file)) mtimes-deps)
|
||||
t))))
|
||||
|
||||
(defun file-exists-p (f)
|
||||
(uiop:file-exists-p f))
|
||||
|
||||
(defun directory-exists-p (d)
|
||||
(uiop:directory-exists-p d))
|
||||
|
||||
(defun delete-file-if-exists (f)
|
||||
(uiop:delete-file-if-exists f))
|
||||
|
||||
(defun file-length-if-exists (f)
|
||||
(when (file-exists-p f)
|
||||
(with-open-file (stream f :element-type '(unsigned-byte 8))
|
||||
(file-length stream))))
|
||||
|
||||
(defun home-dir (&key (add-separator-ends nil))
|
||||
(let ((home (os-utils:getenv "HOME")))
|
||||
(if add-separator-ends
|
||||
(text-utils:strcat home *directory-sep*)
|
||||
home)))
|
||||
|
||||
(defun temporary-filename (&optional (temp-directory nil))
|
||||
(let ((tmpdir (or temp-directory
|
||||
(os-utils:default-temp-dir))))
|
||||
(multiple-value-bind (x filename)
|
||||
(if tmpdir
|
||||
(nix:mkstemp (format nil "~a~a~a" tmpdir *directory-sep*
|
||||
config:+program-name+))
|
||||
(nix:mkstemp (format nil "~atmp~a~a" *directory-sep* *directory-sep*
|
||||
config:+program-name+)))
|
||||
(declare (ignore x))
|
||||
filename)))
|
||||
|
||||
(defmacro with-anaphoric-temp-file ((stream &key (prefix nil) (unlink nil)) &body body)
|
||||
`(let ((temp-file (temporary-filename ,prefix))) ; anaphora
|
||||
(unwind-protect
|
||||
(with-open-file (,stream temp-file
|
||||
:direction :output
|
||||
:if-exists :error
|
||||
:if-does-not-exist :create)
|
||||
,@body)
|
||||
,(if unlink
|
||||
`(delete-file-if-exists temp-file)
|
||||
nil))))
|
||||
|
||||
(defun has-file-permission-p (file permission)
|
||||
(find permission (osicat:file-permissions file) :test #'eq))
|
||||
|
||||
(defun file-can-write-p (file)
|
||||
(has-file-permission-p file :user-write))
|
||||
|
||||
(defmacro gen-permission-files (&rest modes)
|
||||
`(progn
|
||||
,@(loop for mode in modes collect
|
||||
`(define-constant ,(misc:format-fn-symbol t "+~a+" mode)
|
||||
,mode
|
||||
:test #'eql))))
|
||||
|
||||
(gen-permission-files
|
||||
nix:s-irwxu nix:s-irusr nix:s-iwusr nix:s-ixusr nix:s-irwxg nix:s-irgrp nix:s-iwgrp
|
||||
nix:s-ixgrp nix:s-irwxo nix:s-iroth nix:s-iwoth nix:s-ixoth nix:s-isuid nix:s-isgid)
|
||||
|
||||
(defun set-file-permissions (file mode)
|
||||
(nix:chmod file mode))
|
||||
|
||||
(misc:defcached cached-directory-files ((path) :test equal)
|
||||
(declare (optimize (speed 0) (safety 3) (debug 3)))
|
||||
(if (gethash path cache)
|
||||
(gethash path cache)
|
||||
(progn
|
||||
(setf (gethash path cache) (uiop:directory-files path))
|
||||
(cached-directory-files path))))
|
||||
|
||||
(defun directory-files (path)
|
||||
(and path
|
||||
(uiop:directory-files path)))
|
||||
|
||||
(defun make-directory (path)
|
||||
(if (not (cl-ppcre:scan (concatenate 'string *directory-sep* "$") path))
|
||||
(make-directory (concatenate 'string path *directory-sep*))
|
||||
(ensure-directories-exist path)))
|
||||
|
||||
(defun package-path ()
|
||||
(uiop:pathname-parent-directory-pathname
|
||||
(asdf:component-pathname
|
||||
(asdf:find-component (symbolicate (string-upcase config:+program-name+))
|
||||
nil))))
|
||||
|
||||
(defun file-in-package (name)
|
||||
(concatenate 'string (namestring (package-path)) name))
|
||||
|
||||
(defparameter *file-link-to* nil)
|
||||
|
||||
(define-constant +rel-link+ :rel)
|
||||
|
||||
(define-constant +abs-link+ :abs)
|
||||
|
||||
(defmacro see-file (&body forms)
|
||||
(if (> (length forms) 1)
|
||||
(warn "see-file: too many elements in forms, must be exactly 2"))
|
||||
(let ((path (first-elt forms)))
|
||||
(when (not (stringp path))
|
||||
(error (format nil "see-file: the path ~a is not a string" path)))
|
||||
(when (= (length path) 0)
|
||||
(error (format nil "see-file: the path ~a is to short" path)))
|
||||
(if (string= *directory-sep* (string (first-elt path)))
|
||||
`(setf *file-link-to* (cons ,path +abs-link+))
|
||||
`(setf *file-link-to* (cons ,path +rel-link+)))))
|
||||
|
||||
(defun link-file-path (file)
|
||||
(misc:with-load-forms-in-var (*file-link-to* link-file file)
|
||||
(if link-file
|
||||
(destructuring-bind (path . type)
|
||||
link-file
|
||||
(if (eq type +rel-link+)
|
||||
(cat-parent-dir (parent-dir-path file) path)
|
||||
path))
|
||||
nil)))
|
||||
|
||||
(defmacro file-is-link-if-else ((file link-file-pointed) is-link-forms is-not-link-forms)
|
||||
`(let ((,link-file-pointed (link-file-path ,file)))
|
||||
(if ,link-file-pointed
|
||||
,is-link-forms
|
||||
,is-not-link-forms)))
|
||||
|
||||
(defun pathname->namestring (p)
|
||||
(uiop:native-namestring p))
|
||||
|
||||
(defun namestring->pathname (p)
|
||||
(uiop:parse-native-namestring p))
|
||||
|
||||
(defun read-single-form (file)
|
||||
(with-open-file (stream file :direction :input :if-does-not-exist nil)
|
||||
(when stream
|
||||
(read stream))))
|
||||
|
||||
(defun eq-filename (a b)
|
||||
(flet ((strip (a) (strip-dirs-from-path (pathname->namestring a))))
|
||||
(string= (strip a)
|
||||
(strip b))))
|
|
@ -0,0 +1,149 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :follow-requests)
|
||||
|
||||
(defclass follow-requests-window (focus-marked-window simple-line-navigation-window)
|
||||
((requests
|
||||
:initarg :requests
|
||||
:initform ()
|
||||
:accessor requests
|
||||
:type tooter:account
|
||||
:documentation "All the accounts that request to follow you")
|
||||
(header-message-lines
|
||||
:initarg :header-message-lines
|
||||
:initform ()
|
||||
:accessor header-message-lines
|
||||
:documentation "lines of text printed on top of the window")
|
||||
(screen
|
||||
:initarg :screen
|
||||
:initform nil
|
||||
:accessor screen
|
||||
:documentation "A reference to the main window (the screen)")
|
||||
(style
|
||||
:initarg :style
|
||||
:initform nil
|
||||
:accessor style
|
||||
:documentation "The visual style of the window")))
|
||||
|
||||
(defmethod refresh-config :after ((object follow-requests-window))
|
||||
(with-accessors ((screen screen)
|
||||
(croatoan-window croatoan-window)
|
||||
(bgcolor bgcolor)
|
||||
(fgcolor fgcolor)
|
||||
(top-row-padding top-row-padding)
|
||||
(header-message-lines header-message-lines)
|
||||
(style style)) object
|
||||
(let* ((theme-style (swconf:form-style swconf:+key-input-dialog+))
|
||||
(fg (swconf:foreground theme-style))
|
||||
(bg (swconf:background theme-style))
|
||||
(width (truncate (/ (win-width screen)
|
||||
3)))
|
||||
(height (truncate (/ (win-height screen)
|
||||
3)))
|
||||
(y (truncate (- (/ (win-height screen) 2)
|
||||
(/ height 2))))
|
||||
(x (truncate (- (/ (win-width screen) 2)
|
||||
(/ width 2)))))
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background bg))
|
||||
(setf (bgcolor croatoan-window) bg)
|
||||
(setf (fgcolor croatoan-window) fg)
|
||||
(setf style theme-style)
|
||||
(win-resize object width height)
|
||||
(win-move object x y)
|
||||
(let* ((header (_ "Please evaluate the following requests, only items shown below will be accepted, deleted ones will be rejected:"))
|
||||
(header-words (text-utils:split-words header))
|
||||
(header-lines (text-utils:flush-left-mono-text header-words
|
||||
(win-width-no-border object)))
|
||||
(attach-y-start (1+ (length header-lines))))
|
||||
(setf top-row-padding attach-y-start)
|
||||
(setf header-message-lines header-lines))
|
||||
object)))
|
||||
|
||||
(defmethod draw :after ((object follow-requests-window))
|
||||
(with-accessors ((style style)
|
||||
(header-message-lines header-message-lines)) object
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(let* ((bgcolor (bgcolor croatoan-window))
|
||||
(fgcolor (fgcolor croatoan-window))
|
||||
(win-width (win-width-no-border object)))
|
||||
(loop
|
||||
for y from 1
|
||||
for line in header-message-lines do
|
||||
(print-text object
|
||||
(text-utils:right-padding line win-width)
|
||||
1 y
|
||||
:fgcolor fgcolor
|
||||
:bgcolor bgcolor
|
||||
:attributes (attribute-bold)))))))
|
||||
|
||||
(defun init (follow-requests usernames-follow-requests screen)
|
||||
"Initialize the window
|
||||
|
||||
- follows-requests the account entity (from tooter library) that requestes to follow you
|
||||
- username-follow-requests the username of the accounts that requestes to follow you
|
||||
- screen the main window
|
||||
"
|
||||
(flet ((make-rows (usernames bg fg)
|
||||
(mapcar (lambda (username)
|
||||
(make-instance 'line
|
||||
:normal-text username
|
||||
:selected-text username
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg))
|
||||
usernames)))
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *follow-requests-window*
|
||||
(make-instance 'follow-requests-window
|
||||
:requests follow-requests
|
||||
:uses-border-p t
|
||||
:screen screen
|
||||
:keybindings keybindings:*follow-requests-keymap*
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *follow-requests-window*)
|
||||
(setf (rows *follow-requests-window*)
|
||||
(make-rows usernames-follow-requests
|
||||
(bgcolor low-level-window)
|
||||
(fgcolor low-level-window)))
|
||||
(setf (row-selected-index *follow-requests-window*) 0)
|
||||
*follow-requests-window*)))
|
||||
|
||||
(defun process-requests ()
|
||||
"Process the accepted or follow' requests, the accepted are the
|
||||
requeste that are not be erased from the window (see the class
|
||||
row-oriented-widget)"
|
||||
(with-accessors ((all-accounts requests)
|
||||
(rows rows)) specials:*follow-requests-window*
|
||||
(let* ((accepted-usernames (mapcar #'normal-text rows))
|
||||
(accepted-accounts (remove-if-not (lambda (acc)
|
||||
(find-if (lambda (a)
|
||||
(string= a
|
||||
(tooter:account-name acc)))
|
||||
accepted-usernames))
|
||||
all-accounts))
|
||||
(rejected-accounts (set-difference all-accounts
|
||||
accepted-accounts
|
||||
:key #'tooter:id
|
||||
:test #'string=)))
|
||||
(loop for accepted-account in accepted-accounts do
|
||||
(let ((id (tooter:id accepted-account)))
|
||||
(api-client:accept-follow-request id)))
|
||||
(loop for rejected-account in rejected-accounts do
|
||||
(let ((id (tooter:id rejected-account)))
|
||||
(api-client:reject-follow-request id))))))
|
|
@ -0,0 +1,83 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :hooks)
|
||||
|
||||
(defvar *hook* nil
|
||||
"The hook currently being run.")
|
||||
|
||||
(defgeneric add-hook (hook fn &key append)
|
||||
(:documentation "Add FN to the value of HOOK.")
|
||||
(:method ((hook symbol) fn &key append)
|
||||
(declare (type (or function symbol) fn))
|
||||
(if (not append)
|
||||
(pushnew fn (symbol-value hook))
|
||||
(unless (member fn (symbol-value hook))
|
||||
(appendf (symbol-value hook) (list fn))))))
|
||||
|
||||
(defgeneric remove-hook (hook fn)
|
||||
(:documentation "Remove FN from the symbol value of HOOK.")
|
||||
(:method ((hook symbol) fn)
|
||||
(removef (symbol-value hook) fn)))
|
||||
|
||||
(defmacro with-hook-restart (&body body)
|
||||
`(with-simple-restart (continue "Call next function in hook ~s" *hook*)
|
||||
,@body))
|
||||
|
||||
(defun run-hooks (&rest hooks)
|
||||
"Run all the hooks in HOOKS, without arguments.
|
||||
The variable `*hook*' is bound to the name of each hook as it is being
|
||||
run."
|
||||
(dolist (*hook* hooks)
|
||||
(run-hook *hook*)))
|
||||
|
||||
(defgeneric run-hook (hook &rest args)
|
||||
(:documentation "Apply each function in HOOK to ARGS.")
|
||||
(:method ((*hook* symbol) &rest args)
|
||||
(dolist (fn (symbol-value *hook*))
|
||||
(with-hook-restart
|
||||
(apply fn args)))))
|
||||
|
||||
(defgeneric run-hook-until-failure (hook &rest args)
|
||||
(:documentation "Like `run-hook-with-args', but quit once a function returns nil.")
|
||||
(:method ((*hook* symbol) &rest args)
|
||||
(loop
|
||||
for fn in (symbol-value *hook*)
|
||||
always (apply fn args))))
|
||||
|
||||
(defgeneric run-hook-until-success (hook &rest args)
|
||||
(:documentation "Like `run-hook-with-args', but quit once a function returns
|
||||
non-nil.")
|
||||
(:method ((*hook* symbol) &rest args)
|
||||
(loop
|
||||
for fn in (symbol-value *hook*)
|
||||
thereis (apply fn args))))
|
||||
|
||||
(defparameter *before-main-loop* ()
|
||||
"run this hooks before UI main loop starts")
|
||||
|
||||
(defparameter *before-quit* ()
|
||||
"Run this hooks just before closing the database connection and the
|
||||
program")
|
||||
|
||||
(defparameter *before-prepare-for-rendering-message* '()
|
||||
"Run this hooks before rendering the message on a
|
||||
message-window (the message window is passed as parameter")
|
||||
|
||||
(defparameter *before-sending-message* '()
|
||||
"Run this hooks before sending the message, note that the message
|
||||
could be encrypted after this hooks runs, the function takes a
|
||||
message-window as parameter")
|
|
@ -0,0 +1,145 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :html-utils)
|
||||
|
||||
(define-constant +tag-link+ "a" :test #'string=)
|
||||
|
||||
(define-constant +tag-break+ "br" :test #'string=)
|
||||
|
||||
(define-constant +tag-paragraph+ "p" :test #'string=)
|
||||
|
||||
(define-constant +tag-div+ "div" :test #'string=)
|
||||
|
||||
(define-constant +attribute-url+ "href" :test #'string=)
|
||||
|
||||
(defun tag (node)
|
||||
"Given a node returns the tag part"
|
||||
(first node))
|
||||
|
||||
(defun attributes (node)
|
||||
"Given a node returns the attribute part"
|
||||
(second node))
|
||||
|
||||
(defun attribute-key (attribute)
|
||||
"Given an attribute the key part"
|
||||
(first attribute))
|
||||
|
||||
(defun attribute-value (attribute)
|
||||
"Given an attribute the value part"
|
||||
(second attribute))
|
||||
|
||||
(defun children (node)
|
||||
"Return children of this nodes if exists"
|
||||
(when (and node
|
||||
(listp node)
|
||||
(> (length node)
|
||||
2))
|
||||
(subseq node 2)))
|
||||
|
||||
(defun tag= (tag node)
|
||||
(string-equal tag (tag node)))
|
||||
|
||||
(defun find-attribute (attribute-key node)
|
||||
"find attribute on a node"
|
||||
(find-if (lambda (attribute)
|
||||
(string= attribute-key
|
||||
(attribute-key attribute)))
|
||||
(attributes node)))
|
||||
|
||||
(defun html->text (html &key (add-link-footnotes t))
|
||||
"Transform html to text, note that if `add-link-footnotes` is non nil footnotes that marks html link in the text are added aftere the body of the message
|
||||
|
||||
This function uses a library that transhorm html5 text into s-expressions um the form
|
||||
|
||||
'(name (attributes) children*)
|
||||
|
||||
Some convenience functions are provided to works with this structures.
|
||||
"
|
||||
(when html
|
||||
(let ((root (append (list :root
|
||||
nil)
|
||||
(html5-parser:parse-html5-fragment html :dom :xmls)))
|
||||
(link-count 0)
|
||||
(body (misc:make-fresh-array 0 #\a 'character nil))
|
||||
(footnotes (misc:make-fresh-array 0 #\a 'character nil)))
|
||||
(with-output-to-string (body-stream body)
|
||||
(with-output-to-string (footnotes-stream footnotes)
|
||||
(format footnotes-stream "~2%")
|
||||
(labels ((descend-children (node)
|
||||
(loop for child in (children node) do
|
||||
(descend child)))
|
||||
(descend (node)
|
||||
(when node
|
||||
(cond
|
||||
((stringp node)
|
||||
(princ node body-stream))
|
||||
((consp (car node))
|
||||
(descend (car node)))
|
||||
((tag= +tag-link+ node)
|
||||
(let ((link (find-attribute +attribute-url+ node)))
|
||||
(incf link-count)
|
||||
(if link
|
||||
(format footnotes-stream
|
||||
"[~a] ~a~%"
|
||||
link-count
|
||||
(attribute-value link))
|
||||
(format footnotes-stream
|
||||
"[~a] ~a~%"
|
||||
link-count
|
||||
(_ "No address found")))
|
||||
(descend-children node)
|
||||
(when add-link-footnotes
|
||||
(format body-stream "[~a] " link-count))))
|
||||
((tag= +tag-break+ node)
|
||||
(format body-stream "~%")
|
||||
(descend-children node))
|
||||
((or (tag= +tag-paragraph+ node)
|
||||
(tag= +tag-div+ node))
|
||||
(format body-stream "~%")
|
||||
(descend-children node)
|
||||
(format body-stream "~%"))
|
||||
(t
|
||||
(descend-children node))))))
|
||||
(descend root)
|
||||
(if add-link-footnotes
|
||||
(strcat body footnotes)
|
||||
body)))))))
|
||||
|
||||
(defun extract-shotcodes (file)
|
||||
"Extract shotcodes from the file:
|
||||
https://github.com/milesj/emojibase/blob/master/packages/generator/src/resources/shortcodes.ts.
|
||||
Returns an alist (cons shortcode utf8-emoj)"
|
||||
(with-open-file (stream file)
|
||||
(flet ((readline ()
|
||||
(read-line stream nil nil)))
|
||||
(let ((res ()))
|
||||
(loop with i = (readline) while i do
|
||||
(multiple-value-bind (match-emoji-p registers-emoji)
|
||||
(cl-ppcre:scan-to-strings "^\\s+// \(.\) " i)
|
||||
(when match-emoji-p
|
||||
(let ((emoji (first-elt registers-emoji)))
|
||||
(setf i (readline))
|
||||
(multiple-value-bind (match-shortcode-p registers-shortcode)
|
||||
(cl-ppcre:scan-to-strings "\\['\([^']+\)'\(\\]|,\)" i)
|
||||
(when match-shortcode-p
|
||||
(setf res
|
||||
(acons (format nil ":~a:" (first-elt registers-shortcode))
|
||||
(format nil "~a" emoji)
|
||||
res)))))))
|
||||
(setf i (readline)))
|
||||
res))))
|
|
@ -0,0 +1,70 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :interfaces)
|
||||
|
||||
(defgeneric clone (object))
|
||||
|
||||
(defmethod clone (object))
|
||||
|
||||
(defgeneric clone-into (from to))
|
||||
|
||||
(defmethod clone-into (from to))
|
||||
|
||||
(defgeneric copy-flat (object))
|
||||
|
||||
(defmethod copy-flat (object))
|
||||
|
||||
(defgeneric copy-flat-into (from to))
|
||||
|
||||
(defmethod copy-flat-into (from to))
|
||||
|
||||
(defmacro with-simple-clone ((object type))
|
||||
(alexandria:with-gensyms (res)
|
||||
`(let ((,res (make-instance ,type)))
|
||||
(clone-into ,object ,res)
|
||||
,res)))
|
||||
|
||||
(defmacro with-simple-copy-flat ((object type))
|
||||
(alexandria:with-gensyms (res)
|
||||
`(let ((,res (make-instance ,type)))
|
||||
(copy-flat-into ,object ,res)
|
||||
,res)))
|
||||
|
||||
(defgeneric serialize (object))
|
||||
|
||||
(defgeneric serialize-to-stream (object stream))
|
||||
|
||||
(defgeneric deserialize (object file))
|
||||
|
||||
(defmethod serialize (object)
|
||||
(format nil "~s" (marshal:marshal object)))
|
||||
|
||||
(defmethod serialize-to-stream (object stream)
|
||||
(prin1 (marshal:marshal object) stream))
|
||||
|
||||
(defmethod deserialize (object file)
|
||||
(declare (ignore object))
|
||||
(marshal:unmarshal (read-from-string (filesystem-utils:slurp-file file))))
|
||||
|
||||
;; to use with ms:initialize-unmarshalled-instance
|
||||
(defgeneric post-deserialization-fix (object))
|
||||
|
||||
(defmethod post-deserialization-fix (object)
|
||||
object)
|
||||
|
||||
(defmethod post-deserialization-fix ((object (eql nil)))
|
||||
nil)
|
|
@ -0,0 +1,146 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :keybindings-window)
|
||||
|
||||
(defclass keybindings-window (suggestions-window tree-holder)
|
||||
((keybindings-tree
|
||||
:initform nil
|
||||
:initarg :keybindings-tree
|
||||
:accessor keybindings-tree
|
||||
:documentation "The keymap"))
|
||||
(:documentation "A window that suggests next keys in a keymap"))
|
||||
|
||||
(defmethod refresh-config :after ((object keybindings-window))
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(refresh-config-colors object swconf:+key-keybindings-window+)
|
||||
(refresh-config-sizes object swconf:+key-keybindings-window+)
|
||||
(let ((y (- (win-height *main-window*)
|
||||
(win-height object)
|
||||
+command-window-height+)))
|
||||
(win-move object 0 y))))
|
||||
|
||||
(defmethod calculate ((object keybindings-window) dt)
|
||||
(declare (ignore object dt)))
|
||||
|
||||
(defun print-suggestion-tree (window)
|
||||
"Print a text representation of a tree in `window'"
|
||||
(with-accessors ((keybindings-tree keybindings-tree)
|
||||
(paginated-info paginated-info)
|
||||
(current-page current-page)) window
|
||||
(when-window-shown (window)
|
||||
(win-clear window)
|
||||
(win-box window)
|
||||
(when paginated-info
|
||||
(loop
|
||||
for line in (elt paginated-info current-page)
|
||||
for row-count from 1 do
|
||||
(loop
|
||||
for block in line
|
||||
with x = 1 do
|
||||
(print-text window block x row-count)
|
||||
(incf x (text-width block))))))))
|
||||
|
||||
(defmethod draw :after ((object keybindings-window))
|
||||
(labels ((column-size (column)
|
||||
(let ((line (first column)))
|
||||
(loop for block in line sum (text-width block)))))
|
||||
(with-accessors ((keybindings-tree keybindings-tree)
|
||||
(paginated-info paginated-info)
|
||||
(current-page current-page)) object
|
||||
(when-window-shown (object)
|
||||
(win-clear object)
|
||||
(win-box object)
|
||||
(when paginated-info
|
||||
(loop
|
||||
for column in (elt paginated-info current-page)
|
||||
with column-count = 1
|
||||
do
|
||||
(let ((column-size (column-size column)))
|
||||
(loop
|
||||
for row in column
|
||||
with row-count = 1 do
|
||||
(loop
|
||||
for block in row
|
||||
with x = 1 do
|
||||
(print-text object block (+ x column-count) row-count)
|
||||
(incf x (text-width block)))
|
||||
(incf row-count))
|
||||
(incf column-count column-size)))
|
||||
(draw-pagination-info object))
|
||||
(win-refresh object)))))
|
||||
|
||||
(defun build-data-for-print (data)
|
||||
(keybindings:humanize-key data))
|
||||
|
||||
(defun build-tree-batches (window tree)
|
||||
"Split the tree in column to fit the window height and pages to fit window width"
|
||||
(with-accessors ((render-arrow-value render-arrow-value)
|
||||
(render-leaf-value render-leaf-value)
|
||||
(render-branch-value render-branch-value)
|
||||
(render-spacer-value render-spacer-value)
|
||||
(render-vertical-line-value render-vertical-line-value)) window
|
||||
(when-let* ((tree-lines (tree->annotated-lines tree
|
||||
:print-data-fn #'build-data-for-print
|
||||
:arrow-char render-arrow-value
|
||||
:spacer-child render-spacer-value
|
||||
:child-char render-branch-value
|
||||
:line-char render-vertical-line-value
|
||||
:last-child-char render-leaf-value
|
||||
:print-data t))
|
||||
(batches (text-utils:box-fit-multiple-column-annotated tree-lines
|
||||
(- (win-width window) 2)
|
||||
(- (win-height window)
|
||||
+box-height-diff+))))
|
||||
(with-accessors ((tree-color-map tree-color-map)) window
|
||||
(let ((colorized-batches (loop for batch in batches collect
|
||||
(loop for column in batch collect
|
||||
(loop for line in column collect
|
||||
(colorize-tree-line line
|
||||
tree-color-map))))))
|
||||
colorized-batches)))))
|
||||
|
||||
(defmethod update-suggestions ((object keybindings-window) hint
|
||||
&key
|
||||
(tree nil)
|
||||
&allow-other-keys)
|
||||
"Expand the keybinding tree starting from node `hint' in slot `keybindings-tree'.
|
||||
|
||||
if tree is nil set the slot `keybindings-tree' to `tree'.
|
||||
"
|
||||
(with-accessors ((keybindings-tree keybindings-tree)
|
||||
(paginated-info paginated-info)
|
||||
(current-page current-page)) object
|
||||
(when tree
|
||||
(setf keybindings-tree tree))
|
||||
(when hint
|
||||
(let ((res (find-keymap-node hint keybindings-tree)))
|
||||
(when (typep res 'mtree:m-tree)
|
||||
(setf keybindings-tree res)
|
||||
(when-let* ((paginated (build-tree-batches object keybindings-tree)))
|
||||
(setf paginated-info paginated)
|
||||
(setf current-page 0)))
|
||||
res))))
|
||||
|
||||
(defun init ()
|
||||
"Initialize the window"
|
||||
(let* ((low-level-window (make-croatoan-window :draw-border t))
|
||||
(high-level-window (make-instance 'keybindings-window
|
||||
:key-config swconf:+key-keybindings-window+
|
||||
:croatoan-window low-level-window)))
|
||||
(refresh-config high-level-window)
|
||||
(win-hide high-level-window)
|
||||
high-level-window))
|
|
@ -0,0 +1,507 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :keybindings)
|
||||
|
||||
(define-constant +control-prefix+ "^" :test #'string=)
|
||||
|
||||
(define-constant +meta-prefix+ "ALT-" :test #'string=)
|
||||
|
||||
(define-constant +function-placeholder-re+ "function-placeholder" :test #'string=)
|
||||
|
||||
(define-constant +function-placeholder-value+ :fn :test #'string=)
|
||||
|
||||
(defparameter *default-prefix* nil)
|
||||
|
||||
;; KEYPATH := KEY KEYPATH
|
||||
;; | epsilon
|
||||
;; KEY := COMMAND-KEY
|
||||
;; | META-KEY
|
||||
;; | KEYCODE
|
||||
;; | FUNCTION-PLACEHOLDER
|
||||
;; COMMAND-KEY := ( (COMMAND-MOD-1 DASH) | COMMAND-MOD-2 ) KEYCODE
|
||||
;; META-KEY := META-MOD DASH KEYCODE
|
||||
;; KEYCODE := NON-PRINTABLE-KEY | SIMPLE-KEY
|
||||
;; SIMPLE-KEY := CHAR
|
||||
;; NON-PRINTABLE-KEY := |'f1'
|
||||
;; |'f2'
|
||||
;; |'f3'
|
||||
;; |'f4'
|
||||
;; |'f5'
|
||||
;; |'f6'
|
||||
;; |'f7'
|
||||
;; |'f8'
|
||||
;; |'f9'
|
||||
;; |'f10
|
||||
;; | 'right'
|
||||
;; | 'left'
|
||||
;; | 'up'
|
||||
;; | 'down'
|
||||
;; | 'home'
|
||||
;; | 'end'
|
||||
;; | 'npage'
|
||||
;; | 'ppage'
|
||||
;; | "dc"
|
||||
;; COMMAND-MOD-1 := 'C'
|
||||
;; COMMAND-MOD-2 := '^'
|
||||
;; META-MOD := 'M'
|
||||
;; DASH := '-'
|
||||
;; CHAR := ESCAPED-CHAR | (not ( DASH | BLANK))
|
||||
;; BLANKS := BLANK*
|
||||
;; BLANK := #\space | #\Newline | #\Tab
|
||||
;; FUNCTION-PLACEHOLDER := 'function-placeholder'
|
||||
|
||||
|
||||
(defrule function-placeholder "function-placeholder"
|
||||
(:constant +function-placeholder-value+))
|
||||
|
||||
(defrule blank (or #\space #\Newline #\Tab)
|
||||
(:constant nil))
|
||||
|
||||
(defrule blanks (* blank)
|
||||
(:constant nil))
|
||||
|
||||
(defrule escaped-character (and #\\ character)
|
||||
(:function (lambda (a) (list (second a)))))
|
||||
|
||||
(defrule dash #\-
|
||||
(:text t))
|
||||
|
||||
(defrule non-printable-key
|
||||
(or "f10"
|
||||
"f1"
|
||||
"f2"
|
||||
"f3"
|
||||
"f4"
|
||||
"f5"
|
||||
"f6"
|
||||
"f7"
|
||||
"f8"
|
||||
"f9"
|
||||
"right"
|
||||
"left"
|
||||
"up"
|
||||
"down"
|
||||
"home"
|
||||
"end"
|
||||
"npage" ; page down
|
||||
"ppage" ; page up
|
||||
"dc") ; canc
|
||||
(:text t)
|
||||
(:function string-upcase))
|
||||
|
||||
(defrule char
|
||||
(or escaped-character
|
||||
(not (or dash blank)))
|
||||
(:text t))
|
||||
|
||||
(defrule meta-mod #\M
|
||||
(:text t))
|
||||
|
||||
(defrule command-mod-1 #\C)
|
||||
|
||||
(defrule command-mod-2 #\^)
|
||||
|
||||
(defrule simple-key char)
|
||||
|
||||
(defrule keycode (or non-printable-key simple-key)) ; keep the order
|
||||
|
||||
(defun to-meta-code-string (command)
|
||||
(strcat +meta-prefix+ (string-upcase (third command))))
|
||||
|
||||
(defrule meta-key
|
||||
(and meta-mod dash keycode)
|
||||
(:function to-meta-code-string)
|
||||
(:text t))
|
||||
|
||||
(defun to-control-code-string (command)
|
||||
(strcat +control-prefix+ (string-upcase (third command))))
|
||||
|
||||
;; not part of the actual grammar, just syntactic sugar
|
||||
|
||||
(defrule command-key-1
|
||||
(and command-mod-1 dash keycode)
|
||||
(:function to-control-code-string))
|
||||
|
||||
(defrule command-key-2
|
||||
(and command-mod-2 keycode)
|
||||
(:text t))
|
||||
|
||||
(defrule command-key
|
||||
(or command-key-1
|
||||
command-key-2))
|
||||
|
||||
(defrule key
|
||||
(and (or function-placeholder ; keep the order
|
||||
command-key
|
||||
meta-key
|
||||
keycode)
|
||||
(? blanks))
|
||||
(:function first))
|
||||
|
||||
(defrule keypath
|
||||
(and key (? keypath))
|
||||
(:function (lambda (a) (remove-if-null (flatten a)))))
|
||||
|
||||
(defun make-starting-comand-tree ()
|
||||
(make-command-tree *default-prefix*))
|
||||
|
||||
(defun make-command-tree (data)
|
||||
(make-instance 'sorted-m-tree
|
||||
:data data
|
||||
:compare-fn #'string<))
|
||||
|
||||
(defun parse-keypath (keypath &key (existing-tree (make-starting-comand-tree)))
|
||||
"Parse a string representing a list of keys (see `*global-keymap*'),
|
||||
produces a tree and graft the latter on `existing-tree'"
|
||||
(labels ((placeholderp (child)
|
||||
(eq +function-placeholder-value+
|
||||
(data child)))
|
||||
(remove-function-siblings (node)
|
||||
(top-down-visit node
|
||||
(lambda (a)
|
||||
(if (find-if #'placeholderp (children a))
|
||||
(setf (children a)
|
||||
(remove-if-not #'placeholderp (children a)))))))
|
||||
(remove-function-children (node)
|
||||
(top-down-visit node
|
||||
(lambda (a)
|
||||
(when (placeholderp a)
|
||||
(remove-all-children a)))))
|
||||
(add (tree commands)
|
||||
(if commands
|
||||
(let ((new-node (make-command-tree (first commands))))
|
||||
(add-child tree new-node)
|
||||
(add new-node (rest commands)))
|
||||
nil)))
|
||||
(let ((raw (parse 'keypath (strcat keypath " " +function-placeholder-re+)))
|
||||
(new-tree (make-starting-comand-tree)))
|
||||
(add new-tree raw)
|
||||
(graft-branch existing-tree
|
||||
new-tree
|
||||
:test (lambda (a b)
|
||||
(cond
|
||||
((functionp a)
|
||||
t)
|
||||
((eq b +function-placeholder-value+)
|
||||
t)
|
||||
(t
|
||||
(string= a b)))))
|
||||
(remove-function-siblings existing-tree)
|
||||
(remove-function-children existing-tree)
|
||||
existing-tree)))
|
||||
|
||||
(defparameter *global-keymap* (make-starting-comand-tree)
|
||||
"The global keymap.
|
||||
|
||||
A keymap looks like a tree with function as leaf nodes like:
|
||||
|
||||
a
|
||||
/ \
|
||||
b c
|
||||
/ / \
|
||||
#'+ b d
|
||||
/ \
|
||||
#'* #'-
|
||||
|
||||
So, pressing a sequence of 'a -> c -> d' will trigger the function #'-
|
||||
|
||||
")
|
||||
|
||||
(defparameter *thread-keymap* (make-starting-comand-tree)
|
||||
"The keymap for thread window.")
|
||||
|
||||
(defparameter *message-keymap* (make-starting-comand-tree)
|
||||
"The keymap for message window.")
|
||||
|
||||
(defparameter *tags-keymap* (make-starting-comand-tree)
|
||||
"The keymap for tags window.")
|
||||
|
||||
(defparameter *conversations-keymap* (make-starting-comand-tree)
|
||||
"The keymap for conversations windows.")
|
||||
|
||||
(defparameter *send-message-keymap* (make-starting-comand-tree)
|
||||
"The keymap for window to confirm sending a message.")
|
||||
|
||||
(defparameter *follow-requests-keymap* (make-starting-comand-tree)
|
||||
"The keymap for window to accept follow requests.")
|
||||
|
||||
(defparameter *open-attach-keymap* (make-starting-comand-tree)
|
||||
"The keymap for window to open message's attachments.")
|
||||
|
||||
(defun define-key (key-sequence function &optional (existing-keymap *global-keymap*))
|
||||
"Define a key sequence that trigger a function:
|
||||
|
||||
The syntax is a simple list of keys or a single key:
|
||||
|
||||
- key [key ...]
|
||||
|
||||
where key can be either a printable character, a
|
||||
<control>-character code or ALT-character. A character code can be
|
||||
defined using caret notation: '^character' (e.g. '^A') or using
|
||||
'C-' as placeholder for <control> (e.g. 'C-A').
|
||||
|
||||
The allowed character in alt-character code are downcase only.
|
||||
|
||||
Please note that this function will modify existing keymap.
|
||||
"
|
||||
(let* ((tree (parse-keypath key-sequence :existing-tree existing-keymap))
|
||||
(placeholder (find-child tree +function-placeholder-value+ :compare #'eq)))
|
||||
(assert placeholder)
|
||||
(assert (functionp function))
|
||||
(setf (data placeholder) function)
|
||||
tree))
|
||||
|
||||
(define-constant +croatoan-last-standard-key+ 511 :test #'=)
|
||||
|
||||
(defclass encoded-map-entry ()
|
||||
((terminal-code
|
||||
:initform nil
|
||||
:initarg :terminal-code
|
||||
:accessor terminal-code)
|
||||
(croatoan-code
|
||||
:initform nil
|
||||
:initarg :croatoan-code
|
||||
:accessor croatoan-code)
|
||||
(curses-code
|
||||
:initform nil
|
||||
:initarg :curses-code
|
||||
:accessor curses-code))
|
||||
(:documentation "terminal-code: the raw string that the terminal provide to encode a key
|
||||
croatoan-code: ther symbol croatoan use and returns to usercode
|
||||
curses-keycode: integer that map terminal-code
|
||||
(used internally by croatoan to match croatoan-code
|
||||
Example: \"^[1\" :alt-1 512.
|
||||
Note that \"^]\" is the character #\Esc"))
|
||||
|
||||
(defmethod print-object ((object encoded-map-entry) stream)
|
||||
(print-unreadable-object (object stream :type t :identity nil)
|
||||
(with-accessors ((terminal-code terminal-code)
|
||||
(croatoan-code croatoan-code)
|
||||
(curses-code curses-code)) object
|
||||
(format stream "~s ~s ~s" terminal-code croatoan-code curses-code))))
|
||||
|
||||
(defun make-encoded-map-entry (terminal-code croatoan-code curses-code)
|
||||
(make-instance 'encoded-map-entry
|
||||
:terminal-code terminal-code
|
||||
:croatoan-code croatoan-code
|
||||
:curses-code curses-code))
|
||||
|
||||
(defun term-escaped-sequence (&rest chars)
|
||||
(append (list #\Esc)
|
||||
chars))
|
||||
|
||||
(defun other-codes ()
|
||||
"Others keycode (the terminal encoding is non standard and will requires tweak).
|
||||
Values a list of `encoded-map-entry'"
|
||||
(flet ((collect-codes (from-ascii-code below-ascii-code from-curses-code)
|
||||
(loop
|
||||
for character-code from from-ascii-code below below-ascii-code by 1
|
||||
for curses-code from from-curses-code by 1 collect
|
||||
(let ((char (code-char character-code)))
|
||||
(make-encoded-map-entry (format nil "~a~a" #\Esc char) ; terminal
|
||||
(format-keyword (format nil "alt-~a" char)) ; croatoan
|
||||
curses-code))))) ; curses
|
||||
(let* ((number-and-symbols (collect-codes 33 65 (1+ +croatoan-last-standard-key+)))
|
||||
(last-curses-code (curses-code (last-elt number-and-symbols)))
|
||||
(characters (collect-codes 97
|
||||
127
|
||||
(1+ last-curses-code))))
|
||||
(setf last-curses-code (curses-code (last-elt characters)))
|
||||
(let ((misc (list (make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\3 #\D)
|
||||
:alt-left
|
||||
(+ 1 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\3 #\A)
|
||||
:alt-up
|
||||
(+ 2 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\3 #\C)
|
||||
:alt-right
|
||||
(+ 3 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\3 #\B)
|
||||
:alt-down
|
||||
(+ 4 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\5 #\D)
|
||||
:control-left
|
||||
(+ 5 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\5 #\A)
|
||||
:control-up
|
||||
(+ 6 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\5 #\C)
|
||||
:control-right
|
||||
(+ 7 last-curses-code))
|
||||
(make-encoded-map-entry (term-escaped-sequence #\[ #\1 #\; #\5 #\B)
|
||||
:control-down
|
||||
(+ 8 last-curses-code)))))
|
||||
(append number-and-symbols characters misc)))))
|
||||
|
||||
(defparameter *local-key-alist* (other-codes))
|
||||
|
||||
(defun add-keymap-to-curses-db ()
|
||||
"Remove nonstandard key form 'croatoan:*key-alist*' and add ours
|
||||
instead"
|
||||
(loop for keymapping in *local-key-alist* do
|
||||
(croatoan:define-function-key (croatoan-code keymapping) (terminal-code keymapping)
|
||||
:key-code (curses-code keymapping))))
|
||||
|
||||
(defun init-keyboard-mapping ()
|
||||
(add-keymap-to-curses-db))
|
||||
|
||||
(defun find-keymap-node (key tree)
|
||||
"Find `key' in keymap `tree'.
|
||||
Returns the node of the tree or a function if the node found node
|
||||
has a single children (the function to call).
|
||||
If `key' can not be found returns nil.
|
||||
"
|
||||
(when-let* ((node-pos (num:binary-search (children tree)
|
||||
(make-node key)
|
||||
:compare-fn (lambda (a b)
|
||||
(string< (data a) (data b)))
|
||||
:equal-fn (lambda (a b)
|
||||
(string= (data a) (data b)))))
|
||||
(node (interfaces:clone (elt (children tree) node-pos))))
|
||||
(assert (or (null node)
|
||||
(not (leafp node))))
|
||||
(setf (parent node) nil)
|
||||
(let* ((child-with-function-to-call (find-if (lambda (child) (functionp (data child)))
|
||||
(children node)))
|
||||
(function-to-call (and child-with-function-to-call
|
||||
(data child-with-function-to-call))))
|
||||
(or function-to-call
|
||||
node))))
|
||||
|
||||
(defun humanize-key (key)
|
||||
"Transform a node of the keymap in something that humans can easly
|
||||
understand"
|
||||
(cond
|
||||
((functionp key)
|
||||
(if (documentation key t)
|
||||
(with-input-from-string (stream (documentation key t))
|
||||
(read-line stream))
|
||||
(function-name key)))
|
||||
((string= key "^J")
|
||||
(_ "Enter"))
|
||||
((string= key "DC")
|
||||
(_ "Delete"))
|
||||
((string= key "NPAGE")
|
||||
(_ "Page-up"))
|
||||
((string= key "PPAGE")
|
||||
(_ "Page-down"))
|
||||
(t
|
||||
(to-s key))))
|
||||
|
||||
(defun key-paths (keymapping-tree)
|
||||
"Transform a keymap tree in a list of path from root of the tree to
|
||||
each leaf (as strings)"
|
||||
(let ((all-texts ())
|
||||
(all-functions ()))
|
||||
(labels ((collect-fn (&optional (starting-path ()))
|
||||
(let ((path (copy-list starting-path)))
|
||||
(lambda (node)
|
||||
(let ((data (data node)))
|
||||
(push data path)
|
||||
(if (functionp data)
|
||||
(progn
|
||||
(push (reverse path) all-texts)
|
||||
(push data all-functions))
|
||||
path)))))
|
||||
(collect (node &optional (collector (collect-fn)))
|
||||
(let* ((path-so-far (funcall collector node)))
|
||||
(do-children (child node)
|
||||
(let ((new-collector (collect-fn path-so-far)))
|
||||
(collect child new-collector)))))
|
||||
(humanize-paths (paths)
|
||||
(loop for path in paths collect
|
||||
(mapcar #'humanize-key (rest path))))
|
||||
(build-string (paths)
|
||||
(mapcar (lambda (a) (format nil "~a" (join-with-strings a " ")))
|
||||
paths)))
|
||||
(collect keymapping-tree)
|
||||
(mapcar #'make-help-fields
|
||||
(build-string (humanize-paths all-texts))
|
||||
all-functions))))
|
||||
|
||||
(defun help-fields-get-function (fields)
|
||||
(getf fields :function))
|
||||
|
||||
(defun help-fields-get-text (fields)
|
||||
(getf fields :text))
|
||||
|
||||
(defun make-help-fields (text function)
|
||||
(list :text text :function function))
|
||||
|
||||
(defun help-expand (x fields)
|
||||
"Expands an entry in quick help window (see function `print-help'
|
||||
and `make-blocking-list-dialog-window') showing the full docstring for a command"
|
||||
(declare (ignore x))
|
||||
(when-let* ((function (help-fields-get-function fields))
|
||||
(bg (swconf:win-bg swconf:+key-help-dialog+))
|
||||
(fg (swconf:win-fg swconf:+key-help-dialog+)))
|
||||
(windows:make-blocking-message-dialog specials:*main-window*
|
||||
nil
|
||||
(function-name function)
|
||||
(if (string-not-empty-p (documentation function t))
|
||||
(split-lines (documentation function t))
|
||||
(list (_ "No documentation available, you can help! :-)")))
|
||||
bg
|
||||
fg)))
|
||||
|
||||
(defun print-help (main-window)
|
||||
"Generate an help text for the focused window and main window"
|
||||
(multiple-value-bind (header-bg header-fg attribute-header)
|
||||
(swconf:quick-help-header-colors)
|
||||
(labels ((colorize-header (a)
|
||||
(tui-utils:make-tui-string a
|
||||
:fgcolor header-fg
|
||||
:bgcolor header-bg
|
||||
:attributes attribute-header))
|
||||
(sort-help (help-values)
|
||||
(sort help-values
|
||||
(lambda (a b)
|
||||
(let* ((text-a (help-fields-get-text a))
|
||||
(text-b (help-fields-get-text b))
|
||||
(alpha-a-p (scan "(?i)^[a-z]" text-a))
|
||||
(alpha-b-p (scan "(?i)^[a-z]" text-b)))
|
||||
(cond
|
||||
((and alpha-a-p
|
||||
alpha-b-p)
|
||||
(string< text-a text-b))
|
||||
(alpha-a-p
|
||||
t)
|
||||
(alpha-b-p
|
||||
nil)
|
||||
(t
|
||||
(string< text-a text-b))))))))
|
||||
(when-let* ((focused-keybindings (main-window:focused-keybindings main-window))
|
||||
(global-help (sort-help (key-paths *global-keymap*)))
|
||||
(header-focused (colorize-header (_ "Focused window keys")))
|
||||
(header-global (colorize-header (_ "Global keys")))
|
||||
(focused-help (sort-help (key-paths focused-keybindings)))
|
||||
(global-header-fields (make-help-fields header-global nil))
|
||||
(focused-header-fields (make-help-fields header-focused nil))
|
||||
(fields (list focused-header-fields)))
|
||||
(setf fields
|
||||
(append fields
|
||||
focused-help
|
||||
(list global-header-fields)
|
||||
global-help))
|
||||
(let ((all-lines (mapcar #'help-fields-get-text
|
||||
fields)))
|
||||
(line-oriented-window:make-blocking-list-dialog-window specials:*main-window*
|
||||
fields
|
||||
all-lines
|
||||
#'help-expand
|
||||
(_ "Quick help")))))))
|
|
@ -0,0 +1,333 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :line-oriented-window)
|
||||
|
||||
(defclass line ()
|
||||
((selected-fg
|
||||
:initform :black
|
||||
:initarg :selected-fg
|
||||
:accessor selected-fg
|
||||
:documentation "The foreground color for a selected line")
|
||||
(selected-bg
|
||||
:initform :cyan
|
||||
:initarg :selected-bg
|
||||
:accessor selected-bg
|
||||
:documentation "The background color for a selected line")
|
||||
(normal-fg
|
||||
:initform :cyan
|
||||
:initarg :normal-fg
|
||||
:accessor normal-fg
|
||||
:documentation "The foreground color for a line")
|
||||
(normal-bg
|
||||
:initform :black
|
||||
:initarg :normal-bg
|
||||
:accessor normal-bg
|
||||
:documentation "The background color for a line")
|
||||
(normal-text
|
||||
:initform (make-tui-string "...")
|
||||
:initarg :normal-text
|
||||
:accessor normal-text
|
||||
:documentation "The actual not selected text")
|
||||
(selected-text
|
||||
:initform (make-tui-string "...")
|
||||
:initarg :selected-text
|
||||
:accessor selected-text
|
||||
:documentation "The actual selected text")
|
||||
(deleted-text
|
||||
:initform (make-tui-string "...")
|
||||
:initarg :deleted-text
|
||||
:accessor deleted-text
|
||||
:documentation "The actual deleted text ")
|
||||
(fields
|
||||
:initform ()
|
||||
:initarg :fields
|
||||
:accessor fields
|
||||
:documentation "A generic plist of useful informations for the window")
|
||||
(index
|
||||
:initform 0
|
||||
:initarg :index
|
||||
:accessor index
|
||||
:documentation "The index of this line in the window")
|
||||
(selected
|
||||
:initform nil
|
||||
:initarg :selected
|
||||
:reader selectedp
|
||||
:writer (setf selected)
|
||||
:documentation "Non nil if this line is selected state"))
|
||||
(:documentation "This class represents a single line in a row-oriented-widget"))
|
||||
|
||||
(defclass row-oriented-widget ()
|
||||
((rows
|
||||
:initform ()
|
||||
:initarg :rows
|
||||
:accessor rows
|
||||
:documentation "The rows of data for this widget")
|
||||
(row-selected-index
|
||||
:initform -1
|
||||
:initarg :row-selected-index
|
||||
:accessor row-selected-index
|
||||
:documentation "The index of the selected row")
|
||||
(single-row-height
|
||||
:initform 1
|
||||
:initarg :single-row-height
|
||||
:accessor single-row-height
|
||||
:documentation "The height of a row (in character)")
|
||||
(top-row-padding
|
||||
:initform 0
|
||||
:initarg :top-row-padding
|
||||
:accessor top-row-padding
|
||||
:documentation "the padding from the top of the window and the
|
||||
position where to draw the first line")
|
||||
(current-row-index
|
||||
:initform 0
|
||||
:initarg :current-row-index
|
||||
:accessor current-row-index
|
||||
:documentation "The active line index")
|
||||
(y-current-row
|
||||
:initform 0
|
||||
:initarg :y-current-row
|
||||
:accessor y-current-row
|
||||
:documentation "The active line position"))
|
||||
(:documentation "A widget that holds a selectable list of lines"))
|
||||
|
||||
(defmethod initialize-instance :after ((object row-oriented-widget) &key &allow-other-keys)
|
||||
(with-accessors ((top-row-padding top-row-padding)
|
||||
(y-current-row y-current-row)) object
|
||||
(setf y-current-row top-row-padding)))
|
||||
|
||||
(defmethod (setf top-row-padding) ((object row-oriented-widget) new-padding)
|
||||
(setf (slot-value object 'top-row-padding) new-padding)
|
||||
(setf (slot-value object 'y-current-row) new-padding)
|
||||
object)
|
||||
|
||||
(defgeneric renderizable-rows-data (object))
|
||||
|
||||
(defgeneric unselect-all (object))
|
||||
|
||||
(defgeneric select-row (object index))
|
||||
|
||||
(defgeneric selected-row (object))
|
||||
|
||||
(defgeneric selected-row-fields (object))
|
||||
|
||||
(defgeneric selected-row-delete (object))
|
||||
|
||||
(defgeneric row-move (object amount)
|
||||
(:documentation "Move selected line of 'amount'. 'Amount' can be
|
||||
an integer number, if positive increase the position of the selected
|
||||
line otherwise decrease, relative to current index position.
|
||||
The value is clamped at range [ 0, '(length (rows object)))' ).
|
||||
This function return the number of positions acually moved"))
|
||||
|
||||
(defmethod renderizable-rows-data ((object row-oriented-widget))
|
||||
"Cut from all the lines a slice that that fits into the widget"
|
||||
(with-accessors ((top-row-padding top-row-padding)
|
||||
(current-row-index current-row-index)
|
||||
(row-selected-index row-selected-index)
|
||||
(rows rows)) object
|
||||
(let* ((window-height (if (uses-border-p object)
|
||||
(win-height-no-border object)
|
||||
(win-height object)))
|
||||
(available-rows (- window-height top-row-padding))
|
||||
(selected-top-offset (rem row-selected-index available-rows))
|
||||
(top (- row-selected-index selected-top-offset))
|
||||
(bottom (+ row-selected-index
|
||||
(- available-rows selected-top-offset))))
|
||||
(values (safe-subseq rows top bottom)
|
||||
selected-top-offset))))
|
||||
|
||||
(defmethod unselect-all ((object row-oriented-widget))
|
||||
(loop for row in (rows object) do
|
||||
(setf (selected row) nil))
|
||||
object)
|
||||
|
||||
(defmethod select-row ((object row-oriented-widget) (index number))
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(restart-case
|
||||
(if (or (< index 0)
|
||||
(>= index (length rows)))
|
||||
(error 'conditions:out-of-bounds :idx index :seq rows)
|
||||
(progn
|
||||
(setf row-selected-index index)
|
||||
(setf (selected (elt rows index)) t)))
|
||||
(ignore-selecting-action (e)
|
||||
(declare (ignore e))
|
||||
nil))
|
||||
object))
|
||||
|
||||
(defmethod selected-row ((object row-oriented-widget))
|
||||
"Return the current selected row"
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(when rows
|
||||
(if (<= 0
|
||||
row-selected-index
|
||||
(1- (length rows)))
|
||||
(elt rows row-selected-index)
|
||||
nil))))
|
||||
|
||||
(defmethod selected-row-fields ((object row-oriented-widget))
|
||||
"Return the fields current selected row"
|
||||
(when-let ((selected-row (selected-row object)))
|
||||
(fields selected-row)))
|
||||
|
||||
(defmethod selected-row-delete ((object row-oriented-widget))
|
||||
"delete the selected row"
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(let ((last-was-removed-p (= row-selected-index
|
||||
(1- (length rows)))))
|
||||
(setf rows (remove-if #'selectedp rows))
|
||||
(when rows
|
||||
(if last-was-removed-p
|
||||
(select-row object (1- row-selected-index))
|
||||
(select-row object row-selected-index)))))
|
||||
object)
|
||||
|
||||
(defmethod row-move ((object row-oriented-widget) amount)
|
||||
"Navigate the lines, move the selected row by `amount', returns the
|
||||
actual of rows moved. This can be different from `amount' if moving
|
||||
this exact quantity wold go beyond the length or fows or zero."
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(when (/= 0 amount)
|
||||
(let* ((desired-amount (+ amount row-selected-index))
|
||||
(actual-amount (if (< amount 0)
|
||||
(max (- desired-amount
|
||||
row-selected-index)
|
||||
(- row-selected-index))
|
||||
(- (min desired-amount
|
||||
(1- (length rows)))
|
||||
row-selected-index))))
|
||||
(select-row object (+ row-selected-index
|
||||
actual-amount))
|
||||
actual-amount))))
|
||||
|
||||
(defclass simple-line-navigation-window (wrapper-window row-oriented-widget border-window)
|
||||
((selected-line-bg
|
||||
:initarg :selected-line-bg
|
||||
:accessor selected-line-bg
|
||||
:documentation "The background color for a selected line")
|
||||
(selected-line-fg
|
||||
:initarg :selected-line-fg
|
||||
:accessor selected-line-fg
|
||||
:documentation "The foreground color for a selected line"))
|
||||
(:documentation "A window that displays a navigable list of objects"))
|
||||
|
||||
(defmethod draw :after ((object simple-line-navigation-window))
|
||||
(with-accessors ((uses-border-p uses-border-p)
|
||||
(single-row-height single-row-height)
|
||||
(top-row-padding top-row-padding)) object
|
||||
(let ((max-line-size (if uses-border-p
|
||||
(win-width-no-border object)
|
||||
(win-width object))))
|
||||
(let ((rows (renderizable-rows-data object))
|
||||
(x (if (uses-border-p object)
|
||||
1
|
||||
0)))
|
||||
(loop
|
||||
for y from (1+ top-row-padding) by single-row-height
|
||||
for ct from 0
|
||||
for row in rows do
|
||||
(if (selectedp row)
|
||||
(print-text object
|
||||
(right-pad-text (text-ellipsize (selected-text row)
|
||||
max-line-size)
|
||||
max-line-size)
|
||||
x y
|
||||
:bgcolor (selected-bg row)
|
||||
:fgcolor (selected-fg row))
|
||||
(print-text object
|
||||
(right-pad-text (text-ellipsize (normal-text row)
|
||||
max-line-size)
|
||||
max-line-size)
|
||||
x y
|
||||
:bgcolor (normal-bg row)
|
||||
:fgcolor (normal-fg row))))))))
|
||||
|
||||
(defgeneric resync-rows-db (object &key redraw)
|
||||
(:documentation "Synchronize information table slot of `object` with
|
||||
table in the database, if `redraw` is not nil redraw the object, if
|
||||
possible"))
|
||||
|
||||
(defun make-blocking-list-dialog-window (screen all-fields text-lines callback
|
||||
&optional (title (_ "Information")))
|
||||
"Draw a window with a scrollable list of entries, pressing enter
|
||||
will fire the `callback' function (with the selected field from `all-fields'
|
||||
and text from `text-line'. This window is fitten into `screen' sizes."
|
||||
(assert (length= all-fields text-lines))
|
||||
(let* ((low-level-window (make-blocking-croatoan-window :enable-function-keys t))
|
||||
(window-width (max (+ 4
|
||||
(length title))
|
||||
(+ (find-max-line-width text-lines)
|
||||
2)))
|
||||
(window-height (min (truncate (* 0.9 (win-height screen)))
|
||||
(+ (length text-lines) 2)))
|
||||
(window-x (truncate (- (* 0.5 (win-width screen))
|
||||
(* 0.5 window-width))))
|
||||
(window-y (truncate (- (* 0.5 (win-height screen))
|
||||
(* 0.5 window-height))))
|
||||
(bg (swconf:win-bg swconf:+key-info-dialog+))
|
||||
(fg (swconf:win-fg swconf:+key-info-dialog+))
|
||||
(high-level-window (make-instance 'simple-line-navigation-window
|
||||
:single-row-height 1
|
||||
:uses-border-p t
|
||||
:croatoan-window low-level-window)))
|
||||
(flet ((draw ()
|
||||
(win-clear high-level-window :redraw nil)
|
||||
(draw high-level-window)
|
||||
(win-box high-level-window)
|
||||
(print-text high-level-window title 2 0)))
|
||||
(setf (background low-level-window)
|
||||
(tui:make-background bg))
|
||||
(setf (fgcolor low-level-window)
|
||||
fg)
|
||||
(win-resize high-level-window window-width window-height)
|
||||
(win-move high-level-window window-x window-y)
|
||||
(setf (rows high-level-window)
|
||||
(loop
|
||||
for text in text-lines
|
||||
for fields in all-fields
|
||||
collect
|
||||
(make-instance 'line
|
||||
:fields fields
|
||||
:normal-text text
|
||||
:selected-text text
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg)))
|
||||
(select-row high-level-window 0)
|
||||
(draw)
|
||||
(loop named inner
|
||||
for c = (get-wide-event low-level-window)
|
||||
while (string/= c "q")
|
||||
do
|
||||
(cond
|
||||
((string= c :up)
|
||||
(unselect-all high-level-window)
|
||||
(row-move high-level-window -1))
|
||||
((string= c :down)
|
||||
(unselect-all high-level-window)
|
||||
(row-move high-level-window 1))
|
||||
((string= c #\Newline)
|
||||
(let ((selected-fields (selected-row-fields high-level-window))
|
||||
(selected-text (selected-text (selected-row high-level-window))))
|
||||
(funcall callback selected-text selected-fields))))
|
||||
(draw))
|
||||
(win-close high-level-window))))
|
|
@ -0,0 +1,77 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :main-window)
|
||||
|
||||
(defclass main-window (wrapper-window)
|
||||
((focused-window
|
||||
:initform nil
|
||||
:initarg :focused-window
|
||||
:accessor focused-window
|
||||
:documentation "The window with the focus, only a window can get
|
||||
the focus at the same time"))
|
||||
(:documentation "the main window AKA the screen"))
|
||||
|
||||
(defmethod refresh-config :after ((object main-window))
|
||||
(refresh-config-colors object swconf:+key-main-window+))
|
||||
|
||||
(defmethod calculate ((object main-window) dt)
|
||||
(do-children (child object)
|
||||
(calculate child dt)))
|
||||
|
||||
(defmethod draw ((object main-window))
|
||||
(do-children (child object)
|
||||
(draw child)))
|
||||
|
||||
(defgeneric focused-keybindings (object))
|
||||
|
||||
(defmethod focused-keybindings ((object main-window))
|
||||
"Return the keymap of the window with focus"
|
||||
(with-accessors ((focused-window focused-window)) object
|
||||
(when focused-window
|
||||
(when-let ((keybindings (keybindings focused-window)))
|
||||
keybindings))))
|
||||
|
||||
(defun init ()
|
||||
"Initialize the screen"
|
||||
(let ((screen (make-screen)))
|
||||
(setf *main-window*
|
||||
(make-instance 'main-window
|
||||
:keybindings keybindings:*global-keymap*
|
||||
:key-config swconf:+key-main-window+
|
||||
:croatoan-window screen))
|
||||
(refresh-config *main-window*)
|
||||
*main-window*))
|
||||
|
||||
(defun parse-subwin-size (size-as-string main-window-size)
|
||||
"Parse a window size, size is a fraction of the main window size"
|
||||
(let* ((raw (num:safe-parse-number size-as-string
|
||||
:fix-fn (lambda (e)
|
||||
(declare (ignore e))
|
||||
main-window-size))))
|
||||
(cond
|
||||
((integerp raw)
|
||||
raw)
|
||||
(t
|
||||
(truncate (* raw main-window-size))))))
|
||||
|
||||
(defun parse-subwin-w (w-as-string)
|
||||
"Parse a window width, `w-as-string' a fraction of the main window width"
|
||||
(parse-subwin-size w-as-string (win-width *main-window*)))
|
||||
|
||||
(defun parse-subwin-h (h-as-string)
|
||||
"Parse a window height, `h-as-string' a fraction of the main window height"
|
||||
(parse-subwin-size h-as-string (win-height *main-window*)))
|
|
@ -0,0 +1,149 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :main)
|
||||
|
||||
(defparameter *tick* 0.0)
|
||||
|
||||
(define-constant +dt+ (/ 1 +fps+) :test #'=)
|
||||
|
||||
(defun incf-dt ()
|
||||
(incf *tick* +dt+))
|
||||
|
||||
(defun setup-bindings ()
|
||||
"This is where an UI event is bound to a function the event nil is
|
||||
the event that is fired wnen no input from user (key pressed mouse
|
||||
etc.) happened"
|
||||
(windows:with-croatoan-window (croatoan-window specials:*main-window*)
|
||||
(bind croatoan-window
|
||||
:resize
|
||||
(lambda (w event)
|
||||
(declare (ignore w event))
|
||||
(windows:refresh-config-all)
|
||||
(windows:draw-all)))
|
||||
(bind croatoan-window
|
||||
t
|
||||
(lambda (w event)
|
||||
(declare (ignore w))
|
||||
(incf-dt)
|
||||
(handler-bind ((conditions:command-not-found
|
||||
(lambda (e)
|
||||
(invoke-restart 'command-window:print-error e))))
|
||||
(command-window:manage-event event))))
|
||||
;; this is the main thread
|
||||
(bind croatoan-window
|
||||
nil
|
||||
(lambda (w e)
|
||||
(declare (ignore w e))
|
||||
(incf-dt)
|
||||
(program-events:dispatch-program-events)
|
||||
(windows:calculate-all +dt+)))))
|
||||
|
||||
(defun init-i18n ()
|
||||
"Initialize i18n machinery"
|
||||
(handler-bind ((error
|
||||
(lambda (e)
|
||||
(declare (ignore e))
|
||||
(invoke-restart 'cl-i18n:return-empty-translation-table))))
|
||||
(setf cl-i18n:*translation-file-root* +catalog-dir+)
|
||||
(cl-i18n:load-language +text-domain+ :locale (cl-i18n:find-locale))))
|
||||
|
||||
(defun init-db ()
|
||||
"Initialize the database"
|
||||
(db-utils:with-ready-database (:connect t)
|
||||
(db:purge-history)))
|
||||
|
||||
(defun change-folder ()
|
||||
"Change folder, used in requests of a command line switch"
|
||||
(let ((refresh-event (make-instance 'program-events:refresh-thread-windows-event
|
||||
:new-folder command-line:*start-folder*))
|
||||
(folder-exists-p (db:folder-exists-p command-line:*start-folder*)))
|
||||
(when folder-exists-p
|
||||
(program-events:push-event refresh-event))))
|
||||
|
||||
(defun change-timeline ()
|
||||
"Change timeline, used in requests of a command line switch"
|
||||
(let* ((refresh-event (make-instance 'program-events:refresh-thread-windows-event
|
||||
:new-timeline command-line:*start-timeline*)))
|
||||
(program-events:push-event refresh-event)))
|
||||
|
||||
(defun init ()
|
||||
"Initialize the program"
|
||||
(res:init)
|
||||
(swconf:load-config-file)
|
||||
(init-db)
|
||||
(db-utils:with-ready-database (:connect nil)
|
||||
(modules:load-module +starting-init-file+)
|
||||
;; init main window for first...
|
||||
(main-window:init)
|
||||
(keybindings-window:init)
|
||||
(command-window:init)
|
||||
(thread-window:init)
|
||||
;; the size of message and tag window depends from the sizes of
|
||||
;; thread-window and command window, so the first two must be
|
||||
;; initialized after the latter
|
||||
(message-window:init)
|
||||
(tags-window:init)
|
||||
(conversations-window:init)
|
||||
(setup-bindings)
|
||||
;; ... and init-keyboard-mapping-for last
|
||||
(keybindings:init-keyboard-mapping)
|
||||
(ui:focus-to-thread-window)
|
||||
;; now init the client
|
||||
(client:init)
|
||||
(client:authorize)
|
||||
(when command-line:*start-folder*
|
||||
(change-folder))
|
||||
(when command-line:*start-timeline*
|
||||
(change-timeline))
|
||||
(when command-line:*update-timeline*
|
||||
(ui:update-current-timeline))
|
||||
(when command-line:*check-follow-requests*
|
||||
(ui:start-follow-request-processing))))
|
||||
|
||||
(defun run ()
|
||||
(windows:with-croatoan-window (croatoan-window specials:*main-window*)
|
||||
(setf (frame-rate croatoan-window) +fps+)
|
||||
(db-utils:with-ready-database (:connect nil)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(hooks:run-hooks 'hooks:*before-main-loop*)
|
||||
(run-event-loop croatoan-window))
|
||||
(end-screen)))))
|
||||
|
||||
(defun load-script-file ()
|
||||
"Load (exexute) a lisp file used in requests of a command line switch"
|
||||
(setf program-events:*process-events-immediately* t)
|
||||
(swconf:load-config-file)
|
||||
(init-db)
|
||||
(db-utils:with-ready-database (:connect nil)
|
||||
(client:init)
|
||||
(client:authorize)
|
||||
(load command-line:*script-file* :verbose nil :print nil)))
|
||||
|
||||
(defun main ()
|
||||
"The entry point function of the program"
|
||||
(init-i18n)
|
||||
(command-line:manage-opts)
|
||||
(if command-line:*script-file*
|
||||
(load-script-file)
|
||||
(let ((croatoan::*debugger-hook* #'(lambda (c h)
|
||||
(declare (ignore h))
|
||||
(end-screen)
|
||||
(print c))))
|
||||
(init)
|
||||
(run))))
|
|
@ -0,0 +1,232 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :message-rendering-utils)
|
||||
|
||||
(defun mention-p (maybe-mention)
|
||||
(scan (strcat "^" +mention-prefix+)
|
||||
maybe-mention))
|
||||
|
||||
(defun add-mention-prefix (username)
|
||||
(if (mention-p username)
|
||||
username
|
||||
(strcat +mention-prefix+ username)))
|
||||
|
||||
(defun strip-mention-prefix (maybe-mention)
|
||||
(if (not (mention-p maybe-mention))
|
||||
maybe-mention
|
||||
(subseq maybe-mention (length +mention-prefix+))))
|
||||
|
||||
(defun find-first-mention-in-message (message-body)
|
||||
(when message-body
|
||||
(with-input-from-string (body-stream message-body)
|
||||
(when-let* ((first-line (read-line body-stream nil ""))
|
||||
(mentions (split-words first-line))
|
||||
(first-mention (first mentions)))
|
||||
(when (mention-p first-mention)
|
||||
first-mention)))))
|
||||
|
||||
(defun crypto-message-destination-user (message-data)
|
||||
(with-accessors ((body sending-message:body)
|
||||
(subject sending-message:subject)
|
||||
(reply-to sending-message:reply-to)
|
||||
(visibility sending-message:visibility)) message-data
|
||||
(when (string= visibility
|
||||
+status-direct-visibility+)
|
||||
(if reply-to
|
||||
(let ((reply-username (status-id->username reply-to)))
|
||||
(db:username->id reply-username))
|
||||
(when-let* ((mention (find-first-mention-in-message body))
|
||||
(user (db:user-exists-p (msg-utils:strip-mention-prefix mention)))
|
||||
(username (db:row-user-username user)))
|
||||
(db:username->id username))))))
|
||||
|
||||
(defun maybe-crypt-message (send-message-window &key (notify-cant-crypt nil))
|
||||
(with-accessors ((message-data sending-message:message-data)
|
||||
(rows line-oriented-window:rows)) send-message-window
|
||||
(with-accessors ((body sending-message:body)
|
||||
(subject sending-message:subject)
|
||||
(reply-to sending-message:reply-to)
|
||||
(visibility sending-message:visibility)) message-data
|
||||
(when (string= visibility
|
||||
+status-direct-visibility+)
|
||||
(let ((destination-user-id (crypto-message-destination-user message-data)))
|
||||
(if (null destination-user-id)
|
||||
(when notify-cant-crypt
|
||||
(ui:notify (_ "This message will *not* be crypted")))
|
||||
(let* ((destination-username (db:user-id->username destination-user-id))
|
||||
(destination-mention (msg-utils:add-mention-prefix destination-username))
|
||||
(new-body (misc:make-fresh-array 0 #\Space 'character nil))
|
||||
(crypto-key (db:crypto-user-key destination-username)))
|
||||
(if (null crypto-key)
|
||||
(when notify-cant-crypt
|
||||
(ui:notify (format nil
|
||||
(_ "No key to crypt message for ~s found")
|
||||
destination-username)))
|
||||
(with-output-to-string (body-stream new-body)
|
||||
;; add username on top
|
||||
(format body-stream "~a~%" destination-mention)
|
||||
(format body-stream
|
||||
"~a~%"
|
||||
(crypto-utils:encrypt-message body crypto-key))
|
||||
(setf body new-body))))))))))
|
||||
|
||||
(defun find-crypto-data (text)
|
||||
(with-input-from-string (stream text)
|
||||
(loop for line = (read-line stream nil nil)
|
||||
while line
|
||||
do
|
||||
(when (crypto-utils:crypto-text-p line)
|
||||
(return-from find-crypto-data line))))
|
||||
nil)
|
||||
|
||||
(defun maybe-decrypt-message (message-row message-text &key (notify-cant-decrypt nil))
|
||||
(let* ((username (db:row-message-username message-row))
|
||||
(html-stripped (html-utils:html->text (db:row-message-content message-row)
|
||||
:add-link-footnotes nil))
|
||||
(mention (find-first-mention-in-message html-stripped))
|
||||
(reply-p (db:row-message-reply-to-id message-row))
|
||||
(crypto-key (cond
|
||||
(reply-p
|
||||
(db:crypto-user-key username))
|
||||
(mention
|
||||
(db:crypto-user-key (msg-utils:strip-mention-prefix mention)))
|
||||
(t
|
||||
nil)))
|
||||
(crypto-data (find-crypto-data message-text)))
|
||||
(if crypto-data
|
||||
(if (and (null crypto-key)
|
||||
notify-cant-decrypt)
|
||||
(ui:notify (format nil
|
||||
(_ "Unable to find the crypto key for user ~s.")
|
||||
username)
|
||||
:as-error t)
|
||||
(crypto-utils:decrypt-message crypto-data crypto-key))
|
||||
nil)))
|
||||
|
||||
(defun attachment-type->description (type)
|
||||
(macrolet ((gen-cond (key types descriptions)
|
||||
`(cond
|
||||
,@(append
|
||||
(loop
|
||||
for type in types
|
||||
for description in descriptions collect
|
||||
`((string-equal ,type ,key) ,description))
|
||||
`((t
|
||||
(_ "invalid type")))))))
|
||||
(gen-cond type
|
||||
("unknown" "image" "gifv" "video" "audio")
|
||||
((_ "unknown") (_ "image") (_ "gifv") (_ "video") (_ "audio")))))
|
||||
|
||||
(defun attachment-type->metadata (type row)
|
||||
(let ((data (misc:make-fresh-array 0 #\a 'character nil)))
|
||||
(with-output-to-string (stream data)
|
||||
(format stream
|
||||
(_ "description: ~a~%")
|
||||
(db-utils:db-getf row :description))
|
||||
(format stream
|
||||
(_ "size: ~aX~a pixels~%")
|
||||
(db-utils:db-getf row :width)
|
||||
(db-utils:db-getf row :height))
|
||||
(when (or (string-equal type "gifv")
|
||||
(string-equal type "video")
|
||||
(string-equal type "audio"))
|
||||
(format stream
|
||||
(_"duration: ~a~%")
|
||||
(db-utils:db-getf row :duration))))
|
||||
data))
|
||||
|
||||
(defun status-attachments->text (status-id)
|
||||
(let ((text (misc:make-fresh-array 0 #\Space 'character nil)))
|
||||
(when-let* ((all-attachments (db:all-attachments-to-status status-id)))
|
||||
(with-output-to-string (stream text)
|
||||
(multiple-value-bind (header-prefix header-postfix header-value)
|
||||
(swconf:message-window-attachments-header)
|
||||
(let ((actual-header-value (or header-value
|
||||
(_ "Attachments"))))
|
||||
(format stream (strcat header-prefix
|
||||
actual-header-value
|
||||
header-postfix))))
|
||||
(loop for attachment in all-attachments do
|
||||
(let ((type (db-utils:db-getf attachment
|
||||
:type
|
||||
(_ "unknown"))))
|
||||
(format stream
|
||||
(_"type: ~a~%metadata~%~a~%address: ~a~2%")
|
||||
(attachment-type->description type)
|
||||
(attachment-type->metadata type attachment)
|
||||
(db-utils:db-getf attachment :url (_ "unknown")))))))
|
||||
text))
|
||||
|
||||
(defgeneric message-original->text-body (object &key &allow-other-keys))
|
||||
|
||||
(defmethod message-original->text-body ((object string) &key &allow-other-keys)
|
||||
(let* ((raw-body (html-utils:html->text object)))
|
||||
(emoji-shortcodes:emojify raw-body)))
|
||||
|
||||
(defun prepend-crypto-marker (decrypted-text)
|
||||
(format nil "~a~2%~a" (swconf:crypted-mark-value) decrypted-text))
|
||||
|
||||
(defmethod message-original->text-body ((object null) &key &allow-other-keys)
|
||||
(declare (ignore object))
|
||||
"")
|
||||
|
||||
(defmethod message-original->text-body ((object list)
|
||||
&key
|
||||
(notify-cant-decrypt nil)
|
||||
(try-decrypt nil)
|
||||
&allow-other-keys)
|
||||
(let ((as-text (message-original->text-body (db:row-message-content object))))
|
||||
(if try-decrypt
|
||||
(let ((decrypted (maybe-decrypt-message object
|
||||
as-text
|
||||
:notify-cant-decrypt notify-cant-decrypt)))
|
||||
(if decrypted
|
||||
(message-original->text-body (prepend-crypto-marker decrypted))
|
||||
as-text))
|
||||
as-text)))
|
||||
|
||||
(defun message-original->text-header (message-row)
|
||||
(let* ((date-format (swconf:date-fmt swconf:+key-message-window+))
|
||||
(username (db:row-message-username message-row))
|
||||
(display-name (db:row-message-user-display-name message-row))
|
||||
(creation-time (db:row-message-creation-time message-row))
|
||||
(lockedp (db-utils:db-not-nil-p (db:row-lockedp message-row)))
|
||||
(locked-mark (swconf:message-window-account-locking-status-mark lockedp))
|
||||
(encoded-date (db-utils:encode-datetime-string creation-time))
|
||||
(from-label (_ "From: "))
|
||||
(boosted-label (_ "Boosted: "))
|
||||
(boosted-id (db:row-message-reblog-id message-row))
|
||||
(boosted-username (and boosted-id
|
||||
(db:status-id->username boosted-id)))
|
||||
(date-label (_ "Date: "))
|
||||
(padding-length (max (length from-label)
|
||||
(length date-label)
|
||||
(length boosted-label)))
|
||||
(text (misc:make-fresh-array 0 #\Space 'character nil)))
|
||||
(with-output-to-string (stream text)
|
||||
(format stream
|
||||
"~a(~a) ~a~a~%"
|
||||
(right-padding from-label padding-length)
|
||||
display-name username locked-mark)
|
||||
(format stream "~a~a~2%"
|
||||
(right-padding date-label padding-length)
|
||||
(format-time encoded-date date-format))
|
||||
(when boosted-id
|
||||
(format stream "~a~a~%"
|
||||
(right-padding boosted-label padding-length)
|
||||
boosted-username)))
|
||||
text))
|
|
@ -0,0 +1,224 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :message-window)
|
||||
|
||||
(defclass message-window (wrapper-window
|
||||
row-oriented-widget
|
||||
focus-marked-window
|
||||
title-window)
|
||||
((source-text
|
||||
:initform nil
|
||||
:initarg :source-text
|
||||
:reader source-text)
|
||||
(line-position-mark
|
||||
:initform (make-tui-string "0")
|
||||
:initarg :line-position-mark
|
||||
:accessor line-position-mark)))
|
||||
|
||||
(defgeneric prepare-for-rendering (object))
|
||||
|
||||
(defmethod prepare-for-rendering ((object message-window))
|
||||
(flet ((fit-lines (lines)
|
||||
(let ((res ()))
|
||||
(loop for line in lines do
|
||||
(if (string-empty-p line)
|
||||
(push nil res)
|
||||
(loop
|
||||
for fitted-line in
|
||||
(flush-left-mono-text (split-words line)
|
||||
(win-width-no-border object))
|
||||
do
|
||||
(push fitted-line res))))
|
||||
(reverse res))))
|
||||
(with-accessors ((source-text source-text)) object
|
||||
(when hooks:*before-prepare-for-rendering-message*
|
||||
(hooks:run-hook 'hooks:*before-prepare-for-rendering-message* object))
|
||||
(let* ((lines (split-lines source-text))
|
||||
(fitted-lines (fit-lines lines))
|
||||
(color-re (swconf:color-regexps))
|
||||
(new-rows (loop for line in fitted-lines collect
|
||||
(let ((res line))
|
||||
(loop for re in color-re do
|
||||
(setf res (colorize-line res re)))
|
||||
(colorized-line->tui-string res)))))
|
||||
(setf (rows object)
|
||||
(mapcar (lambda (text-line)
|
||||
(make-instance 'line
|
||||
:normal-text text-line))
|
||||
|
||||
new-rows))
|
||||
(select-row object 0)
|
||||
object))))
|
||||
|
||||
(defmethod (setf source-text) (new-text (object message-window))
|
||||
(setf (slot-value object 'source-text) new-text)
|
||||
(prepare-for-rendering object))
|
||||
|
||||
(defun refresh-line-mark-config (window)
|
||||
(multiple-value-bind (mark-value mark-fg mark-bg)
|
||||
(swconf:message-window-line-mark-values)
|
||||
(setf (line-position-mark window)
|
||||
(make-tui-string mark-value
|
||||
:fgcolor mark-fg
|
||||
:bgcolor mark-bg))))
|
||||
|
||||
(defmethod refresh-config :after ((object message-window))
|
||||
(refresh-config-colors object swconf:+key-message-window+)
|
||||
(refresh-line-mark-config object)
|
||||
(let* ((thread-window-width (win-width *thread-window*))
|
||||
(thread-window-height (win-height *thread-window*))
|
||||
(command-window-height (win-height *command-window*))
|
||||
(main-window-height (win-height *main-window*))
|
||||
(height (- main-window-height
|
||||
command-window-height
|
||||
thread-window-height))
|
||||
(width thread-window-width)
|
||||
(x (win-x *thread-window*))
|
||||
(y (+ (win-y *thread-window*)
|
||||
thread-window-height)))
|
||||
(win-resize object width height)
|
||||
(win-move object x y)))
|
||||
|
||||
(defmethod calculate ((object message-window) dt)
|
||||
(declare (ignore object dt)))
|
||||
|
||||
(defun draw-text (window)
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) window
|
||||
(let ((actual-rows (safe-subseq rows row-selected-index)))
|
||||
(loop
|
||||
for line in actual-rows
|
||||
for y from 1 below (win-height-no-border window) do
|
||||
(let ((text-line (normal-text line)))
|
||||
(when (string-not-empty-p text-line)
|
||||
(print-text window text-line 1 y)))))))
|
||||
|
||||
(defun draw-buffer-line-mark (window)
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)
|
||||
(line-position-mark line-position-mark)) window
|
||||
(let* ((height (1- (win-height-no-border window)))
|
||||
(rows-count (length rows))
|
||||
(fraction (/ row-selected-index
|
||||
(max 1 (1- rows-count))))
|
||||
(mark-y (1+ (truncate (* fraction height))))
|
||||
(mark-x (1- (win-width window))))
|
||||
(print-text window line-position-mark mark-x mark-y))))
|
||||
|
||||
(defmethod draw ((object message-window))
|
||||
(when-window-shown (object)
|
||||
(win-clear object :redraw nil)
|
||||
(win-box object)
|
||||
(draw-text object)
|
||||
(when (source-text object)
|
||||
(draw-buffer-line-mark object))
|
||||
(call-next-method)))
|
||||
|
||||
(defgeneric scroll-down (object &optional amount))
|
||||
|
||||
(defgeneric scroll-up (object &optional amount))
|
||||
|
||||
(defgeneric scroll-end (object))
|
||||
|
||||
(defgeneric scroll-begin (object))
|
||||
|
||||
(defgeneric scroll-next-page (object))
|
||||
|
||||
(defgeneric scroll-previous-page (object))
|
||||
|
||||
(defgeneric search-regex (object regex))
|
||||
|
||||
(defmethod scroll-down ((object message-window) &optional (amount 1))
|
||||
(when (/= (row-move object amount)
|
||||
0)
|
||||
(draw object)))
|
||||
|
||||
(defmethod scroll-up ((object message-window) &optional (amount 1))
|
||||
(when (/= (row-move object (- amount))
|
||||
0)
|
||||
(draw object)))
|
||||
|
||||
(defmethod scroll-end ((object message-window))
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(when (/= (row-move object (- (length rows) row-selected-index))
|
||||
0)
|
||||
(draw object))))
|
||||
|
||||
(defmethod scroll-begin ((object message-window))
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(when (/= (row-move object (- row-selected-index))
|
||||
0)
|
||||
(draw object))))
|
||||
|
||||
(defmethod scroll-next-page ((object message-window))
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(let ((actual-window-height (win-height-no-border object)))
|
||||
(when (and (> (- (length rows)
|
||||
row-selected-index)
|
||||
actual-window-height)
|
||||
(/= (row-move object actual-window-height)
|
||||
0))
|
||||
(draw object)))))
|
||||
|
||||
(defmethod scroll-previous-page ((object message-window))
|
||||
(when (/= (row-move object (- (win-height-no-border object)))
|
||||
0)
|
||||
(draw object)))
|
||||
|
||||
(defun first-line->string (window)
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) window
|
||||
(tui-string->chars-string (normal-text (elt rows row-selected-index)))))
|
||||
|
||||
(defmethod search-regex ((object message-window) regex)
|
||||
(with-accessors ((rows rows)
|
||||
(row-selected-index row-selected-index)) object
|
||||
(let ((line-found (position-if (lambda (a)
|
||||
(scan regex
|
||||
(tui-string->chars-string (normal-text a))))
|
||||
rows
|
||||
:start (min (1+ row-selected-index)
|
||||
(length rows)))))
|
||||
(when line-found
|
||||
(row-move object (- line-found row-selected-index))
|
||||
(draw object)
|
||||
(let ((line (first-line->string object)))
|
||||
(labels ((highlight (&optional (start-scan 0))
|
||||
(multiple-value-bind (start end)
|
||||
(scan regex line :start start-scan)
|
||||
(when start
|
||||
(let ((mask (make-tui-string (subseq line start end)
|
||||
:fgcolor (win-bgcolor object)
|
||||
:bgcolor (win-fgcolor object))))
|
||||
(print-text object mask (1+ start) 1)
|
||||
(highlight end))))))
|
||||
(highlight)))))))
|
||||
|
||||
(defun init ()
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *message-window*
|
||||
(make-instance 'message-window
|
||||
:title (_ "Messages")
|
||||
:keybindings keybindings:*message-keymap*
|
||||
:key-config swconf:+key-message-window+
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *message-window*)
|
||||
(draw *message-window*)
|
||||
*message-window*))
|
|
@ -0,0 +1,919 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;; uses code from
|
||||
|
||||
;; niccolo': a chemicals inventory
|
||||
;; Copyright (C) 2016 Universita' degli Studi di Palermo
|
||||
|
||||
;; 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 3 of the
|
||||
;; License, or (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :misc-utils)
|
||||
|
||||
;; debug utils
|
||||
|
||||
(defparameter *debug* nil)
|
||||
|
||||
(defmacro when-debug (&body body)
|
||||
`(when (not (null *debug*))
|
||||
,@body))
|
||||
|
||||
(defun debug-log (format-string &rest parameters)
|
||||
(when (not (log:debug))
|
||||
(log4cl:remove-all-appenders log4cl:*root-logger*)
|
||||
(log:config :debug :nopackage
|
||||
:daily (text-utils:strcat (res:home-datadir)
|
||||
"tinmop.log")
|
||||
:backup nil))
|
||||
(let ((message (apply #'format nil format-string parameters)))
|
||||
(log:debug message)))
|
||||
|
||||
(defun dbg (format-string &rest parameters)
|
||||
(apply #'debug-log format-string parameters))
|
||||
|
||||
(defun dbg-and-quit (format-string &rest parameters)
|
||||
(apply #'dbg format-string parameters)
|
||||
(uiop:quit))
|
||||
|
||||
(defun dbg-stdout (format-string &rest parameters)
|
||||
((lambda (a b)
|
||||
(apply #'format t a b))
|
||||
(concatenate 'string format-string "~%")
|
||||
parameters))
|
||||
|
||||
(defun dump-hash-table (table)
|
||||
(let ((res '()))
|
||||
(maphash (lambda (k v) (push (format nil "~s -> ~s~%" k v) res)) table)
|
||||
res))
|
||||
|
||||
(defgeneric dump-hashtable (table))
|
||||
|
||||
(defmethod dump-hashtable ((table hash-table))
|
||||
(maphash (lambda (k v) (misc:dbg "~s -> ~s" k v)) table))
|
||||
|
||||
(defmethod dump-hashtable (table)
|
||||
(dbg "~s"table))
|
||||
|
||||
(defmacro with-messages-start-end ((start-message end-message
|
||||
&key (print-only-if-debug-mode t)) &body body)
|
||||
(alexandria:with-gensyms (res)
|
||||
(let* ((debug-p (find :debug-mode *features*))
|
||||
(print-msg-p (or (not print-only-if-debug-mode)
|
||||
debug-p)))
|
||||
`(progn
|
||||
,(when print-msg-p
|
||||
`(dbg ,start-message))
|
||||
(let ((,res (progn ,@body)))
|
||||
,(when print-msg-p
|
||||
`(dbg ,end-message))
|
||||
,res)))))
|
||||
|
||||
;; macro utils
|
||||
|
||||
(defmacro format-fn-symbol (package format &rest format-args)
|
||||
`(alexandria:format-symbol ,package ,(concatenate 'string "~:@(" format "~)")
|
||||
,@format-args))
|
||||
|
||||
(defun format-keyword (thing)
|
||||
(alexandria:make-keyword (format nil "~:@(~a~)" thing)))
|
||||
|
||||
(defun check-body-keywords (body ammitted)
|
||||
(let ((all-keywords (loop
|
||||
for ct from 1
|
||||
for i in body when (and (oddp ct)
|
||||
(keywordp i))
|
||||
collect i)))
|
||||
(loop for i in all-keywords do
|
||||
(when (not (find i ammitted :test #'eq))
|
||||
(error (format nil "keyword must be one of ~a, but ~a was found"
|
||||
ammitted
|
||||
i))))))
|
||||
|
||||
;; functions utils
|
||||
|
||||
(defun function-name (data)
|
||||
"Implementation dependent"
|
||||
(assert (functionp data))
|
||||
(multiple-value-bind (x y name)
|
||||
(function-lambda-expression data)
|
||||
(declare (ignore x y))
|
||||
(if name
|
||||
(string-downcase (symbol-name name))
|
||||
data)))
|
||||
|
||||
(defmacro fn-delay (a)
|
||||
(if (symbolp a)
|
||||
`(lambda (&rest p) (apply (function ,a) p))
|
||||
`(lambda (&rest p) (apply ,a p))))
|
||||
|
||||
(defun unsplice (form)
|
||||
(and form
|
||||
(list form)))
|
||||
|
||||
(defmacro defalias (alias &body (def &optional docstring))
|
||||
"Define a value as a top-level function.
|
||||
(defalias string-gensym (compose #'gensym #'string))
|
||||
Like (setf (fdefinition ALIAS) DEF), but with a place to put
|
||||
documentation and some niceties to placate the compiler.
|
||||
Name from Emacs Lisp."
|
||||
`(progn
|
||||
;; Give the function a temporary definition at compile time so
|
||||
;; the compiler doesn't complain about it's being undefined.
|
||||
(eval-when (:compile-toplevel)
|
||||
(unless (fboundp ',alias)
|
||||
(defun ,alias (&rest args)
|
||||
(declare (ignore args)))))
|
||||
(eval-when (:load-toplevel :execute)
|
||||
(compile ',alias ,def)
|
||||
,@(unsplice
|
||||
(when docstring
|
||||
`(setf (documentation ',alias 'function) ,docstring))))
|
||||
',alias))
|
||||
|
||||
(defun a->function (a)
|
||||
(cond
|
||||
((functionp a)
|
||||
a)
|
||||
((symbolp a)
|
||||
(symbol-function a))))
|
||||
|
||||
(defmacro gen-type-p (name)
|
||||
(alexandria:with-gensyms (a)
|
||||
(let ((fname (if (cl-ppcre:scan "-" (symbol-name name))
|
||||
(alexandria:format-symbol t "~:@(~a-p~)"
|
||||
(symbol-name name))
|
||||
(alexandria:format-symbol t "~:@(~ap~)"
|
||||
(symbol-name name)))))
|
||||
`(defun ,fname (,a)
|
||||
(eql (type-of ,a) ',name)))))
|
||||
|
||||
(defmacro define-compiler-macros (name &body args)
|
||||
(alexandria:with-gensyms (low-level-function-name)
|
||||
(let* ((function-name (alexandria:format-symbol t "~:@(~a~)" name)))
|
||||
`(progn
|
||||
(defalias ,low-level-function-name #',function-name)
|
||||
(define-compiler-macro ,function-name (&whole form ,@args)
|
||||
(let ((low-funname ',low-level-function-name))
|
||||
(if (every #'constantp (list ,@args))
|
||||
(funcall (symbol-function low-funname) ,@args)
|
||||
(progn
|
||||
form))))))))
|
||||
|
||||
(defmacro definline (name arg &rest body)
|
||||
(let* ((function-name (alexandria:format-symbol t "~:@(~a~)" name)))
|
||||
`(progn
|
||||
(declaim (inline ,function-name))
|
||||
(defun ,function-name (,@arg) ,@body))))
|
||||
|
||||
(defmacro defun-inline-function (name arg &body body)
|
||||
(let* ((function-name (alexandria:format-symbol t "~:@(~a~)" name))
|
||||
(low-level-function-name (alexandria:format-symbol t "~:@(%~a~)" name)))
|
||||
`(progn
|
||||
(declaim (inline ,function-name))
|
||||
(defun ,function-name (,@arg) (,low-level-function-name ,@arg))
|
||||
(defun ,low-level-function-name (,@arg) ,@body))))
|
||||
|
||||
(defmacro defmethod-inline-function (name arg &body body)
|
||||
(let* ((function-name (alexandria:format-symbol t "~:@(~a~)" name))
|
||||
(low-level-function-name (alexandria:format-symbol t "~:@(%~a~)" name)))
|
||||
`(progn
|
||||
(declaim (inline ,function-name))
|
||||
(defgeneric ,low-level-function-name (,@(loop for i in arg collect
|
||||
(if (atom i)
|
||||
i
|
||||
(first i)))))
|
||||
|
||||
(defmethod ,function-name (,@arg) (,low-level-function-name
|
||||
,@(loop for i in arg collect
|
||||
(if (atom i)
|
||||
i
|
||||
(first i)))))
|
||||
(defmethod ,low-level-function-name (,@arg) ,@body))))
|
||||
|
||||
(alexandria:define-constant +cache-invalid-value+ :invalid-cache-value :test #'eq)
|
||||
|
||||
(defmacro defcached (name (arg &key (test 'equalp) (clear-cache nil))
|
||||
declaration
|
||||
(&body body))
|
||||
(let* ((function-clear-cache-name (format-fn-symbol t "~a-clear-cache" name))
|
||||
(function-name (format-fn-symbol t "~:@(~a~)" name))
|
||||
(cache-name (format-fn-symbol t "~:@(cache~)")))
|
||||
`(let ((,cache-name (make-hash-table :test (quote ,test))))
|
||||
(defun ,function-clear-cache-name () (clrhash ,cache-name))
|
||||
(defun ,function-name (,@arg) ,(if declaration
|
||||
declaration
|
||||
`(declare (optimize (speed 0) (safety 3) (debug 3))))
|
||||
|
||||
(and ,clear-cache (setf ,cache-name (make-hash-table :test (quote ,test))))
|
||||
,@(list body)))))
|
||||
|
||||
(defmacro defcached-list (name (args &key (equal-fn #'=))
|
||||
&body body)
|
||||
"Uses a list as cache storage, good only with few elements!"
|
||||
(let* ((function-clear-cache-name (format-fn-symbol t "~a-clear-cache" name))
|
||||
(function-search-cache-name (format-fn-symbol t "~a-search-cache" name))
|
||||
(function-ins-cache-name (format-fn-symbol t "~a-insert-cache" name))
|
||||
(function-name (format-fn-symbol t "~:@(~a~)" name)))
|
||||
(multiple-value-bind (forms declaration)
|
||||
(alexandria:parse-body body)
|
||||
(alexandria:with-gensyms (cache)
|
||||
`(let ((,cache '()))
|
||||
(defun ,function-clear-cache-name ()
|
||||
(setf ,cache '()))
|
||||
(defun ,function-search-cache-name (d)
|
||||
(find d ,cache :test ,equal-fn))
|
||||
(defun ,function-ins-cache-name (d)
|
||||
(pushnew d ,cache :test ,equal-fn))
|
||||
(defun ,function-name (,@args)
|
||||
,@declaration
|
||||
,@forms))))))
|
||||
|
||||
(defun nest-expressions (data &optional (leaf nil))
|
||||
(if (null data)
|
||||
(list leaf)
|
||||
(append (first data) (if (rest data)
|
||||
(list (nest-expressions (rest data) leaf))
|
||||
(nest-expressions (rest data) leaf)))))
|
||||
|
||||
(defun replace-e! (expr num)
|
||||
(if (null (first expr))
|
||||
nil
|
||||
(if (atom (first expr))
|
||||
(append (list
|
||||
(if (eq (first expr) :e!)
|
||||
num
|
||||
(first expr)))
|
||||
(replace-e! (rest expr) num))
|
||||
(append (list (replace-e! (first expr) num))
|
||||
(replace-e! (rest expr) num)))))
|
||||
|
||||
(alexandria:define-constant +nil-equiv-bag+ '(:none :false :nil) :test #'equalp)
|
||||
|
||||
(defun build-plist (params)
|
||||
(let ((keywords (mapcar #'alexandria:make-keyword
|
||||
(loop for i from 0 below (length params) when (oddp (1+ i))
|
||||
collect (elt params i))))
|
||||
(vals (mapcar #'(lambda (a)
|
||||
(typecase a
|
||||
(symbol (let ((key (alexandria:make-keyword a)))
|
||||
(and (not (find key +nil-equiv-bag+ :test #'eq))
|
||||
key)))
|
||||
(cons (list a))
|
||||
(otherwise a)))
|
||||
(loop for i from 0 below (length params) when (evenp (1+ i))
|
||||
collect (elt params i)))))
|
||||
(mapcar #'(lambda (a b) (cons a b)) keywords vals)))
|
||||
|
||||
|
||||
(defmacro build-assocs-chain (path start)
|
||||
(if (null path)
|
||||
start
|
||||
`(cdr (assoc ,(first path) (build-assocs-chain ,(rest path) ,start)))))
|
||||
|
||||
(defmacro gen-trivial-plist-predicate (name class var get-fn)
|
||||
(let ((name-fn (alexandria:format-symbol t "~:@(~a-p~)" name)))
|
||||
`(progn
|
||||
(defgeneric ,name-fn (object))
|
||||
(defmethod ,name-fn ((object ,class))
|
||||
(funcall ,get-fn object ,var)))))
|
||||
|
||||
(defmacro gen-trivial-plist-predicates (class get-fn &rest vars)
|
||||
`(progn
|
||||
,@(loop for v in vars collect
|
||||
`(gen-trivial-plist-predicate ,(alexandria:symbolicate (string-trim "+" v))
|
||||
,class
|
||||
,v
|
||||
(function ,get-fn)))))
|
||||
|
||||
(defmacro gen-trivial-plist-get (function-name-prefix name class var get-fn)
|
||||
(let ((name-fn (alexandria:format-symbol t "~:@(~a-~a~)" function-name-prefix name)))
|
||||
`(progn
|
||||
(defgeneric ,name-fn (object))
|
||||
(defmethod ,name-fn ((object ,class))
|
||||
(funcall ,get-fn object ,var)))))
|
||||
|
||||
(defmacro gen-trivial-plist-gets (class get-fn function-name-prefix &rest vars)
|
||||
`(progn
|
||||
,@(loop for v in vars collect
|
||||
`(gen-trivial-plist-get ,function-name-prefix
|
||||
,(alexandria:symbolicate (string-trim "+" v))
|
||||
,class
|
||||
,v
|
||||
(function ,get-fn)))))
|
||||
|
||||
;; plist
|
||||
|
||||
(defun recursive-assoc (path start)
|
||||
(if (null path)
|
||||
start
|
||||
(recursive-assoc (rest path) (cdr (assoc (first path) start)))))
|
||||
|
||||
(defun recursive-assoc-just-before (path start)
|
||||
(if (= (length path) 1)
|
||||
start
|
||||
(recursive-assoc-just-before (rest path) (cdr (assoc (first path) start)))))
|
||||
|
||||
(defun n-setf-path-value (db path new-value)
|
||||
(let* ((ptr (recursive-assoc-just-before path db))
|
||||
(last-key (alexandria:last-elt path))
|
||||
(last-cons (assoc last-key ptr)))
|
||||
(if last-cons
|
||||
(values (setf (cdr last-cons) new-value) t)
|
||||
(values nil nil))))
|
||||
|
||||
(defun plist-path-value (db path)
|
||||
(let* ((ptr (recursive-assoc-just-before path db))
|
||||
(last-key (alexandria:last-elt path))
|
||||
(last-cons (assoc last-key ptr)))
|
||||
(if last-cons
|
||||
(values (cdr last-cons) t)
|
||||
(values nil nil))))
|
||||
|
||||
;; misc
|
||||
|
||||
(defun not-null-p (a)
|
||||
(not (null a)))
|
||||
|
||||
(definline code->char (code &key (limit-to-ascii nil))
|
||||
(code-char (if limit-to-ascii
|
||||
(alexandria:clamp code 0 127)
|
||||
code)))
|
||||
|
||||
(definline char->code (code)
|
||||
(char-code code))
|
||||
|
||||
(defmacro swap (a b)
|
||||
`(rotatef ,a ,b))
|
||||
|
||||
;;;; binary files utils
|
||||
;;;; big endian...
|
||||
|
||||
(defun 2byte->word (byte1 byte2) ;; little endian
|
||||
(let ((res #x00000000))
|
||||
(boole boole-ior
|
||||
(boole boole-ior byte1 res)
|
||||
(ash byte2 8))))
|
||||
|
||||
(defun 2word->int (word1 word2)
|
||||
(let ((res #x00000000))
|
||||
(boole boole-ior
|
||||
(ash (boole boole-ior word1 res) 16)
|
||||
word2)))
|
||||
|
||||
(defun byte->int (bytes)
|
||||
(let ((res #x0000000000000000)
|
||||
(ct 0))
|
||||
(map nil #'(lambda (a)
|
||||
(setf res (boole boole-ior
|
||||
(ash a ct)
|
||||
res))
|
||||
(incf ct 8))
|
||||
bytes)
|
||||
res))
|
||||
|
||||
(defmacro gen-intn->bytes (bits)
|
||||
(let ((function-name (alexandria:format-symbol t "~:@(int~a->bytes~)" bits)))
|
||||
`(defun ,function-name (val &optional (count 0) (res '()))
|
||||
(if (>= count ,(/ bits 8))
|
||||
(reverse res)
|
||||
(,function-name (ash val -8)
|
||||
(1+ count)
|
||||
(push (boole boole-and val #x00ff)
|
||||
res))))))
|
||||
|
||||
(gen-intn->bytes 16)
|
||||
|
||||
(gen-intn->bytes 32)
|
||||
|
||||
(defun bytes->string (bytes)
|
||||
(coerce (mapcar #'code-char bytes) 'string))
|
||||
|
||||
(defun read-ieee-float-32 (stream)
|
||||
(let ((bytes (make-fresh-list 4)))
|
||||
(read-sequence bytes stream)
|
||||
(let ((bits (byte->int bytes)))
|
||||
(ieee-floats:decode-float32 bits))))
|
||||
|
||||
(defmacro define-offset-size (package prefix &rest name-offset-size)
|
||||
`(progn
|
||||
,@(loop for i in name-offset-size collect
|
||||
`(progn
|
||||
(alexandria:define-constant
|
||||
,(alexandria:format-symbol package "~@:(+~a-~a-offset+~)" prefix (first i))
|
||||
,(second i) :test #'=)
|
||||
,(when (= (length i) 3)
|
||||
`(alexandria:define-constant
|
||||
,(alexandria:format-symbol package "~@:(+~a-~a-size+~)" prefix
|
||||
(first i))
|
||||
,(third i) :test #'=))))))
|
||||
|
||||
(defmacro define-parse-header-chunk ((name offset size object &optional (slot name)))
|
||||
(alexandria:with-gensyms (bytes)
|
||||
`(progn
|
||||
(defgeneric ,(alexandria:format-symbol t "PARSE-~:@(~a~)" name) (,object stream))
|
||||
(defmethod ,(alexandria:format-symbol t "PARSE-~:@(~a~)" name) ((object ,object) stream)
|
||||
(file-position stream ,offset)
|
||||
(let* ((,bytes (make-fresh-list ,size)))
|
||||
(read-sequence ,bytes stream)
|
||||
,(when (not (null slot))
|
||||
`(setf (,slot object) ,bytes))
|
||||
(values ,bytes object))))))
|
||||
|
||||
(defun read-list (stream size &key (offset nil))
|
||||
(when offset
|
||||
(file-position stream offset))
|
||||
(let* ((bytes (misc-utils:make-fresh-list size)))
|
||||
(read-sequence bytes stream)
|
||||
bytes))
|
||||
|
||||
(defun read-array (stream size &key (offset nil))
|
||||
(when offset
|
||||
(file-position stream offset))
|
||||
(let* ((bytes (misc-utils:make-array-frame size 0 '(unsigned-byte 8) t)))
|
||||
(read-sequence bytes stream)
|
||||
bytes))
|
||||
|
||||
;; sequence utils
|
||||
|
||||
(defun safe-elt (sequence index)
|
||||
(and (>= index 0)
|
||||
(< index (length sequence))
|
||||
(elt sequence index)))
|
||||
|
||||
(defun safe-last-elt (sequence)
|
||||
(safe-elt sequence (1- (length sequence))))
|
||||
|
||||
(defun safe-subseq (sequence start &optional (end nil))
|
||||
(when sequence
|
||||
(restart-case
|
||||
(if (and (< start 0)
|
||||
(< end 0))
|
||||
(error 'conditions:out-of-bounds
|
||||
:seq sequence
|
||||
:idx end)
|
||||
(let* ((actual-start (alexandria:clamp start
|
||||
0
|
||||
(length sequence)))
|
||||
(actual-end (and end
|
||||
(max actual-start
|
||||
(min end
|
||||
(length sequence))))))
|
||||
(subseq sequence actual-start actual-end)))
|
||||
(use-value (e) e)
|
||||
(return-nil ()
|
||||
nil)
|
||||
(return-entire-sequence ()
|
||||
sequence))))
|
||||
|
||||
(defgeneric sequence-empty-p (a))
|
||||
|
||||
(defmethod sequence-empty-p ((a vector))
|
||||
(vector-empty-p a))
|
||||
|
||||
(defmethod sequence-empty-p ((a sequence))
|
||||
(alexandria:emptyp a))
|
||||
|
||||
(defun vector-empty-p (v)
|
||||
(declare (optimize (speed 3) (safety 0) (debug 0)))
|
||||
(declare (vector v))
|
||||
(= (length v) 0))
|
||||
|
||||
(defun random-num-filled-vector (size max)
|
||||
(map-into (misc:make-array-frame size max (type-of max) t)
|
||||
#'(lambda () (num:lcg-next-upto max))))
|
||||
|
||||
(definline random-elt (seq)
|
||||
(elt seq (num:lcg-next-upto (length seq))))
|
||||
|
||||
(defun safe-random-elt (seq)
|
||||
"note: values nil if (or (null seq) (= (length seq) 0))"
|
||||
(and seq
|
||||
(> (length seq) 0)
|
||||
(elt seq (num:lcg-next-upto (length seq)))))
|
||||
|
||||
(defun make-fresh-list (size &optional (el nil))
|
||||
(map-into (make-list size)
|
||||
(if (functionp el)
|
||||
el
|
||||
#'(lambda () el))))
|
||||
|
||||
(defun seq->list (sequence)
|
||||
(if (listp sequence)
|
||||
(copy-list sequence)
|
||||
(map-into (make-list (length sequence)) #'identity sequence)))
|
||||
|
||||
(defmacro *cat (type-return input)
|
||||
`(reduce #'(lambda (a b) (concatenate ',type-return a b)) ,input))
|
||||
|
||||
(defun lcat (&rest v)
|
||||
(declare (optimize (speed 3) (safety 1) (debug 0)))
|
||||
(*cat list v))
|
||||
|
||||
(defun vcat (&rest v)
|
||||
(declare (optimize (speed 3) (safety 1) (debug 0)))
|
||||
(*cat vector v))
|
||||
|
||||
(defun fresh-list-insert@ (a v pos)
|
||||
(declare (optimize (speed 3) (safety 1) (debug 0)))
|
||||
(declare (list a))
|
||||
(lcat (subseq a 0 pos)
|
||||
(list v)
|
||||
(subseq a pos)))
|
||||
|
||||
(defun fresh-list-subst@ (a v pos)
|
||||
(declare (optimize (speed 3) (safety 1) (debug 0)))
|
||||
(declare (list a))
|
||||
(lcat (subseq a 0 pos)
|
||||
(list v)
|
||||
(subseq a (1+ pos))))
|
||||
|
||||
(eval-when (:compile-toplevel :load-toplevel :execute)
|
||||
(defun make-array-frame (size &optional (el nil) (type t) (simplep nil))
|
||||
"All elements points to the same address/reference!"
|
||||
(make-array size
|
||||
:fill-pointer (if (not simplep) size nil)
|
||||
:adjustable (if (not simplep) t nil)
|
||||
:initial-element el
|
||||
:element-type type)))
|
||||
|
||||
(defun make-fresh-array (size &optional (el nil) (type t) (simplep nil))
|
||||
(let ((res (make-array size
|
||||
:fill-pointer (if (not simplep) size nil)
|
||||
:adjustable (if (not simplep) t nil)
|
||||
:initial-element el
|
||||
:element-type type)))
|
||||
(map-into res #'(lambda (a) (setf a (cond
|
||||
((functionp el)
|
||||
(funcall el))
|
||||
((arrayp el)
|
||||
(alexandria:copy-array el))
|
||||
((listp el)
|
||||
(copy-list el))
|
||||
(t
|
||||
el))))
|
||||
res)))
|
||||
|
||||
(defun list->array (the-list)
|
||||
(make-array (length the-list)
|
||||
:fill-pointer (length the-list)
|
||||
:adjustable t
|
||||
:initial-contents (copy-list the-list)))
|
||||
|
||||
(defun copy-list-into-array (from to)
|
||||
(assert (= (length from) (length to)))
|
||||
(loop
|
||||
for i in from
|
||||
for ct from 0 by 1 do
|
||||
(setf (elt to ct) i))
|
||||
to)
|
||||
|
||||
(defun array-slice (array start &optional (end nil))
|
||||
(let* ((new-size (if end
|
||||
(- end start)
|
||||
(length array)))
|
||||
(new-fill-pointer (cond
|
||||
((array-has-fill-pointer-p array)
|
||||
(if end
|
||||
new-size
|
||||
(fill-pointer array)))
|
||||
(t
|
||||
nil)))
|
||||
(new-array (make-array new-size
|
||||
:element-type (array-element-type array)
|
||||
:fill-pointer new-fill-pointer
|
||||
:initial-element (alexandria:first-elt array)
|
||||
:adjustable (adjustable-array-p array)))
|
||||
(end-iteration (or end
|
||||
(length array))))
|
||||
(loop
|
||||
for index-from from start below end-iteration
|
||||
for index-to from 0
|
||||
do
|
||||
(setf (elt new-array index-to)
|
||||
(elt array index-from)))
|
||||
new-array))
|
||||
|
||||
(defun list->simple-array (the-list start-type type)
|
||||
(let ((res (make-array-frame (length the-list) start-type type t)))
|
||||
(loop
|
||||
for element in the-list
|
||||
for i from 0 below (length the-list) do
|
||||
(setf (elt res i) element))
|
||||
res))
|
||||
|
||||
(defun permutation (li)
|
||||
(let ((res-partial '())
|
||||
(res '()))
|
||||
(labels ((perm (start tail)
|
||||
(let ((partial-tree '()))
|
||||
(loop for i in start do
|
||||
(loop for j in (set-difference tail i) do
|
||||
(push (append i (list j)) partial-tree)))
|
||||
(setf res-partial (reverse (copy-tree partial-tree))))))
|
||||
(loop for ct in li do
|
||||
(do ((start (list (list ct)) res-partial))
|
||||
((null (set-difference li (first start)))
|
||||
(progn
|
||||
(setf res (append res res-partial))
|
||||
(setf res-partial '())))
|
||||
(perm start li))))
|
||||
res))
|
||||
|
||||
(defun shuffle (sequence)
|
||||
(loop for i from (1- (length sequence)) downto 1 do
|
||||
(let ((rnd (num:lcg-next-upto (1+ i))))
|
||||
(swap (elt sequence rnd) (elt sequence i))))
|
||||
sequence)
|
||||
|
||||
(defun split-into-sublist (lst len)
|
||||
(if (or (= len 0)
|
||||
(< (length lst) len))
|
||||
(if (null lst)
|
||||
lst
|
||||
(list lst))
|
||||
(append (list (subseq lst 0 len)) (split-into-sublist (subseq lst len) len))))
|
||||
|
||||
(defun group-by (sequence &key (test #'=))
|
||||
(let ((distinct '()))
|
||||
(loop for i in sequence do
|
||||
(pushnew i distinct :test test))
|
||||
(loop for i in distinct collect
|
||||
(remove-if-not #'(lambda (a) (funcall test a i)) sequence))))
|
||||
|
||||
(defgeneric delete@ (sequence position))
|
||||
|
||||
(defgeneric safe-delete@ (sequence position)
|
||||
(:documentation "Return sequence if position is out of bound"))
|
||||
|
||||
(defmacro gen-delete@ ((sequence position) &body body)
|
||||
`(if (and (>= ,position 0)
|
||||
(< ,position (length ,sequence)))
|
||||
,@body
|
||||
(error 'conditions:out-of-bounds :seq sequence :idx position)))
|
||||
|
||||
(defmethod delete@ ((sequence list) position)
|
||||
(gen-delete@
|
||||
(sequence position)
|
||||
(append (subseq sequence 0 position)
|
||||
(and (/= position (- (length sequence) 1))
|
||||
(subseq sequence (1+ position))))))
|
||||
|
||||
(defmethod delete@ ((sequence vector) position)
|
||||
(gen-delete@
|
||||
(sequence position)
|
||||
(make-array (1- (length sequence))
|
||||
:fill-pointer (1- (length sequence))
|
||||
:adjustable t
|
||||
:initial-contents (concatenate 'vector (subseq sequence 0 position)
|
||||
(and (/= position (- (length sequence) 1))
|
||||
(subseq sequence (1+ position)))))))
|
||||
|
||||
(defmethod safe-delete@ ((sequence sequence) position)
|
||||
(restart-case
|
||||
(delete@ sequence position)
|
||||
(return-nil () nil)
|
||||
(return-whole () sequence)
|
||||
(new-index (i) (safe-delete@ sequence i))))
|
||||
|
||||
(defun safe-all-but-last-elt (sequence)
|
||||
(handler-bind ((conditions:out-of-bounds
|
||||
#'(lambda (c)
|
||||
(declare (ignore c))
|
||||
(invoke-restart 'return-nil))))
|
||||
(safe-delete@ sequence (1- (length sequence)))))
|
||||
|
||||
(defgeneric remove-compact-remap-sequence (sequence predicate))
|
||||
|
||||
(defmethod remove-compact-remap-sequence ((sequence list) predicate)
|
||||
(let ((nullified (loop
|
||||
for i in sequence
|
||||
for ct from 0 collect
|
||||
(if (funcall predicate ct i)
|
||||
nil
|
||||
i)))
|
||||
(mapping nil)
|
||||
(results '()))
|
||||
(loop
|
||||
for i in nullified
|
||||
for pos from 0 do
|
||||
(when (not (null i))
|
||||
(push i results)
|
||||
(push (list pos (1- (length results))) mapping)))
|
||||
(values (reverse results) mapping)))
|
||||
|
||||
(defmethod remove-compact-remap-sequence ((sequence vector) predicate)
|
||||
(let ((nullified (loop for i from 0 below (length sequence) collect
|
||||
(if (funcall predicate i (elt sequence i))
|
||||
nil
|
||||
(elt sequence i))))
|
||||
(mapping nil)
|
||||
(results (make-array-frame 0)))
|
||||
(loop for i from 0 below (length nullified) do
|
||||
(when (not (null (elt nullified i)))
|
||||
(vector-push-extend (elt nullified i) results)
|
||||
(push (list i (1- (length results))) mapping)))
|
||||
(values results mapping)))
|
||||
|
||||
(defun remove-if-null (a)
|
||||
(remove-if #'null a))
|
||||
|
||||
(defun remove-if-not-null (a)
|
||||
(remove-if #'(lambda (i) (not (null i))) a))
|
||||
|
||||
(defun copy-multiply (from to length source-step copy-num)
|
||||
(loop for ct from 0 below (* source-step length) by source-step
|
||||
for ct2 from 0 below (* length source-step copy-num) by (* source-step copy-num) do
|
||||
(loop for ct3 from 0 below (* source-step copy-num) by 1 do
|
||||
(setf (elt to (+ ct2 ct3))
|
||||
(elt from (+ ct (mod ct3 source-step))))))
|
||||
to)
|
||||
|
||||
(defun all-but-last-elt (s)
|
||||
(if s
|
||||
(let ((length (length s)))
|
||||
(if (> length 0)
|
||||
(subseq s 0 (1- length))
|
||||
s))
|
||||
s))
|
||||
|
||||
(defgeneric intersperse (seq new-elt))
|
||||
|
||||
(defmethod intersperse ((seq list) new-elt)
|
||||
(loop for (item . rest) on seq
|
||||
if (null rest)
|
||||
collect item
|
||||
else collect item
|
||||
and collect new-elt))
|
||||
|
||||
(defmethod intersperse ((seq sequence) new-elt)
|
||||
(if (< (length seq) 2)
|
||||
(copy-seq seq)
|
||||
(let* ((len1 (length seq))
|
||||
(len2 (1- (* 2 len1)))
|
||||
(ret (typecase seq
|
||||
(string
|
||||
(make-string len2))
|
||||
(vector
|
||||
(make-fresh-array len2))))
|
||||
(j 0))
|
||||
(loop for i below len2 do
|
||||
(if (oddp i)
|
||||
(setf (elt ret i) new-elt)
|
||||
(progn
|
||||
(setf (elt ret i) (elt seq j))
|
||||
(incf j))))
|
||||
ret)))
|
||||
|
||||
;; iterations
|
||||
|
||||
(defmacro do-while (declaration return-form &body body)
|
||||
"C-like \"do { ...} while (condition)\" statement: body is evaluated
|
||||
even if exit condition is t at the very first iteration"
|
||||
(alexandria:with-gensyms (first-iteration)
|
||||
`(do ,(append (list `(,first-iteration t nil))
|
||||
declaration)
|
||||
,(append (list `(if ,first-iteration
|
||||
nil
|
||||
,(first return-form)))
|
||||
(rest return-form))
|
||||
,@body)))
|
||||
|
||||
(defmacro do-while* (declaration return-form &body body)
|
||||
"C-like \"do { ...} while (condition)\" statement: body is evaluated
|
||||
even if exit condition is t at the very first iteration"
|
||||
(alexandria:with-gensyms (first-iteration)
|
||||
`(do* ,(append (list `(,first-iteration t nil))
|
||||
declaration)
|
||||
,(append (list `(if ,first-iteration
|
||||
nil
|
||||
,(first return-form)))
|
||||
(rest return-form))
|
||||
,@body)))
|
||||
|
||||
;; cg vectors
|
||||
|
||||
(defmacro gen-vec-comp ((prefix-name comp-name index) &rest declarations)
|
||||
(let ((name (format-fn-symbol t "~a-~a" prefix-name comp-name))
|
||||
(set-name (format-fn-symbol t "%set-~a-~a" prefix-name comp-name))
|
||||
(arg (format-fn-symbol t "v")))
|
||||
`(progn
|
||||
(defun ,set-name (vec value)
|
||||
(setf (elt vec ,index) value))
|
||||
(defsetf ,name ,set-name)
|
||||
(defun ,name (,arg)
|
||||
,@declarations
|
||||
(elt ,arg ,index))
|
||||
(define-compiler-macros ,name ,arg))))
|
||||
|
||||
;; cffi
|
||||
|
||||
(definline make-null-pointer ()
|
||||
(cffi:null-pointer))
|
||||
|
||||
;; plugins, sort of
|
||||
|
||||
(defmacro with-load-forms-in-var ((special-var output-var file) &body body)
|
||||
`(let* ((,special-var nil))
|
||||
(load ,file)
|
||||
(let ((,output-var ,special-var))
|
||||
,@body)))
|
||||
|
||||
;;;; derived from local-time library
|
||||
|
||||
(alexandria:define-constant +unix-epoch+ (encode-universal-time 0 0 0 1 1 1970 0)
|
||||
:test #'=)
|
||||
|
||||
(defun time-unix->universal (unix-timestamp)
|
||||
"Return the UNIVERSAL-TIME corresponding to the TIMESTAMP"
|
||||
;; universal time is seconds from 1900-01-01T00:00:00Z
|
||||
;; unix timestamp is seconds from 1970-01-01T00:00:00Z
|
||||
(+ unix-timestamp +unix-epoch+))
|
||||
|
||||
(defmacro gen-time-access (name pos)
|
||||
`(defun ,(format-fn-symbol t "time-~a-of" name) (time-list)
|
||||
(elt time-list ,pos)))
|
||||
|
||||
(defmacro gen-all-time-access (&rest name-pos)
|
||||
`(progn
|
||||
,@(loop for i in name-pos collect
|
||||
`(gen-time-access ,(car i) ,(cdr i)))))
|
||||
|
||||
(gen-all-time-access (second . 0)
|
||||
(minutes . 1)
|
||||
(hour . 2)
|
||||
(date . 3)
|
||||
(month . 4)
|
||||
(year . 5)
|
||||
(day . 6)
|
||||
(daylight-p . 7)
|
||||
(zone . 8))
|
||||
|
||||
(defun year->timestamp (year)
|
||||
(local-time:encode-timestamp 0
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
1
|
||||
(truncate (max 0
|
||||
(num:safe-parse-number year)))))
|
||||
|
||||
(defun current-year ()
|
||||
(local-time:timestamp-year (db-utils:local-time-obj-now)))
|
||||
|
||||
(defun extract-year-from-timestamp (ts)
|
||||
(local-time:timestamp-year ts))
|
||||
|
||||
(defun command-terminated-no-error-p (command-error-code)
|
||||
(= command-error-code 0))
|
||||
|
||||
(defun format-time (local-time-object format-control-list)
|
||||
(with-output-to-string (stream)
|
||||
(local-time:format-timestring stream local-time-object :format format-control-list)))
|
||||
|
||||
;; threads
|
||||
|
||||
(defmacro with-lock ((lock) &body body)
|
||||
`(bt:with-recursive-lock-held (,lock)
|
||||
,@body))
|
||||
|
||||
(defmacro defun-w-lock (name parameters lock &body body)
|
||||
(multiple-value-bind (remaining-forms declarations doc-string)
|
||||
(alexandria:parse-body body :documentation t)
|
||||
`(defun ,name ,parameters
|
||||
,doc-string
|
||||
,declarations
|
||||
(with-lock (,lock)
|
||||
,@remaining-forms))))
|
||||
|
||||
;; http
|
||||
|
||||
(defun get-url-content (url)
|
||||
(drakma:http-request url
|
||||
:want-stream t
|
||||
:verify :required
|
||||
:external-format-out :utf8))
|
|
@ -0,0 +1,134 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :modeline-window)
|
||||
|
||||
(defclass modeline-window (wrapper-window)
|
||||
((mapping-code->fn
|
||||
:initform ()
|
||||
:initarg :mapping-code->fn
|
||||
:accessor mapping-code->fn)
|
||||
(modeline-src
|
||||
:initform ""
|
||||
:initarg :modeline-text-src
|
||||
:accessor modeline-src)
|
||||
(modeline-text
|
||||
:initform (_ "modeline")
|
||||
:initarg :modeline-text
|
||||
:accessor modeline-text)
|
||||
(modeline-fg
|
||||
:initform nil
|
||||
:initarg :modeline-fg
|
||||
:accessor modeline-fg)
|
||||
(modeline-bg
|
||||
:initform nil
|
||||
:initarg :modeline-bg
|
||||
:accessor modeline-bg)
|
||||
(parsed-modeline
|
||||
:initform ()
|
||||
:initarg :parsed-modeline
|
||||
:accessor parsed-modeline)))
|
||||
|
||||
(defgeneric expand-modeline-spec (object))
|
||||
|
||||
;; MODELINE := (FIELD | TEXT)*
|
||||
;; FIELD := PERCENT KEY
|
||||
;; KEY := [%abcdefghilmnopqrstuvzABCDEFGHILMNOPQRSTUVZ']
|
||||
;; PERCENT := '%'
|
||||
;; TEXT := (not percent)+
|
||||
|
||||
(defrule key
|
||||
(or #\%
|
||||
#\a #\b #\c #\d #\e #\f #\g #\h #\i #\j #\k #\l #\m
|
||||
#\n #\o #\p #\q #\r #\s #\t #\u #\v #\w #\x #\y #\z
|
||||
#\A #\B #\C #\D #\E #\F #\G #\H #\I #\J #\K #\L #\M
|
||||
#\N #\O #\P #\Q #\R #\S #\T #\U #\V #\W #\X #\Y #\Z)
|
||||
(:text t))
|
||||
|
||||
(defrule percent #\%
|
||||
(:constant :key))
|
||||
|
||||
(defrule field (and percent key))
|
||||
|
||||
(defrule text (+ (not percent))
|
||||
(:text t))
|
||||
|
||||
(defrule modeline (* (or field text)))
|
||||
|
||||
(defun call-function-mapped (win key mapping)
|
||||
(let ((fn (cdr (assoc key mapping :test #'string=))))
|
||||
(if fn
|
||||
(funcall fn win)
|
||||
(format nil "~~~a" key))))
|
||||
|
||||
(defmethod expand-modeline-spec ((object modeline-window))
|
||||
(with-accessors ((modeline-src modeline-src)
|
||||
(modeline-text modeline-text)
|
||||
(modeline-bg modeline-bg)
|
||||
(modeline-fg modeline-fg)
|
||||
(mapping-code->fn mapping-code->fn)
|
||||
(parsed-modeline parsed-modeline)) object
|
||||
;; parsed is like '("foo" (:key "a") "bar" ...)
|
||||
(let ((res (make-tui-string "")))
|
||||
(loop for i in parsed-modeline do
|
||||
(let ((executed (cond
|
||||
((listp i)
|
||||
(call-function-mapped object (lastcar i) mapping-code->fn))
|
||||
(t
|
||||
(make-tui-string i
|
||||
:fgcolor modeline-fg
|
||||
:bgcolor modeline-bg)))))
|
||||
(setf res (cat-tui-string res executed))))
|
||||
(setf res (text-ellipsize res (win-width-no-border object)))
|
||||
(setf modeline-text res))))
|
||||
|
||||
(defun refresh-modeline-config (win key)
|
||||
(with-accessors ((modeline-src modeline-src)
|
||||
(modeline-text modeline-text)
|
||||
(modeline-bg modeline-bg)
|
||||
(modeline-fg modeline-fg)
|
||||
(mapping-code->fn mapping-code->fn)
|
||||
(parsed-modeline parsed-modeline)) win
|
||||
(multiple-value-bind (bg fg)
|
||||
(swconf:modeline-colors key)
|
||||
(setf modeline-fg fg)
|
||||
(setf modeline-bg bg)
|
||||
(setf modeline-src (swconf:modeline-fmt key))
|
||||
(setf parsed-modeline (parse 'modeline modeline-src))
|
||||
(expand-modeline-spec win))
|
||||
win))
|
||||
|
||||
(defmethod calculate ((object modeline-window) dt))
|
||||
|
||||
(defmethod draw :after ((object modeline-window))
|
||||
(with-accessors ((modeline-bg modeline-bg)
|
||||
(modeline-fg modeline-fg)
|
||||
(modeline-text modeline-text)) object
|
||||
(when-window-shown (object)
|
||||
(let* ((line modeline-text)
|
||||
(line-length (text-width line))
|
||||
(max-width (win-width-no-border object)))
|
||||
(when (< line-length max-width)
|
||||
(setf line
|
||||
(cat-tui-string line (build-string (- max-width line-length)))))
|
||||
(print-text object line 1 (1- (win-height object)))))))
|
||||
|
||||
(defun add-modeline-char-expander (win code fn)
|
||||
(assert (stringp code))
|
||||
(assert (functionp fn))
|
||||
(with-accessors ((mapping-code->fn mapping-code->fn)) win
|
||||
(setf mapping-code->fn
|
||||
(acons code fn mapping-code->fn))))
|
|
@ -0,0 +1,37 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :modules)
|
||||
|
||||
(defun load-module (path)
|
||||
(flet ((%load (file)
|
||||
(load file :verbose nil :print nil)))
|
||||
(let ((config-file (conditions:with-default-on-error (nil)
|
||||
(get-config-file path)))
|
||||
(data-file (conditions:with-default-on-error (nil)
|
||||
(get-data-file path))))
|
||||
(cond
|
||||
(config-file
|
||||
(%load config-file))
|
||||
(data-file
|
||||
(%load data-file))
|
||||
(t
|
||||
(error (_ "Unrecoverable error: file ~a not found in any of the directory ~a ~a ~a ~a")
|
||||
+sys-data-dir+
|
||||
+sys-conf-dir+
|
||||
(home-datadir)
|
||||
(home-confdir)))))))
|
|
@ -0,0 +1,620 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package mtree-utils)
|
||||
|
||||
;; tree := nil | node
|
||||
;; node := (list atom node*)
|
||||
;; example: '(1 (2) (3 (4) (5)))
|
||||
|
||||
(defgeneric leafp (object))
|
||||
|
||||
(defmethod leafp ((object cons))
|
||||
(null (cdr object)))
|
||||
|
||||
(defun random-choose-leaf (tree)
|
||||
(if (leafp tree)
|
||||
(car tree)
|
||||
(let ((children (cdr tree)))
|
||||
(random-choose-leaf (misc:random-elt children)))))
|
||||
|
||||
(defun traverse-apply-tree (function tree &optional (args nil))
|
||||
(append
|
||||
(if (and (consp tree)
|
||||
(not (null tree)))
|
||||
(reverse
|
||||
(append
|
||||
(reverse (loop for i in (cdr tree) collect (traverse-apply-tree function i args)))
|
||||
(list (apply function (append (list (car tree)) args)))))
|
||||
nil)))
|
||||
|
||||
(defun traverse-napply-tree (function tree &optional (args nil))
|
||||
(when (and (consp tree)
|
||||
(not (null tree)))
|
||||
(loop for i in (cdr tree) collect (traverse-napply-tree function i args))
|
||||
(rplaca tree (apply function (append (list (car tree)) args)))))
|
||||
|
||||
(defun traverse-find-if-tree (tree item &key (test #'equal) (key #'identity))
|
||||
(progn
|
||||
(traverse-apply-tree #'(lambda (x) (if (funcall test item (funcall key x))
|
||||
(return-from traverse-find-if-tree x)
|
||||
nil))
|
||||
tree)
|
||||
nil))
|
||||
|
||||
(defun traverse-find-all-if-tree (tree item &key (test #'equal) (key #'identity))
|
||||
(let ((res '()))
|
||||
(traverse-apply-tree #'(lambda (x) (if (funcall test item (funcall key x))
|
||||
(push x res)))
|
||||
tree)
|
||||
res))
|
||||
|
||||
(defun traverse-apply-tree-cdr (function tree &optional (args nil))
|
||||
(append
|
||||
(if (and (consp tree)
|
||||
(not (null tree)))
|
||||
(append
|
||||
(list (apply function (append (list tree) args)))
|
||||
(loop for i in (cdr tree) by #'cdr collect (traverse-apply-tree-cdr function i args)))
|
||||
nil)))
|
||||
|
||||
(defun traverse-nadd-child (tree node child &key (test #'equal) (key #'identity))
|
||||
(traverse-apply-tree-cdr
|
||||
#'(lambda (x) (when (funcall test (funcall key (car x)) node)
|
||||
(progn
|
||||
(rplacd x (append (list (list child)) (cdr x)))
|
||||
(rplaca x (car x)))))
|
||||
tree)
|
||||
tree)
|
||||
|
||||
(defun nappend-child (tree child)
|
||||
(rplacd tree (concatenate 'list (cdr tree) (list (list child)))))
|
||||
|
||||
(defun traverse-ndelete-child (tree node &key (test #'equal) (key #'identity))
|
||||
(traverse-apply-tree-cdr
|
||||
#'(lambda (x) (loop
|
||||
for i in (cdr x)
|
||||
for ct = 0 then (1+ ct)
|
||||
do
|
||||
(if (funcall test (funcall key (car i)) node)
|
||||
(rplacd x (misc-utils:safe-delete@ (cdr x) ct)))))
|
||||
tree)
|
||||
tree)
|
||||
|
||||
(defmacro %navigate (tree path)
|
||||
(if path
|
||||
`(nth ,(first path) (%navigate ,tree ,(rest path)))
|
||||
tree))
|
||||
|
||||
(defmacro navigate (tree path)
|
||||
`(%navigate ,tree ,(reverse path)))
|
||||
|
||||
(defun init-children ()
|
||||
(misc:make-fresh-array 0 nil t t))
|
||||
|
||||
(defclass m-tree ()
|
||||
((data
|
||||
:initform nil
|
||||
:initarg :data
|
||||
:accessor data)
|
||||
(parent
|
||||
:initform nil
|
||||
:initarg :parent
|
||||
:accessor parent)
|
||||
(children
|
||||
:initform (init-children)
|
||||
:initarg :children
|
||||
:accessor children)))
|
||||
|
||||
(defmethod marshal:class-persistant-slots ((object m-tree))
|
||||
'(data parent children))
|
||||
|
||||
(defgeneric pprint-tree (object stream &optional level parent-length other-data))
|
||||
|
||||
(defgeneric add-child (object child &optional child-pos))
|
||||
|
||||
(defgeneric child-data-pushnew (object child &key key test))
|
||||
|
||||
(defgeneric graft-branch (rootstock scion &key key test overwrite-rootstock-data-p))
|
||||
|
||||
(defgeneric add-children (object children))
|
||||
|
||||
(defgeneric add-children* (object &rest children))
|
||||
|
||||
(defgeneric find-child (object to-find &key compare))
|
||||
|
||||
(defgeneric find-child-if (object predicate))
|
||||
|
||||
(defgeneric rootp (object))
|
||||
|
||||
(defgeneric top-down-visit (object function &optional args))
|
||||
|
||||
(defgeneric bottom-up-visit (object function &optional args))
|
||||
|
||||
(defgeneric remove-all-children (object))
|
||||
|
||||
(defgeneric remove-child (object needle &key key test))
|
||||
|
||||
(defgeneric remove-child-if (object predicate))
|
||||
|
||||
(defgeneric count-leaves (object))
|
||||
|
||||
(defgeneric count-nodes (object))
|
||||
|
||||
(defgeneric mtree-equal (tree-1 tree-2 &key key-fn compare-fn))
|
||||
|
||||
(defgeneric root-node (object))
|
||||
|
||||
(defgeneric single-node-tree-p (object))
|
||||
|
||||
(defgeneric tree->text-lines (object &key
|
||||
last-child-char line-char child-char arrow-char
|
||||
print-data
|
||||
print-data-fn))
|
||||
|
||||
(defgeneric tree->annotated-lines (object &key
|
||||
last-child-char line-char child-char arrow-char
|
||||
print-data
|
||||
print-data-fn))
|
||||
|
||||
(defparameter *use-pprint-tree* nil)
|
||||
|
||||
(defmethod print-object ((object m-tree) stream)
|
||||
(if *use-pprint-tree*
|
||||
(pprint-tree object stream)
|
||||
(format stream "[data ~a children ~a]" (data object) (children object))))
|
||||
|
||||
(defmethod pprint-tree ((object m-tree) stream &optional (level 0) (parent-length 0)
|
||||
(other-data nil))
|
||||
(declare (ignore other-data))
|
||||
(labels ((indent (level &optional (char " ")) (make-list level :initial-element char)))
|
||||
(with-accessors ((data data) (children children)) object
|
||||
(let ((data-length (+
|
||||
(do ((parent (parent object) (parent parent))
|
||||
(data-length 0))
|
||||
((not parent) data-length)
|
||||
(incf data-length (length (format nil "~a" (data parent)))))
|
||||
|
||||
(length (format nil "~a" data)))))
|
||||
(format stream "~{~a~}~a" (indent (+ level parent-length))
|
||||
data)
|
||||
(if (leafp object)
|
||||
(format stream "~%")
|
||||
(progn
|
||||
(pprint-tree (elt children 0) stream 1)
|
||||
(map nil #'(lambda (c) (pprint-tree c stream (1+ level) data-length))
|
||||
(subseq children 1))))))))
|
||||
|
||||
(defmethod clone ((object m-tree))
|
||||
(make-instance 'm-tree :data (data object) :parent (parent object)
|
||||
:children (alexandria:copy-array (children object))))
|
||||
|
||||
(defmethod add-child ((object m-tree) (child m-tree)
|
||||
&optional (child-pos (length (children object))))
|
||||
(with-accessors ((children children)) object
|
||||
(setf (parent child) object)
|
||||
(if (and child-pos
|
||||
(< child-pos (length children))
|
||||
(>= child-pos 0))
|
||||
(setf children
|
||||
(let ((res (misc:make-fresh-array (1+ (length children))
|
||||
nil (type-of child) t)))
|
||||
(loop for i from 0 below child-pos do
|
||||
(setf (elt res i) (elt children i)))
|
||||
(setf (elt res child-pos) child)
|
||||
(loop for i from (1+ child-pos) below (length res) do
|
||||
(setf (elt res i) (elt children (1- i))))
|
||||
res))
|
||||
(setf children
|
||||
(let ((res (misc:make-fresh-array (1+ (length children))
|
||||
nil (type-of child) t)))
|
||||
(loop for i from 0 below (1- (length res)) do
|
||||
(setf (elt res i) (elt children i)))
|
||||
(setf (elt res (1- (length res))) child)
|
||||
res)))
|
||||
(values object child)))
|
||||
|
||||
(defmethod child-data-pushnew ((object m-tree) (child m-tree)
|
||||
&key (key #'identity) (test #'eq))
|
||||
"Push a child if there is no siblings with the same data under
|
||||
`test' or `key' functions"
|
||||
(let ((old-data (map 'list (lambda (a) (funcall key (data a)))
|
||||
(children object)))
|
||||
(new-datum (funcall key (data child))))
|
||||
(when (not (find new-datum old-data :test test))
|
||||
(add-child object child))
|
||||
object))
|
||||
|
||||
(defmacro do-children ((child node) &body body)
|
||||
`(loop for ,child across (children ,node) do
|
||||
,@body))
|
||||
|
||||
(defmacro do-children-from-end ((child node) &body body)
|
||||
`(loop for ,child across (reverse (children ,node)) do
|
||||
,@body))
|
||||
|
||||
(defmethod graft-branch ((rootstock m-tree) (scion m-tree)
|
||||
&key
|
||||
(key #'identity) (test #'eq)
|
||||
(overwrite-rootstock-data-p t))
|
||||
"Graft a tree with a single branch (scion) to a tree (rootstock).
|
||||
|
||||
They have to share a common prefix of a list one node
|
||||
(i.e '(funcall test (key (data rootstock)) (key (data scion)))' is
|
||||
non-nil)
|
||||
|
||||
If `overwrite-rootstock-data-p' is non-nil any the node of the scion that is equals
|
||||
under `test' to the any of the rootstock overwrite it.
|
||||
|
||||
Assume this function modify rootstock.
|
||||
|
||||
Examples given:
|
||||
|
||||
a a a
|
||||
/ \ + | -> / \
|
||||
b c c b c
|
||||
\ | / \
|
||||
d e e d
|
||||
|
||||
a a a------+
|
||||
/ \ + | -> / \ |
|
||||
b c d b c d
|
||||
\ | \ |
|
||||
d e d e
|
||||
|
||||
a a a
|
||||
/ \ + | -> / \
|
||||
b c c b c
|
||||
| /
|
||||
e e
|
||||
|
||||
a b a
|
||||
/ \ + | -> / \
|
||||
b c c b c
|
||||
\ | \
|
||||
d e d
|
||||
|
||||
"
|
||||
(labels ((extract-data (a)
|
||||
(funcall key (data a)))
|
||||
(test-data (a b)
|
||||
(funcall test
|
||||
(extract-data a)
|
||||
(extract-data b)))
|
||||
(twins-sibling (children-rootstock children-scion)
|
||||
(loop for child-rootstock across children-rootstock do
|
||||
(loop for child-scion across children-scion do
|
||||
(when (test-data child-rootstock child-scion)
|
||||
(return-from twins-sibling
|
||||
(values child-rootstock child-scion)))))
|
||||
nil))
|
||||
(with-accessors ((parent-rootstock parent)
|
||||
(children-rootstock children)) rootstock
|
||||
(if (test-data rootstock scion)
|
||||
(progn
|
||||
(when overwrite-rootstock-data-p
|
||||
(setf (data rootstock)
|
||||
(data scion)))
|
||||
(cond
|
||||
((misc:vector-empty-p children-rootstock)
|
||||
(add-children rootstock (children scion)))
|
||||
(t
|
||||
(multiple-value-bind (twin-rootstock twin-scion)
|
||||
(twins-sibling children-rootstock
|
||||
(children scion))
|
||||
(if twin-rootstock
|
||||
(graft-branch twin-rootstock twin-scion :test test :key key)
|
||||
(add-children rootstock (children scion)))))))
|
||||
(when (not (rootp rootstock))
|
||||
(add-child parent-rootstock scion)))
|
||||
rootstock)))
|
||||
|
||||
(defmethod add-children ((object m-tree) (children list))
|
||||
(loop for i in children do
|
||||
(add-child object i))
|
||||
object)
|
||||
|
||||
(defmethod add-children ((object m-tree) (children vector))
|
||||
(loop for i across children do
|
||||
(add-child object i))
|
||||
object)
|
||||
|
||||
(defmethod add-children* ((object m-tree) &rest children)
|
||||
(add-children object children))
|
||||
|
||||
(defmethod find-child ((object m-tree) to-find &key (compare #'equalp))
|
||||
(with-accessors ((data data) (children children)) object
|
||||
(if (funcall compare data to-find)
|
||||
object
|
||||
(if (leafp object)
|
||||
nil
|
||||
(find-if-not #'null
|
||||
(map 'vector #'(lambda (c)
|
||||
(find-child c to-find :compare compare))
|
||||
children))))))
|
||||
|
||||
(defmethod find-child-if ((object m-tree) predicate)
|
||||
(let ((res '()))
|
||||
(labels ((%find-child-if (object predicate)
|
||||
(when (funcall predicate object)
|
||||
(push object res))
|
||||
(do-children (child object)
|
||||
(%find-child-if child predicate))))
|
||||
(%find-child-if object predicate)
|
||||
res)))
|
||||
|
||||
(defmethod leafp ((object m-tree))
|
||||
(= (length (children object)) 0))
|
||||
|
||||
(defmethod rootp ((object m-tree))
|
||||
(null (parent object)))
|
||||
|
||||
(defmethod top-down-visit ((node m-tree) function &optional (args nil))
|
||||
(apply function (concatenate 'list (list node) args))
|
||||
(loop for c across (children node) do
|
||||
(top-down-visit c function args)))
|
||||
|
||||
(defmethod bottom-up-visit ((node m-tree) function &optional (args nil))
|
||||
(loop for c across (children node) do
|
||||
(bottom-up-visit c function args))
|
||||
(apply function (concatenate 'list (list node) args)))
|
||||
|
||||
(defmethod remove-all-children ((object m-tree))
|
||||
(setf (children object) (init-children)))
|
||||
|
||||
(defmethod remove-child ((object m-tree) (needle m-tree) &key
|
||||
(key #'identity)
|
||||
(test #'eq))
|
||||
(with-accessors ((children children)) object
|
||||
(if (leafp object)
|
||||
nil
|
||||
(loop for i fixnum from 0 below (length children) do
|
||||
(if (funcall test (funcall key needle) (funcall key (elt children i)))
|
||||
(progn
|
||||
(setf children
|
||||
(concatenate `(vector ,(array-element-type children)
|
||||
,(1- (length children)))
|
||||
(subseq children 0 i)
|
||||
(subseq children (1+ i))))
|
||||
(return-from remove-child t))
|
||||
(remove-child (elt children i) needle :key key :test test))))))
|
||||
|
||||
(defmethod remove-child ((object m-tree) needle &key
|
||||
(key #'identity)
|
||||
(test #'eq))
|
||||
(with-accessors ((children children)) object
|
||||
(if (leafp object)
|
||||
nil
|
||||
(loop for i fixnum from 0 below (length children) do
|
||||
(if (funcall test (funcall key needle) (funcall key (elt children i)))
|
||||
(progn
|
||||
(setf children
|
||||
(concatenate `(vector ,(array-element-type children)
|
||||
,(1- (length children)))
|
||||
(subseq children 0 i)
|
||||
(subseq children (1+ i))))
|
||||
(return-from remove-child t))
|
||||
(remove-child (elt children i) needle :key key :test test))))))
|
||||
|
||||
(defmethod remove-child-if ((object m-tree) predicate)
|
||||
(top-down-visit object
|
||||
#'(lambda (n)
|
||||
(with-accessors ((children children)) n
|
||||
(setf children (delete-if predicate children))))))
|
||||
|
||||
(defmethod count-leaves ((object m-tree))
|
||||
(let ((results 0))
|
||||
(top-down-visit object #'(lambda (n)
|
||||
(when (leafp n)
|
||||
(incf results))))
|
||||
results))
|
||||
|
||||
(defmethod count-nodes ((object m-tree))
|
||||
(let ((results 0))
|
||||
(top-down-visit object #'(lambda (n)
|
||||
(declare (ignore n))
|
||||
(incf results)))
|
||||
results))
|
||||
|
||||
(defmethod mtree-equal ((tree-1 m-tree) (tree-2 m-tree)
|
||||
&key
|
||||
(key-fn #'identity)
|
||||
(compare-fn #'eq))
|
||||
(labels ((%mtree-equal (tree-a tree-b)
|
||||
(with-accessors ((children-a children)) tree-a
|
||||
(with-accessors ((children-b children)) tree-b
|
||||
(let ((value-a (funcall key-fn (data tree-a)))
|
||||
(value-b (funcall key-fn (data tree-b))))
|
||||
(if (funcall compare-fn value-a value-b)
|
||||
(if (= (length children-a)
|
||||
(length children-b))
|
||||
(progn
|
||||
(loop
|
||||
for child-a across children-a
|
||||
for child-b across children-b do
|
||||
(%mtree-equal child-a
|
||||
child-b))
|
||||
t)
|
||||
(return-from mtree-equal nil))
|
||||
(return-from mtree-equal nil)))))))
|
||||
(%mtree-equal tree-1 tree-2)))
|
||||
|
||||
(defmethod root-node ((object m-tree))
|
||||
(if (rootp object)
|
||||
object
|
||||
(root-node (parent object))))
|
||||
|
||||
(defmethod single-node-tree-p ((object m-tree))
|
||||
(and (rootp object)
|
||||
(leafp object)))
|
||||
|
||||
(defun make-node (data &optional (parent nil))
|
||||
(make-instance 'm-tree :data data :parent parent))
|
||||
|
||||
(defclass sorted-m-tree (m-tree)
|
||||
((compare-fn
|
||||
:initform #'<
|
||||
:initarg :compare-fn
|
||||
:accessor compare-fn
|
||||
:documentation "The predicate for children comparison. Default #'<")
|
||||
(key-fn
|
||||
:initform #'identity
|
||||
:initarg :key-fn
|
||||
:accessor key-fn
|
||||
:documentation "The function to extract the values from slot
|
||||
`data' from each children. Default #'identity"))
|
||||
(:documentation "A tree that keep its children sorted"))
|
||||
|
||||
(misc:definline sort-children (tree)
|
||||
(with-accessors ((children children)
|
||||
(compare-fn compare-fn)
|
||||
(key-fn key-fn)) tree
|
||||
(setf children (num:shellsort children compare-fn
|
||||
:key (lambda (a) (funcall key-fn (data a)))))))
|
||||
|
||||
(defmethod initialize-instance :after ((object sorted-m-tree) &key &allow-other-keys)
|
||||
(sort-children object)
|
||||
object)
|
||||
|
||||
(defmethod add-child :after ((object sorted-m-tree) (child m-tree)
|
||||
&optional (child-pos (length (children object))))
|
||||
(declare (ignore child child-pos))
|
||||
(sort-children object))
|
||||
|
||||
(alexandria:define-constant +tree-arrow-char+ #\BLACK_RIGHT-POINTING_ISOSCELES_RIGHT_TRIANGLE
|
||||
:test #'char=)
|
||||
|
||||
(defmethod tree->text-lines ((object m-tree)
|
||||
&key
|
||||
(last-child-char (string #\╰))
|
||||
(line-char (string #\│))
|
||||
(child-char (string #\├))
|
||||
(spacer-child (string #\─))
|
||||
(arrow-char (format nil "~c " +tree-arrow-char+))
|
||||
|
||||
(print-data nil)
|
||||
(print-data-fn #'to-s))
|
||||
(let ((res ())
|
||||
(indent-step 1))
|
||||
(labels ((last-child-p (tree pos)
|
||||
(if (rootp tree)
|
||||
t
|
||||
(>= pos (1- (length (children (parent tree)))))))
|
||||
(%print (node indent-level child-pos empty-levels)
|
||||
(let ((line "")
|
||||
(data (if print-data
|
||||
(funcall print-data-fn (data node))
|
||||
"")))
|
||||
(flet ((cat-line (&rest chunks)
|
||||
(setf line
|
||||
(reduce #'strcat chunks :initial-value line))))
|
||||
(loop for i from 1 below indent-level do
|
||||
(if (find i empty-levels :test #'=)
|
||||
(cat-line " ")
|
||||
(cat-line line-char))
|
||||
(loop repeat indent-step do
|
||||
(cat-line " ")))
|
||||
(cond
|
||||
((rootp node)
|
||||
(cat-line data))
|
||||
((last-child-p node child-pos)
|
||||
(push indent-level empty-levels)
|
||||
(cat-line last-child-char spacer-child arrow-char data))
|
||||
(t
|
||||
(cat-line child-char spacer-child arrow-char data))))
|
||||
(values line empty-levels)))
|
||||
(visit (tree indent-level child-pos empty-levels)
|
||||
(multiple-value-bind (line new-empty-levels)
|
||||
(%print tree indent-level child-pos empty-levels)
|
||||
(push line res)
|
||||
(loop
|
||||
for node across (children tree)
|
||||
for ct-pos from 0
|
||||
do
|
||||
(visit node (1+ indent-level) ct-pos new-empty-levels)))))
|
||||
(visit object 0 0 ())
|
||||
(reverse res))))
|
||||
|
||||
(defmethod tree->annotated-lines ((object m-tree)
|
||||
&key
|
||||
(last-child-char
|
||||
(string #\BOX_DRAWINGS_LIGHT_ARC_UP_AND_RIGHT))
|
||||
(line-char
|
||||
(string #\BOX_DRAWINGS_LIGHT_VERTICAL))
|
||||
(child-char
|
||||
(string #\BOX_DRAWINGS_LIGHT_VERTICAL_AND_RIGHT))
|
||||
(spacer-child (string #\BOX_DRAWINGS_LIGHT_HORIZONTAL))
|
||||
(arrow-char "> ")
|
||||
(print-data nil)
|
||||
(print-data-fn #'to-s))
|
||||
(let ((res ())
|
||||
(indent-step 1))
|
||||
(labels ((last-child-p (tree pos)
|
||||
(if (rootp tree)
|
||||
t
|
||||
(>= pos (1- (length (children (parent tree)))))))
|
||||
(%print (node indent-level child-pos empty-levels)
|
||||
(let ((line ())
|
||||
(data (if print-data
|
||||
(funcall print-data-fn (data node))
|
||||
"")))
|
||||
(labels ((append-build-element (&rest chunks)
|
||||
(setf line
|
||||
(reduce #'append
|
||||
(mapcar (lambda (a) (list a)) chunks)
|
||||
:initial-value line)))
|
||||
(cat-line (&rest chunks)
|
||||
(if line
|
||||
(let* ((last-element (alexandria:last-elt line))
|
||||
(new-element (list (annotated-text-value last-element)))
|
||||
(to-concat (strcat* (append new-element
|
||||
chunks))))
|
||||
(setf (alexandria:last-elt line) (cons :branch to-concat)))
|
||||
(setf line (list (cons :a (strcat* chunks))))))
|
||||
(build-element (trunk-char data node)
|
||||
(append-build-element (cons :branch
|
||||
(strcat trunk-char
|
||||
spacer-child))
|
||||
(cons :arrow arrow-char)
|
||||
(if (leafp node)
|
||||
(cons :data-leaf data)
|
||||
(cons :data data)))))
|
||||
(loop for i from 1 below indent-level do
|
||||
(if (find i empty-levels :test #'=)
|
||||
(cat-line " ")
|
||||
(append-build-element (cons :d line-char)))
|
||||
(loop repeat indent-step do
|
||||
(cat-line " ")))
|
||||
(cond
|
||||
((rootp node)
|
||||
(append-build-element (cons :data data)))
|
||||
((last-child-p node child-pos)
|
||||
(push indent-level empty-levels)
|
||||
(build-element last-child-char data node))
|
||||
(t
|
||||
(build-element child-char data node))))
|
||||
(values line empty-levels)))
|
||||
(visit (tree indent-level child-pos empty-levels)
|
||||
(multiple-value-bind (line new-empty-levels)
|
||||
(%print tree indent-level child-pos empty-levels)
|
||||
(push line res)
|
||||
(loop
|
||||
for node across (children tree)
|
||||
for ct-pos from 0
|
||||
do
|
||||
(visit node (1+ indent-level) ct-pos new-empty-levels)))))
|
||||
(visit object 0 0 ())
|
||||
(reverse res))))
|
|
@ -0,0 +1,101 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :notify-window)
|
||||
|
||||
(define-constant +notify-win-background-color+ '(:yellow :blue)
|
||||
:test #'equalp)
|
||||
|
||||
(define-constant +notify-win-background+ (make-instance 'complex-char
|
||||
:simple-char #\Space
|
||||
:color-pair +notify-win-background-color+
|
||||
:attributes nil)
|
||||
:test #'complex-char=)
|
||||
|
||||
(defclass notify-window (wrapper-window)
|
||||
((life
|
||||
:initform 10.0
|
||||
:initarg :life
|
||||
:accessor life)
|
||||
(pending
|
||||
:initform 0
|
||||
:initarg :pending
|
||||
:accessor pending)))
|
||||
|
||||
(defun notify-window-p (thing)
|
||||
(typep thing 'notify-window))
|
||||
|
||||
(defmethod refresh-config :after ((object notify-window))
|
||||
(refresh-config-colors object swconf:+key-notify-window+))
|
||||
|
||||
(defmethod calculate ((object notify-window) dt)
|
||||
(with-accessors ((life life)) object
|
||||
(when (< life 0.0)
|
||||
(let ((remove-win-event (make-instance 'program-events:remove-notify-user-event
|
||||
:payload object)))
|
||||
(win-close object)
|
||||
(program-events:push-event remove-win-event)))
|
||||
(decf (life object) dt)))
|
||||
|
||||
(defmethod draw ((object notify-window))
|
||||
(declare (ignore object)))
|
||||
|
||||
(defgeneric draw-pending (object))
|
||||
|
||||
(defmethod draw-pending ((object notify-window))
|
||||
(with-accessors ((pending pending)) object
|
||||
(when (> pending 0)
|
||||
(print-text object
|
||||
(format nil (n_ "~a pending"
|
||||
"~a pending"
|
||||
pending)
|
||||
pending)
|
||||
2
|
||||
(1- (win-height object))))))
|
||||
|
||||
(defun force-error-colors (window)
|
||||
(with-croatoan-window (croatoan-window window)
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background :red))
|
||||
(setf (bgcolor croatoan-window) :red)
|
||||
(setf (fgcolor croatoan-window) :yellow))
|
||||
window)
|
||||
|
||||
(defun make-notification-window (message life &key (pending 0) (hidep nil) (notify-error nil))
|
||||
(let* ((low-level-window (make-croatoan-window :draw-border t))
|
||||
(high-level-window (make-instance 'notify-window
|
||||
:life life
|
||||
:pending pending
|
||||
:croatoan-window low-level-window)))
|
||||
(refresh-config high-level-window)
|
||||
(when notify-error
|
||||
(force-error-colors high-level-window))
|
||||
(let ((win-w (truncate (* 1/6 (win-width *main-window*))))
|
||||
(win-h (truncate (* 1/8 (win-height *main-window*)))))
|
||||
(win-resize high-level-window win-w win-h)
|
||||
;; add-flush-left-text will expand window's height if needed
|
||||
(add-flush-left-text high-level-window
|
||||
message 0
|
||||
:has-border-p t
|
||||
:attributes (attribute-bold))
|
||||
(win-raise-to-top high-level-window)
|
||||
(win-move high-level-window 1 1)
|
||||
(win-box high-level-window)
|
||||
(when hidep
|
||||
(win-hide high-level-window))
|
||||
(mtree:add-child specials:*main-window* high-level-window)
|
||||
high-level-window)))
|
|
@ -0,0 +1,288 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
;; NOTE: any random function are not for crypto use!
|
||||
|
||||
(in-package :num-utils)
|
||||
|
||||
(defun safe-parse-number (maybe-number &key (fix-fn #'(lambda (e) (declare (ignore e)) nil)))
|
||||
(handler-bind ((error
|
||||
(lambda(e)
|
||||
(return-from safe-parse-number (funcall fix-fn e)))))
|
||||
(if (or (not (stringp maybe-number))
|
||||
(string= maybe-number "-"))
|
||||
(error "Paring a non string element")
|
||||
(parse-number:parse-number maybe-number))))
|
||||
|
||||
(defun parse-number-default (maybe-number default)
|
||||
(safe-parse-number maybe-number
|
||||
:fix-fn (lambda (e) (declare (ignore e)) default)))
|
||||
|
||||
(defun find-min-max (function the-list)
|
||||
(restart-case
|
||||
(reduce #'(lambda (a b) (if (funcall function a b) a b)) the-list)
|
||||
(use-value (e) e)))
|
||||
|
||||
(defun find-min (the-list)
|
||||
(find-min-max #'< the-list))
|
||||
|
||||
(defun find-max (the-list)
|
||||
(find-min-max #'> the-list))
|
||||
|
||||
(defgeneric round-all (object &key rounding-function))
|
||||
|
||||
(defmethod round-all ((object list) &key (rounding-function #'round))
|
||||
(mapcar #'(lambda (n) (funcall rounding-function n)) object))
|
||||
|
||||
(defmethod round-all ((object number) &key (rounding-function #'round))
|
||||
(funcall rounding-function object))
|
||||
|
||||
(defmethod round-all ((object vector) &key (rounding-function #'round))
|
||||
(map (type-of object) #'(lambda (n) (funcall rounding-function n)) object))
|
||||
|
||||
(defun fract (n)
|
||||
(multiple-value-bind (int frac)
|
||||
(truncate n)
|
||||
(declare (ignore int))
|
||||
frac))
|
||||
|
||||
(defun sign (n)
|
||||
(if (< n 0)
|
||||
-1
|
||||
1))
|
||||
|
||||
(defun count-digit (number &optional (so-far 1))
|
||||
(let ((reduced (truncate (/ number 10))))
|
||||
(if (= reduced 0)
|
||||
so-far
|
||||
(count-digit reduced (1+ so-far)))))
|
||||
|
||||
(alexandria:define-constant +fnv-prime-32+ 16777619 :test #'=)
|
||||
|
||||
(alexandria:define-constant +fnv-offset-basis-32+ 2166136261 :test #'=)
|
||||
|
||||
(defun fnv-hash-32 (octects)
|
||||
(let ((hash +fnv-offset-basis-32+))
|
||||
(loop for i across octects do
|
||||
(setf hash (boole boole-xor hash i))
|
||||
(setf hash (ldb (byte 32 0) (* hash +fnv-prime-32+))))
|
||||
hash))
|
||||
|
||||
(defun string-fnv-hash-32 (s)
|
||||
(fnv-hash-32 (map 'vector #'char-code (coerce s 'list))))
|
||||
|
||||
(alexandria:define-constant +fnv-prime-256+
|
||||
374144419156711147060143317175368453031918731002211 :test #'=)
|
||||
|
||||
(alexandria:define-constant +fnv-offset-basis-256+
|
||||
100029257958052580907070968620625704837092796014241193945225284501741471925557
|
||||
:test #'=)
|
||||
|
||||
(defun fnv-hash-256 (octects)
|
||||
(let ((hash +fnv-offset-basis-256+))
|
||||
(loop for i across octects do
|
||||
(setf hash (boole boole-xor hash i))
|
||||
(setf hash (ldb (byte 256 0) (* hash +fnv-prime-256+))))
|
||||
hash))
|
||||
|
||||
(defun string-fnv-hash-256 (s)
|
||||
(fnv-hash-256 (map 'vector #'char-code (coerce s 'list))))
|
||||
|
||||
(alexandria:define-constant +lcg-modulo-pow+ 64 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-good-bit-starts+ 32 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-good-bit-size+ 32 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-modulo+ 18446744073709551616 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-max+ 4294967295 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-a+ 3935559000370003845 :test #'=)
|
||||
|
||||
(alexandria:define-constant +lcg-c+ 2691343689449507681 :test #'=)
|
||||
|
||||
(defparameter *lcg-seed* 0)
|
||||
|
||||
(defun lcg-set-seed (&optional (seed (get-universal-time)))
|
||||
(setf *lcg-seed* seed))
|
||||
|
||||
(defun lcg-next ()
|
||||
(setf *lcg-seed*
|
||||
(ldb (byte +lcg-modulo-pow+ 0)
|
||||
(+ (* +lcg-a+ *lcg-seed*) +lcg-c+)))
|
||||
(ldb (byte +lcg-good-bit-size+ +lcg-good-bit-starts+) *lcg-seed*))
|
||||
|
||||
(defun lcg-next01 ()
|
||||
(coerce (/ (lcg-next) +lcg-max+)
|
||||
'float))
|
||||
|
||||
(defgeneric lcg-next-upto (max))
|
||||
|
||||
(defmethod lcg-next-upto ((max float))
|
||||
(multiple-value-bind (integer-part remainder)
|
||||
(truncate max)
|
||||
(coerce (+ (* (lcg-next01) integer-part) (* (lcg-next01) remainder))
|
||||
'float)))
|
||||
|
||||
(defmethod lcg-next-upto ((max integer))
|
||||
(mod (lcg-next) max))
|
||||
|
||||
(defmethod lcg-next-upto ((max ratio))
|
||||
(lcg-next-upto (float max)))
|
||||
|
||||
(defun lcg-next-in-range (from to)
|
||||
(+ (lcg-next-upto (- to from)) from))
|
||||
|
||||
(defun lcg-next-in-range* (range)
|
||||
"range is a cons cell (from . to)"
|
||||
(lcg-next-in-range (car range) (cdr range)))
|
||||
|
||||
(defmacro with-lcg-seed ((&optional (seed `(get-universal-time))) &body body)
|
||||
`(let ((*lcg-seed* ,seed))
|
||||
,@body))
|
||||
|
||||
(defun get-random-float-sign ()
|
||||
(declare (optimize (speed 3) (debug 0) (safety 0)))
|
||||
(if (= (the integer (lcg-next-upto 2)) 0) 1.0 -1.0))
|
||||
|
||||
(defgeneric shellsort (sequence predicate &key key)
|
||||
(:documentation "Note: makes a new sequence"))
|
||||
|
||||
(defmethod shellsort ((sequence list) predicate &key (key #'identity))
|
||||
(call-next-method (copy-list sequence)
|
||||
predicate
|
||||
:key key))
|
||||
|
||||
(defmethod shellsort ((sequence vector) predicate &key (key #'identity))
|
||||
(call-next-method (alexandria:copy-array sequence)
|
||||
predicate
|
||||
:key key))
|
||||
|
||||
(defun tokuda-sequence (n)
|
||||
(do ((k 1 (1+ k))
|
||||
(h 1.0 (+ (* 2.25 h) 1.0))
|
||||
(res '()))
|
||||
((not (< h n)) res)
|
||||
(push (ceiling h) res)))
|
||||
|
||||
(defmethod shellsort (sequence predicate &key (key #'identity))
|
||||
(loop for gap in (tokuda-sequence (length sequence)) do
|
||||
(loop for i from gap below (length sequence) by 1 do
|
||||
(let ((tmp (elt sequence i)))
|
||||
(do ((j i (- j gap)))
|
||||
((not (and (>= j gap)
|
||||
(not (funcall predicate
|
||||
(funcall key (elt sequence (- j gap)))
|
||||
(funcall key tmp)))))
|
||||
(setf (elt sequence j) tmp))
|
||||
(let ((swp (elt sequence (- j gap))))
|
||||
(setf (elt sequence j) swp))))))
|
||||
sequence)
|
||||
|
||||
(defun multisort (bag fns)
|
||||
(shellsort bag
|
||||
#'(lambda (a b)
|
||||
(let ((partial (loop named outer for fn in fns do
|
||||
(cond
|
||||
((< (funcall fn a b) 0)
|
||||
(return-from outer t))
|
||||
((> (funcall fn a b) 0)
|
||||
(return-from outer nil))))))
|
||||
partial))))
|
||||
|
||||
(defun multisort* (bag &rest fns)
|
||||
(multisort bag fns))
|
||||
|
||||
(defmacro gen-multisort-test (fn-< fn-> fn-access)
|
||||
(alexandria:with-gensyms (a b access-a access-b)
|
||||
`(lambda (,a ,b)
|
||||
(let ((,access-a (funcall (misc:fn-delay ,fn-access) ,a))
|
||||
(,access-b (funcall (misc:fn-delay ,fn-access) ,b)))
|
||||
(cond
|
||||
((funcall (misc:fn-delay ,fn-<) ,access-a ,access-b)
|
||||
-1)
|
||||
((funcall (misc:fn-delay ,fn->) ,access-a ,access-b)
|
||||
1)
|
||||
(t 0))))))
|
||||
|
||||
(defparameter *default-epsilon* 1e-7)
|
||||
|
||||
(defmacro with-epsilon ((epsilon) &body body)
|
||||
`(let ((*default-epsilon* ,epsilon))
|
||||
,@body))
|
||||
|
||||
(defun add-epsilon-rel (v &optional (epsilon *default-epsilon*))
|
||||
(+ v (* epsilon v)))
|
||||
|
||||
(defun epsilon<= (a b &optional (epsilon *default-epsilon*))
|
||||
(or (<= a b)
|
||||
(epsilon= a b epsilon)))
|
||||
|
||||
(defun epsilon>= (a b &optional (epsilon *default-epsilon*))
|
||||
(or (>= a b)
|
||||
(epsilon= a b epsilon)))
|
||||
|
||||
(defun epsilon= (a b &optional (epsilon *default-epsilon*))
|
||||
(and (<= (- b epsilon) a (+ b epsilon))))
|
||||
|
||||
(defun binary-search (sequence value-looking-for
|
||||
&key
|
||||
(compare-fn #'<)
|
||||
(equal-fn #'=)
|
||||
(left-limit 0)
|
||||
(right-limit (1- (length sequence))))
|
||||
"Perform a binary search on `sequence' looking for
|
||||
`value-looking-for' using `equal-fn' as equality test function and
|
||||
`compare-fn' as comparing function. Values position where the value
|
||||
has been found in `sequence' or nil. `sequence' must be sorted in
|
||||
ascending order using the same predicate as `compare-fn'. Recursive."
|
||||
(when (not (misc:sequence-empty-p sequence))
|
||||
(assert (< right-limit (length sequence)))
|
||||
(assert (>= left-limit 0))
|
||||
(assert (< left-limit (length sequence)))
|
||||
(flet ((equals (element)
|
||||
(funcall equal-fn element value-looking-for))
|
||||
(less-than (a b)
|
||||
(funcall compare-fn a b)))
|
||||
(cond
|
||||
((>= left-limit right-limit)
|
||||
(if (equals (elt sequence left-limit))
|
||||
left-limit
|
||||
nil))
|
||||
(t
|
||||
(let* ((midpoint (floor (+ left-limit
|
||||
(/ (- right-limit left-limit)
|
||||
2))))
|
||||
(mid-point-value (elt sequence midpoint)))
|
||||
(cond
|
||||
((equals mid-point-value)
|
||||
midpoint)
|
||||
((less-than value-looking-for mid-point-value)
|
||||
(binary-search sequence
|
||||
value-looking-for
|
||||
:compare-fn compare-fn
|
||||
:equal-fn equal-fn
|
||||
:left-limit left-limit
|
||||
:right-limit (1- midpoint)))
|
||||
(t
|
||||
(binary-search sequence
|
||||
value-looking-for
|
||||
:compare-fn compare-fn
|
||||
:equal-fn equal-fn
|
||||
:left-limit (1+ midpoint)
|
||||
:right-limit right-limit)))))))))
|
|
@ -0,0 +1,127 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :open-attach-window)
|
||||
|
||||
(defclass open-attach-window (focus-marked-window
|
||||
simple-line-navigation-window
|
||||
title-window
|
||||
border-window)
|
||||
((status-id
|
||||
:initform nil
|
||||
:initarg :status-id
|
||||
:accessor status-id)))
|
||||
|
||||
(defmethod refresh-config :after ((object open-attach-window))
|
||||
(with-accessors ((croatoan-window croatoan-window)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)) object
|
||||
(let* ((theme-style (swconf:form-style swconf:+key-open-attach-window+))
|
||||
(fg (swconf:foreground theme-style))
|
||||
(bg (swconf:background theme-style))
|
||||
(selected-fg (swconf:selected-foreground theme-style))
|
||||
(selected-bg (swconf:selected-background theme-style))
|
||||
(win-w (truncate (/ (win-width specials:*main-window*) 2)))
|
||||
(win-h (truncate (/ (win-height specials:*main-window*) 2)))
|
||||
(x (truncate (- (/ (win-width specials:*main-window*) 2)
|
||||
(/ win-w 2))))
|
||||
(y (truncate (- (/ (win-height specials:*main-window*) 2)
|
||||
(/ win-h 2)))))
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background bg))
|
||||
(setf (bgcolor croatoan-window) bg)
|
||||
(setf (fgcolor croatoan-window) fg)
|
||||
(setf selected-line-fg selected-fg)
|
||||
(setf selected-line-bg selected-bg)
|
||||
(win-resize object win-w win-h)
|
||||
(win-move object x y)
|
||||
object)))
|
||||
|
||||
(defmethod resync-rows-db ((object open-attach-window) &key
|
||||
(redraw t)
|
||||
(suggested-message-index nil))
|
||||
(with-accessors ((rows rows)
|
||||
(status-id status-id)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)) object
|
||||
(flet ((make-rows (attach-names bg fg)
|
||||
(mapcar (lambda (name)
|
||||
(make-instance 'line
|
||||
:normal-text name
|
||||
:selected-text name
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg))
|
||||
attach-names)))
|
||||
(let ((attach-names (db:all-attachments-urls-to-status status-id)))
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(setf rows (make-rows attach-names
|
||||
selected-line-bg
|
||||
selected-line-fg))
|
||||
(when suggested-message-index
|
||||
(select-row object suggested-message-index))
|
||||
(when redraw
|
||||
(draw object)))))))
|
||||
|
||||
(defun init (status-id)
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *open-attach-window*
|
||||
(make-instance 'open-attach-window
|
||||
:title (_ "Attachments")
|
||||
:status-id status-id
|
||||
:single-row-height 1
|
||||
:uses-border-p t
|
||||
:keybindings keybindings:*open-attach-keymap*
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *open-attach-window*)
|
||||
(resync-rows-db *open-attach-window* :redraw nil)
|
||||
(when (rows *open-attach-window*)
|
||||
(select-row *open-attach-window* 0))
|
||||
(draw *open-attach-window*)
|
||||
*open-attach-window*))
|
||||
|
||||
(defun get-extension (file)
|
||||
(multiple-value-bind (matchedp res)
|
||||
(cl-ppcre:scan-to-strings "(?i)[a-z0-9]\(\\.[^.]+)(\\?.+)$" file)
|
||||
(when matchedp
|
||||
(first-elt res))))
|
||||
|
||||
(defun open-attachment (url)
|
||||
(flet ((add-extension (cached-value)
|
||||
(strcat (to-s cached-value) (get-extension url))))
|
||||
(let ((cached (db:cache-get-value url)))
|
||||
(if (not cached)
|
||||
(let* ((cached-file-name (add-extension (db:cache-put url)))
|
||||
(cached-output-file (os-utils:cached-file-path cached-file-name))
|
||||
(stream (get-url-content url)))
|
||||
(fs:create-file cached-output-file :skip-if-exists t)
|
||||
(with-open-file (out-stream
|
||||
cached-output-file
|
||||
:element-type '(unsigned-byte 8)
|
||||
:if-does-not-exist :error
|
||||
:if-exists :supersede
|
||||
:direction :output)
|
||||
(loop for byte = (read-byte stream nil nil) while byte do
|
||||
(write-byte byte out-stream)))
|
||||
(open-attachment url))
|
||||
(let ((cached-file (os-utils:cached-file-path (add-extension cached))))
|
||||
(if (or (not (fs:file-exists-p cached-file))
|
||||
(<= (fs:file-size cached-file) 0))
|
||||
(progn
|
||||
(db:cache-invalidate url)
|
||||
(open-attachment url))
|
||||
(os-utils:xdg-open cached-file)))))))
|
|
@ -0,0 +1,87 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :os-utils)
|
||||
|
||||
(alexandria:define-constant +proc-file-system+
|
||||
(concatenate 'string filesystem-utils:*directory-sep*
|
||||
"proc")
|
||||
:test #'string=)
|
||||
|
||||
(alexandria:define-constant +proc-cpuinfo+
|
||||
(concatenate 'string +proc-file-system+ filesystem-utils:*directory-sep*
|
||||
"cpuinfo")
|
||||
:test #'string=)
|
||||
|
||||
(declaim (ftype (function () fixnum) cpu-number))
|
||||
|
||||
(defun cpu-number ()
|
||||
#+windows (the fixnum 1)
|
||||
#-windows
|
||||
(with-open-file (stream +proc-cpuinfo+ :direction :input
|
||||
:if-does-not-exist :error)
|
||||
(do ((line (read-line stream nil nil) (read-line stream nil nil))
|
||||
(cpu-count 0))
|
||||
((not line) (the fixnum cpu-count))
|
||||
(when (cl-ppcre:scan "^processor" line)
|
||||
(incf cpu-count)))))
|
||||
|
||||
(defun xdg-open (file)
|
||||
(uiop:launch-program (format nil "xdg-open '~a'" file)
|
||||
:output nil))
|
||||
|
||||
(defun getenv (name)
|
||||
(nix:getenv name))
|
||||
|
||||
(defun default-temp-dir ()
|
||||
(or (os-utils:getenv "TMPDIR")
|
||||
"/tmp/"))
|
||||
|
||||
(defun external-editor ()
|
||||
(let ((error-message
|
||||
(_ "No editor found, please configure the 'editor' directive in your configuration file"))
|
||||
(editor (or (swconf:external-editor)
|
||||
(getenv "EDITOR"))))
|
||||
(if (null editor)
|
||||
(error error-message)
|
||||
(let ((space (cl-ppcre:scan "\\s" editor)))
|
||||
(if space
|
||||
(let ((exe (subseq editor 0 space))
|
||||
(args (subseq editor (1+ space))))
|
||||
(values exe args))
|
||||
(values editor nil))))))
|
||||
|
||||
(defun open-with-editor (file)
|
||||
(multiple-value-bind (exe args)
|
||||
(external-editor)
|
||||
(sb-ext:run-program exe
|
||||
(append (list args)
|
||||
(list file))
|
||||
:search t
|
||||
:wait t
|
||||
:input t
|
||||
:output t
|
||||
:error t)))
|
||||
|
||||
(defun exit-program (&optional (exit-code 0))
|
||||
(uiop:quit exit-code))
|
||||
|
||||
(defun user-cache-dir (&rest more)
|
||||
(fs:pathname->namestring (apply #'uiop:xdg-cache-home
|
||||
(append (list +program-name+) more))))
|
||||
|
||||
(defun cached-file-path (filename)
|
||||
(text-utils:strcat (user-cache-dir) fs:*directory-sep* filename))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,127 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :point-tracker)
|
||||
|
||||
(defclass point-tracker ()
|
||||
((point-position
|
||||
:initform 0
|
||||
:initarg :point-position
|
||||
:accessor point-position)
|
||||
(point-fg
|
||||
:initform :white
|
||||
:initarg :point-fg
|
||||
:accessor point-fg)
|
||||
(point-bg
|
||||
:initform :white
|
||||
:initarg :point-bg
|
||||
:accessor point-bg)
|
||||
(prompt
|
||||
:initform "> "
|
||||
:initarg :prompt
|
||||
:accessor prompt)))
|
||||
|
||||
(defmethod initialize-instance :after ((object point-tracker) &key &allow-other-keys)
|
||||
(with-accessors ((point-position point-position)
|
||||
(prompt prompt)) object
|
||||
(setf point-position (length prompt))))
|
||||
|
||||
(defgeneric no-prompt-point-pos (object))
|
||||
|
||||
(defgeneric move-point-left (object &key offset))
|
||||
|
||||
(defgeneric move-point-right (object max &key offset))
|
||||
|
||||
(defgeneric move-point (object to max))
|
||||
|
||||
(defgeneric move-point-to-end (object text))
|
||||
|
||||
(defgeneric move-point-to-start (object))
|
||||
|
||||
(defgeneric insert-at-point (object thing text))
|
||||
|
||||
(defgeneric delete-at-point (object text &key direction))
|
||||
|
||||
(defmethod no-prompt-point-pos ((object point-tracker))
|
||||
(- (point-position object)
|
||||
(length (prompt object))))
|
||||
|
||||
(defmethod move-point-left ((object point-tracker) &key (offset 1))
|
||||
(with-accessors ((point-position point-position)
|
||||
(prompt prompt)) object
|
||||
(setf point-position
|
||||
(max (length prompt)
|
||||
(- point-position offset)))
|
||||
point-position))
|
||||
|
||||
(defmethod move-point-right ((object point-tracker) max &key (offset 1))
|
||||
(with-accessors ((point-position point-position)
|
||||
(prompt prompt)) object
|
||||
(let ((prompt-length (length prompt)))
|
||||
(setf point-position
|
||||
(min (+ max prompt-length)
|
||||
(+ offset point-position))))
|
||||
point-position))
|
||||
|
||||
(defmethod move-point ((object point-tracker) to max)
|
||||
(with-accessors ((point-position point-position)
|
||||
(prompt prompt)) object
|
||||
(let ((prompt-length (length prompt)))
|
||||
(setf point-position
|
||||
(clamp (+ to prompt-length)
|
||||
prompt-length
|
||||
(+ max prompt-length)))
|
||||
point-position)))
|
||||
|
||||
(defmethod move-point-to-end ((object point-tracker) text)
|
||||
(let ((length (length text)))
|
||||
(move-point object length length)))
|
||||
|
||||
(defmethod move-point-to-start ((object point-tracker))
|
||||
(move-point object 0 1))
|
||||
|
||||
(defmethod insert-at-point ((object point-tracker) thing text)
|
||||
(with-accessors ((point-position point-position)) object
|
||||
(let* ((actual-point-pos (no-prompt-point-pos object))
|
||||
(res (strcat (subseq text 0 actual-point-pos)
|
||||
(to-s thing)
|
||||
(subseq text actual-point-pos))))
|
||||
(move-point-right object (length res))
|
||||
res)))
|
||||
|
||||
(defmethod insert-at-point ((object point-tracker) thing (text null))
|
||||
(insert-at-point object nil (to-s thing)))
|
||||
|
||||
(defmethod insert-at-point ((object point-tracker) (thing null) text)
|
||||
(prog1
|
||||
(to-s text)
|
||||
(move-point-right object (length text))))
|
||||
|
||||
(defmethod delete-at-point ((object point-tracker) text &key (direction :right))
|
||||
(with-accessors ((point-position point-position)) object
|
||||
(handler-bind ((conditions:out-of-bounds
|
||||
(lambda (c)
|
||||
(declare (ignore c))
|
||||
(invoke-restart 'misc:return-whole))))
|
||||
(if (eq direction
|
||||
:left)
|
||||
(if (> (no-prompt-point-pos object)
|
||||
0)
|
||||
(progn
|
||||
(move-point-left object)
|
||||
(to-s (safe-delete@ text (no-prompt-point-pos object))))
|
||||
(to-s text))
|
||||
(to-s (safe-delete@ text (no-prompt-point-pos object)))))))
|
|
@ -0,0 +1,182 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :priority-queue)
|
||||
|
||||
(defclass priority-queue ()
|
||||
((heap
|
||||
:initform (misc:make-array-frame 1 nil)
|
||||
:initarg :heap
|
||||
:accessor heap)
|
||||
(key-function
|
||||
:initform #'identity
|
||||
:initarg :key-function
|
||||
:accessor key-function)
|
||||
(compare-function
|
||||
:initform #'<
|
||||
:initarg :compare-function
|
||||
:accessor compare-function)
|
||||
(equal-function
|
||||
:initform (misc:make-array-frame 1 nil)
|
||||
:initarg :equal-function
|
||||
:accessor equal-function)))
|
||||
|
||||
(defmethod marshal:class-persistant-slots ((object priority-queue))
|
||||
(append '(heap)
|
||||
(call-next-method)))
|
||||
|
||||
(defgeneric get-children-pos (object parent-pos))
|
||||
|
||||
(defgeneric rearrange-bottom-up (object &optional pos))
|
||||
|
||||
(defgeneric rearrange-top-bottom (object &optional root-pos))
|
||||
|
||||
(defgeneric push-element (object val))
|
||||
|
||||
(defgeneric pop-element (object))
|
||||
|
||||
(defgeneric emptyp (object))
|
||||
|
||||
(defgeneric find-element (object element &key key-fn test-fn))
|
||||
|
||||
(defgeneric remove-element (object element))
|
||||
|
||||
(defgeneric count-elements-if (object predicate &key key-fn))
|
||||
|
||||
(defun get-parent-pos (pos)
|
||||
(floor (/ pos 2)))
|
||||
|
||||
(defmethod get-children-pos ((object priority-queue) parent-pos)
|
||||
(declare (integer parent-pos))
|
||||
(with-accessors ((heap heap)) object
|
||||
(list (and (< (* 2 parent-pos) (fill-pointer heap))
|
||||
(* 2 parent-pos))
|
||||
(and (< (1+ (* 2 parent-pos)) (fill-pointer heap))
|
||||
(1+ (* 2 parent-pos))))))
|
||||
|
||||
(defmethod rearrange-bottom-up ((object priority-queue)
|
||||
&optional (pos (1- (length (heap object)))))
|
||||
(with-accessors ((heap heap)
|
||||
(key-function key-function)
|
||||
(compare-function compare-function)) object
|
||||
(let ((parent-pos (get-parent-pos pos)))
|
||||
(when (and (> parent-pos 0)
|
||||
(funcall compare-function
|
||||
(funcall key-function (elt heap pos))
|
||||
(funcall key-function (elt heap parent-pos))))
|
||||
(let ((swp (elt heap parent-pos)))
|
||||
(setf (elt heap parent-pos) (elt heap pos))
|
||||
(setf (elt heap pos) swp))
|
||||
(rearrange-bottom-up object parent-pos)))))
|
||||
|
||||
(defmethod rearrange-top-bottom ((object priority-queue) &optional (root-pos 1))
|
||||
(with-accessors ((heap heap)
|
||||
(key-function key-function)
|
||||
(compare-function compare-function)
|
||||
(equal-function equal-function)) object
|
||||
(let* ((children (remove-if #'null (get-children-pos object root-pos)))
|
||||
(maximum-child (cond
|
||||
((null children)
|
||||
root-pos)
|
||||
((= (length children) 1)
|
||||
(first children))
|
||||
(t
|
||||
(if (funcall compare-function
|
||||
(funcall key-function (elt heap (first children)))
|
||||
(funcall key-function (elt heap (second children))))
|
||||
(first children)
|
||||
(second children))))))
|
||||
(when (not (funcall equal-function
|
||||
(funcall key-function (elt heap maximum-child))
|
||||
(funcall key-function (elt heap root-pos))))
|
||||
(let ((swp (elt heap root-pos)))
|
||||
(when (funcall compare-function
|
||||
(funcall key-function (elt heap maximum-child))
|
||||
(funcall key-function swp))
|
||||
(setf (elt heap root-pos) (elt heap maximum-child))
|
||||
(setf (elt heap maximum-child) swp)))
|
||||
(rearrange-top-bottom object maximum-child)))))
|
||||
|
||||
(defmethod push-element ((object priority-queue) val)
|
||||
(with-accessors ((heap heap)) object
|
||||
(vector-push-extend val heap)
|
||||
(rearrange-bottom-up object)))
|
||||
|
||||
(defmethod emptyp ((object priority-queue))
|
||||
(with-accessors ((heap heap)) object
|
||||
(<= (length heap) 1)))
|
||||
|
||||
(defmethod pop-element ((object priority-queue))
|
||||
(with-accessors ((heap heap)) object
|
||||
(if (emptyp object)
|
||||
nil
|
||||
(prog1
|
||||
(elt heap 1)
|
||||
(if (= (length heap) 2)
|
||||
(setf (fill-pointer heap) (1- (fill-pointer heap)))
|
||||
(progn
|
||||
(setf (elt heap 1) (alexandria:last-elt heap))
|
||||
(setf (fill-pointer heap) (1- (fill-pointer heap)))
|
||||
(rearrange-top-bottom object)))))))
|
||||
|
||||
(defmacro with-min-queue ((queue compare sort key) &body body)
|
||||
`(let ((,queue (make-instance 'priority-queue
|
||||
:equal-function ,compare
|
||||
:compare-function ,sort
|
||||
:key-function ,key)))
|
||||
,@body))
|
||||
|
||||
(defmethod find-element ((object priority-queue) element
|
||||
&key
|
||||
(key-fn (key-function object))
|
||||
(test-fn (equal-function object)))
|
||||
(find element
|
||||
(heap object)
|
||||
:key key-fn
|
||||
:test test-fn
|
||||
:start 1))
|
||||
|
||||
(defmethod count-elements-if (object predicate
|
||||
&key
|
||||
(key-fn (key-function object)))
|
||||
(count-if predicate
|
||||
(heap object)
|
||||
:key key-fn
|
||||
:start 1))
|
||||
|
||||
(defmethod remove-element ((object priority-queue) element)
|
||||
(with-accessors ((heap heap)
|
||||
(key-function key-function)
|
||||
(equal-function equal-function)
|
||||
(compare-function compare-function)) object
|
||||
(let ((pos (position element
|
||||
heap
|
||||
:start 1
|
||||
:key (key-function object)
|
||||
:test (equal-function object)))
|
||||
(old-length (length heap)))
|
||||
(if (and pos
|
||||
(= pos 1))
|
||||
(pop-element object)
|
||||
(when pos
|
||||
(misc:swap (elt heap pos)
|
||||
(elt heap (1- (length heap))))
|
||||
(setf (fill-pointer heap) (1- (fill-pointer heap)))
|
||||
(when (not (= pos (1- old-length)))
|
||||
(let ((parent-pos (get-parent-pos pos)))
|
||||
(if (funcall compare-function (elt heap pos) (elt heap parent-pos))
|
||||
(rearrange-bottom-up object pos)
|
||||
(rearrange-top-bottom object pos)))))))))
|
|
@ -0,0 +1,769 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :program-events)
|
||||
|
||||
(defparameter *id-lock* (bt:make-recursive-lock))
|
||||
|
||||
(defparameter *event-id* 0)
|
||||
|
||||
;; used only in batch mode from the command line
|
||||
(defparameter *process-events-immediately* nil
|
||||
"Used only in batch mode from the command line. Instead of pushing the event on a priority queue that will be picked by a thread process the event immediately")
|
||||
|
||||
(define-constant +standard-event-priority+ 10 :test #'=)
|
||||
|
||||
;; keep this function stricly monotonic otherwise the order of
|
||||
;; elements in priority queue is going to be messed up
|
||||
(defun-w-lock next-id () *id-lock*
|
||||
(incf *event-id*)
|
||||
*event-id*)
|
||||
|
||||
(defmacro with-notify-errors (&body body)
|
||||
`(handler-case
|
||||
(progn
|
||||
,@body)
|
||||
(error (e)
|
||||
(ui:notify (format nil (_ "Error: ~a") e)
|
||||
:life (* (swconf:config-notification-life) 5)
|
||||
:as-error t))))
|
||||
|
||||
(defclass program-event ()
|
||||
((event-id
|
||||
:initform (next-id)
|
||||
:initarg :event-id
|
||||
:accessor event-id)
|
||||
(payload
|
||||
:initform nil
|
||||
:initarg :payload
|
||||
:accessor payload)
|
||||
(priority
|
||||
:initform +standard-event-priority+
|
||||
:initarg :priority
|
||||
:accessor priority)))
|
||||
|
||||
(defmethod print-object ((object program-event) stream)
|
||||
(print-unreadable-object (object stream :type t :identity nil)
|
||||
(format stream "id ~a priority ~a" (event-id object) (priority object))))
|
||||
|
||||
(defgeneric process-event (object))
|
||||
|
||||
(defgeneric reinitialize-id (object))
|
||||
|
||||
(defmethod reinitialize-id ((object program-event))
|
||||
(setf (event-id object)
|
||||
(next-id))
|
||||
object)
|
||||
|
||||
(defclass events-queue (priority-queue)
|
||||
((lock
|
||||
:initform (bt:make-recursive-lock)
|
||||
:initarg :lock
|
||||
:accessor lock)))
|
||||
|
||||
(defun queue-compare-predicate (a b)
|
||||
(let ((same-priority-p (= (priority a)
|
||||
(priority b))))
|
||||
(if same-priority-p
|
||||
(< (event-id a)
|
||||
(event-id b))
|
||||
(< (priority a)
|
||||
(priority b)))))
|
||||
|
||||
(defun queue-equals-predicate (a b)
|
||||
(= (event-id a)
|
||||
(event-id b)))
|
||||
|
||||
(defmethod initialize-instance :after ((object events-queue) &key &allow-other-keys)
|
||||
(with-accessors ((key-function key-function)
|
||||
(compare-function compare-function)
|
||||
(equal-function equal-function)) object
|
||||
(setf key-function #'identity)
|
||||
(setf equal-function #'queue-equals-predicate)
|
||||
(setf compare-function #'queue-compare-predicate)))
|
||||
|
||||
(defmacro wrapped-in-lock ((queue) &body body)
|
||||
(with-gensyms (lock)
|
||||
`(with-accessors ((,lock lock)) ,queue
|
||||
(with-lock (,lock)
|
||||
,@body))))
|
||||
|
||||
(defparameter *events-queue* (make-instance 'events-queue))
|
||||
|
||||
(defun push-event (event)
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(if *process-events-immediately*
|
||||
(process-event event)
|
||||
(push-element *events-queue* event))))
|
||||
|
||||
(defun pop-event ()
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(pop-element *events-queue*)))
|
||||
|
||||
(defun remove-event (event)
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(remove-element *events-queue* event)))
|
||||
|
||||
(defun find-event (event &key (key-fn #'identity) (test-fn #'eq))
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(find-element *events-queue* event :test-fn test-fn :key-fn key-fn)))
|
||||
|
||||
(defun no-events-p ()
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(emptyp *events-queue*)))
|
||||
|
||||
(defun event-available-p ()
|
||||
(not (no-events-p)))
|
||||
|
||||
(defun count-events (predicate)
|
||||
(wrapped-in-lock (*events-queue*)
|
||||
(count-elements-if *events-queue* predicate :key-fn #'identity)))
|
||||
|
||||
(defclass event-on-own-thread (program-event)
|
||||
((lock
|
||||
:initform (bt:make-recursive-lock)
|
||||
:initarg :lock
|
||||
:accessor lock)
|
||||
(condition-variable
|
||||
:initform (bt:make-condition-variable)
|
||||
:initarg :condition-variable
|
||||
:accessor condition-variable))
|
||||
(:documentation "This is the parent of all events that are
|
||||
generated in athread that is not the main thread, contains a
|
||||
condition variable and associated lock"))
|
||||
|
||||
(defclass ask-user-input-string-event (event-on-own-thread)
|
||||
((prompt
|
||||
:initform +default-command-prompt+
|
||||
:initarg :prompt
|
||||
:accessor prompt)
|
||||
(initial-value
|
||||
:initform nil
|
||||
:initarg :initial-value
|
||||
:accessor initial-value)
|
||||
(complete-fn
|
||||
:initform nil
|
||||
:initarg :complete-fn
|
||||
:accessor complete-fn))
|
||||
(:documentation "This events, when processed, will prepare the
|
||||
command-window `specials:*command-window*' to ask for user
|
||||
input. The most importatn thing is that the process-event will set
|
||||
the slot `command-window:event-to-answer' with this events and will
|
||||
set the payload of this events with the user provided string."))
|
||||
|
||||
(defmethod initialize-instance :after ((object ask-user-input-string-event)
|
||||
&key &allow-other-keys)
|
||||
(setf (priority object) (truncate (/ +standard-event-priority+ 2))))
|
||||
|
||||
(defmethod process-event ((object ask-user-input-string-event))
|
||||
(with-accessors ((prompt prompt)
|
||||
(initial-value initial-value)
|
||||
(complete-fn complete-fn)) object
|
||||
(setf (command-window:event-to-answer specials:*command-window*)
|
||||
object)
|
||||
(setf (point-tracker:prompt specials:*command-window*)
|
||||
prompt)
|
||||
(setf complete:*complete-function* complete-fn)
|
||||
(command-window:set-string-mode specials:*command-window*)
|
||||
(command-window:set-history-most-recent specials:*command-window* prompt)
|
||||
(setf (command-window:command-line specials:*command-window*)
|
||||
initial-value)
|
||||
(point-tracker:move-point-to-end specials:*command-window* initial-value)
|
||||
(windows:draw specials:*command-window*)))
|
||||
|
||||
(defclass user-input-string-event (ask-user-input-string-event)
|
||||
()
|
||||
(:documentation "When user provided a string as this event is
|
||||
generated. When processed it just wlii notify the condition variable
|
||||
of the slots `command-window:event-to-answer' in the object
|
||||
`specials:*command-window*' so that the callee thread can restart
|
||||
the computation with the input."))
|
||||
|
||||
(defmethod initialize-instance :after ((object user-input-string-event)
|
||||
&key &allow-other-keys)
|
||||
(setf (priority object) (truncate (/ +standard-event-priority+ 2))))
|
||||
|
||||
(defmethod process-event ((object user-input-string-event))
|
||||
(with-accessors ((lock lock)
|
||||
(condition-variable condition-variable)) object
|
||||
(with-lock (lock)
|
||||
(bt:condition-notify condition-variable))))
|
||||
|
||||
(defclass notify-user-event (program-event)
|
||||
((added-to-pending-p
|
||||
:initform nil
|
||||
:initarg :added-to-pending
|
||||
:reader added-to-pending-p
|
||||
:writer (setf added-to-pending))
|
||||
(life
|
||||
:initform nil
|
||||
:initarg :life
|
||||
:accessor life)
|
||||
(notify-error
|
||||
:initform nil
|
||||
:initarg :notify-error
|
||||
:accessor notify-error)))
|
||||
|
||||
(defun notify-user-event-p (a)
|
||||
(typep a 'notify-user-event))
|
||||
|
||||
(defmethod process-event ((object notify-user-event))
|
||||
(with-accessors ((added-to-pending-p added-to-pending-p)
|
||||
(notify-error notify-error)) object
|
||||
(let ((other-notification-win (first (mtree:find-child-if specials:*main-window*
|
||||
#'notify-window:notify-window-p)))
|
||||
(pending-before (count-events #'notify-user-event-p)))
|
||||
(if (null other-notification-win)
|
||||
(let* ((life (or (life object)
|
||||
(swconf:config-notification-life)))
|
||||
(notify-win (notify-window:make-notification-window (payload object)
|
||||
life
|
||||
:pending
|
||||
pending-before
|
||||
:notify-error
|
||||
notify-error)))
|
||||
(notify-window:draw-pending notify-win))
|
||||
(progn
|
||||
(when (not added-to-pending-p)
|
||||
(setf (notify-window:pending other-notification-win)
|
||||
(1+ pending-before))
|
||||
(notify-window:draw-pending other-notification-win)
|
||||
(setf (added-to-pending object) t))
|
||||
(progn
|
||||
(setf (event-id object) ; id must be monotonic, so we need to give the event a new one
|
||||
(next-id))
|
||||
(push-event object)))))))
|
||||
|
||||
(defclass remove-notify-user-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object remove-notify-user-event))
|
||||
(let ((win (payload object)))
|
||||
(mtree:remove-child specials:*main-window* win)))
|
||||
|
||||
(defclass update-timeline-event (program-event)
|
||||
((timeline-type
|
||||
:initform nil
|
||||
:initarg :timeline-type
|
||||
:accessor timeline-type)
|
||||
(folder
|
||||
:initform nil
|
||||
:initarg :folder
|
||||
:accessor folder)
|
||||
(local
|
||||
:initform nil
|
||||
:initarg :localp
|
||||
:reader localp
|
||||
:writer (setf local))
|
||||
(min-id
|
||||
:initform nil
|
||||
:initarg :min-id
|
||||
:accessor min-id)))
|
||||
|
||||
(defmethod process-event ((object update-timeline-event))
|
||||
"Update a timeline, save messages, performs topological sorts"
|
||||
(let ((statuses (payload object))
|
||||
(timeline-type (timeline-type object))
|
||||
(folder (folder object)))
|
||||
#+debug-mode
|
||||
(let ((dump (with-output-to-string (stream)
|
||||
(mapcar (lambda (toot) (tooter::present toot stream))
|
||||
statuses))))
|
||||
(dbg "statuses ~a" dump))
|
||||
(loop for status in statuses do
|
||||
(db:update-db status
|
||||
:timeline timeline-type
|
||||
:folder folder
|
||||
:skip-ignored-p t))
|
||||
(db:renumber-timeline-message-index timeline-type
|
||||
folder
|
||||
:account-id nil)))
|
||||
|
||||
(defclass fetch-remote-status-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object fetch-remote-status-event))
|
||||
(let ((status (payload object)))
|
||||
#+debug-mode
|
||||
(let ((dump (with-output-to-string (stream)
|
||||
(tooter::present status stream))))
|
||||
(dbg "fetch single status ~a" dump))
|
||||
(db:update-db status)))
|
||||
|
||||
(defclass search-regex-message-content-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object search-regex-message-content-event))
|
||||
(let ((regexp (payload object)))
|
||||
(message-window:search-regex specials:*message-window* regexp)))
|
||||
|
||||
(defclass thread-goto-message (program-event) ())
|
||||
|
||||
(defmethod process-event ((object thread-goto-message))
|
||||
(let ((message-index (payload object)))
|
||||
(thread-window:goto-message specials:*thread-window* message-index)))
|
||||
|
||||
(defclass thread-search-event (program-event)
|
||||
((search-direction
|
||||
:initform nil
|
||||
:initarg :search-direction
|
||||
:accessor search-direction)))
|
||||
|
||||
(defclass thread-search-message-body-event (thread-search-event) ())
|
||||
|
||||
(defmethod process-event ((object thread-search-message-body-event))
|
||||
(let ((text-looking-for (payload object))
|
||||
(search-direction (search-direction object)))
|
||||
(if (eq :next search-direction)
|
||||
(thread-window:search-next-message-body specials:*thread-window* text-looking-for)
|
||||
(thread-window:search-previous-message-body specials:*thread-window* text-looking-for))))
|
||||
|
||||
(defclass thread-search-message-meta-event (thread-search-event) ())
|
||||
|
||||
(defmethod process-event ((object thread-search-message-meta-event))
|
||||
(let ((text-looking-for (payload object))
|
||||
(search-direction (search-direction object)))
|
||||
(if (eq :next search-direction)
|
||||
(thread-window:search-next-message-meta specials:*thread-window* text-looking-for)
|
||||
(thread-window:search-previous-message-meta specials:*thread-window* text-looking-for))))
|
||||
|
||||
(defclass delete-all-status-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object delete-all-status-event))
|
||||
(db:forget-all-statuses-marked-deleted) ; do not change the order. Forget, then delete.
|
||||
(db:delete-all-statuses-marked-deleted)
|
||||
(db:renumber-all-timelines))
|
||||
|
||||
(defclass quit-program-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object quit-program-event))
|
||||
(ui:quit-program))
|
||||
|
||||
(defclass error-message-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object error-message-event))
|
||||
(command-window:add-error-message specials:*command-window* (payload object)))
|
||||
|
||||
(defclass info-message-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object info-message-event))
|
||||
(command-window:add-info-message specials:*command-window* (payload object)))
|
||||
|
||||
(defclass dialog-event (program-event)
|
||||
((buttons
|
||||
:initform nil
|
||||
:initarg :buttons
|
||||
:accessor buttons)
|
||||
(title
|
||||
:initform nil
|
||||
:initarg :title
|
||||
:accessor title)))
|
||||
|
||||
(defclass error-dialog-event (dialog-event)
|
||||
((buttons
|
||||
:initform nil
|
||||
:initarg :buttons
|
||||
:accessor buttons)
|
||||
(title
|
||||
:initform nil
|
||||
:initarg :title
|
||||
:accessor title)))
|
||||
|
||||
(defmethod process-event ((object error-dialog-event))
|
||||
(let ((dialog-window (windows:make-error-message-dialog specials:*main-window*
|
||||
(title object)
|
||||
(payload object)
|
||||
(buttons object))))
|
||||
(windows:menu-select dialog-window)))
|
||||
|
||||
(defclass info-dialog-event (dialog-event) ())
|
||||
|
||||
(defmethod process-event ((object info-dialog-event))
|
||||
(let ((dialog-window (windows:make-info-message-dialog specials:*main-window*
|
||||
(title object)
|
||||
(payload object)
|
||||
(buttons object))))
|
||||
(windows:menu-select dialog-window)))
|
||||
|
||||
(defclass move-selected-tree-event (program-event)
|
||||
((new-folder
|
||||
:initform nil
|
||||
:initarg :new-folder
|
||||
:accessor new-folder)))
|
||||
|
||||
(defmethod process-event ((object move-selected-tree-event))
|
||||
(let ((selected-fields (line-oriented-window:selected-row-fields
|
||||
specials:*thread-window*)))
|
||||
(if selected-fields
|
||||
(db:move-tree-to-folder (db:row-message-timeline selected-fields)
|
||||
(db:row-message-folder selected-fields)
|
||||
(db:row-message-index selected-fields)
|
||||
(new-folder object))
|
||||
(ui:error-message (_ "No message selected!")))))
|
||||
|
||||
(defclass event-with-message-index ()
|
||||
((message-index
|
||||
:initform db:+message-index-start+
|
||||
:initarg :message-index
|
||||
:accessor message-index)))
|
||||
|
||||
(defclass event-with-timeline-and-folder ()
|
||||
((new-folder
|
||||
:initform nil
|
||||
:initarg :new-folder
|
||||
:accessor new-folder)
|
||||
(new-timeline
|
||||
:initform nil
|
||||
:initarg :new-timeline
|
||||
:accessor new-timeline)))
|
||||
|
||||
(defclass refresh-thread-windows-event (program-event
|
||||
event-with-message-index
|
||||
event-with-timeline-and-folder)
|
||||
())
|
||||
|
||||
(defmethod process-event ((object refresh-thread-windows-event))
|
||||
(with-accessors ((new-folder new-folder)
|
||||
(new-timeline new-timeline)
|
||||
(message-index message-index)) object
|
||||
(assert message-index)
|
||||
(when new-timeline
|
||||
(setf (thread-window:timeline-type specials:*thread-window*)
|
||||
new-timeline))
|
||||
(when new-folder
|
||||
(setf (thread-window:timeline-folder specials:*thread-window*)
|
||||
new-folder))
|
||||
(line-oriented-window:resync-rows-db specials:*thread-window*
|
||||
:suggested-message-index message-index
|
||||
:redraw t)))
|
||||
|
||||
(defun change-status-values (event function-change)
|
||||
(with-accessors ((payload payload)
|
||||
(message-index message-index)) event
|
||||
(when-let ((status-to-change payload))
|
||||
(funcall function-change status-to-change)
|
||||
(client:fetch-remote-status status-to-change)
|
||||
(let* ((refresh-event (make-instance 'refresh-thread-windows-event
|
||||
:message-index message-index)))
|
||||
(push-event refresh-event)))))
|
||||
|
||||
(defclass favourite-status-event (program-event event-with-message-index) ())
|
||||
|
||||
(defmethod process-event ((object favourite-status-event))
|
||||
(with-notify-errors
|
||||
(change-status-values object #'api-client:favourite-status)))
|
||||
|
||||
(defclass unfavourite-status-event (program-event event-with-message-index) ())
|
||||
|
||||
(defmethod process-event ((object unfavourite-status-event))
|
||||
(with-notify-errors
|
||||
(change-status-values object #'api-client:unfavourite-status)))
|
||||
|
||||
(defclass reblog-status-event (program-event event-with-message-index) ())
|
||||
|
||||
(defmethod process-event ((object reblog-status-event))
|
||||
(with-notify-errors
|
||||
(change-status-values object #'api-client:reblog-status)))
|
||||
|
||||
(defclass unreblog-status-event (program-event event-with-message-index) ())
|
||||
|
||||
(defmethod process-event ((object unreblog-status-event))
|
||||
(with-notify-errors
|
||||
(change-status-values object #'api-client:unreblog-status)))
|
||||
|
||||
(defclass unignore-user-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object unignore-user-event))
|
||||
(let ((username (payload object)))
|
||||
(db:unignore-author username)))
|
||||
|
||||
(defclass send-message-change-subject-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object send-message-change-subject-event))
|
||||
(let ((new-subject (payload object)))
|
||||
(setf (sending-message:subject (sending-message:message-data specials:*send-message-window*))
|
||||
new-subject)
|
||||
(windows:draw specials:*send-message-window*)))
|
||||
|
||||
(defclass send-message-change-visibility-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object send-message-change-visibility-event))
|
||||
(let ((new-visibility (payload object))
|
||||
(message-data (sending-message:message-data specials:*send-message-window*)))
|
||||
(setf (sending-message:visibility message-data) new-visibility)
|
||||
(windows:draw specials:*send-message-window*)))
|
||||
|
||||
(defclass open-send-message-window-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object open-send-message-window-event))
|
||||
(let ((message-data (payload object)))
|
||||
(sending-message:init message-data specials:*main-window*)
|
||||
(ui:focus-to-send-message-window)
|
||||
(windows:draw specials:*send-message-window*)))
|
||||
|
||||
(defclass send-message-add-attachment-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object send-message-add-attachment-event))
|
||||
(with-accessors ((croatoan-window windows:croatoan-window)) specials:*send-message-window*
|
||||
(let* ((new-attachment (payload object))
|
||||
(fg (croatoan:fgcolor croatoan-window))
|
||||
(bg (croatoan:bgcolor croatoan-window))
|
||||
(line (make-instance 'line-oriented-window:line
|
||||
:normal-text new-attachment
|
||||
:selected-text new-attachment
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg)))
|
||||
(setf (line-oriented-window:rows specials:*send-message-window*)
|
||||
(append (line-oriented-window:rows specials:*send-message-window*)
|
||||
(list line)))
|
||||
(line-oriented-window:unselect-all specials:*send-message-window*)
|
||||
(line-oriented-window:select-row specials:*send-message-window* 0)
|
||||
(windows:draw specials:*send-message-window*))))
|
||||
|
||||
(defclass send-message-event (program-event)
|
||||
((use-ui-notification
|
||||
:initform nil
|
||||
:initarg :use-ui-notification
|
||||
:reader use-ui-notification-p
|
||||
:writer use-ui-notification)))
|
||||
|
||||
(defmethod process-event ((object send-message-event))
|
||||
(with-accessors ((message-data sending-message:message-data)
|
||||
(rows line-oriented-window:rows)) specials:*send-message-window*
|
||||
(with-accessors ((body sending-message:body)
|
||||
(subject sending-message:subject)
|
||||
(reply-to sending-message:reply-to)
|
||||
(visibility sending-message:visibility)) message-data
|
||||
(let* ((attachments (mapcar #'line-oriented-window:normal-text rows)))
|
||||
(hooks:run-hook 'hooks:*before-sending-message* object)
|
||||
(msg-utils:maybe-crypt-message specials:*send-message-window*
|
||||
:notify-cant-crypt (use-ui-notification-p object))
|
||||
(let ((exceeding-characters (ui:message-exceeds-server-limit-p body)))
|
||||
(if exceeding-characters
|
||||
(ui:exceeding-characters-notify exceeding-characters)
|
||||
(progn
|
||||
(client:send-status body
|
||||
reply-to
|
||||
attachments
|
||||
subject
|
||||
(make-keyword (string-upcase visibility)))
|
||||
(ui:notify (_ "Message sent."))
|
||||
(ui:close-send-message-window))))))))
|
||||
|
||||
(defclass follow-user-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object follow-user-event))
|
||||
(when-let ((username (payload object)))
|
||||
(when (find username (db:all-unfollowed-usernames) :test #'string=)
|
||||
(let ((user-id (db:acct->id username)))
|
||||
(client:follow-user user-id)
|
||||
(db:add-to-followers user-id)))))
|
||||
|
||||
(defclass unfollow-user-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object unfollow-user-event))
|
||||
(when-let ((username (payload object)))
|
||||
(when (find username (db:all-followed-usernames) :test #'string=)
|
||||
(let ((user-id (db:acct->id username)))
|
||||
(client:unfollow-user user-id)
|
||||
(db:remove-from-followers user-id)))))
|
||||
|
||||
(defclass open-follow-requests-window-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object open-follow-requests-window-event))
|
||||
(with-notify-errors
|
||||
(multiple-value-bind (accounts usernames)
|
||||
(api-client:follow-requests)
|
||||
(when accounts
|
||||
(follow-requests:init accounts usernames specials:*main-window*)
|
||||
(ui:focus-to-follow-requests-window)
|
||||
(windows:draw specials:*follow-requests-window*)))))
|
||||
|
||||
(defclass subscribe-tags-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object subscribe-tags-event))
|
||||
(when-let* ((tags (payload object)))
|
||||
(loop for tag in (cl-ppcre:split db:+tag-separator+ tags) do
|
||||
(db:subscribe-to-tag tag))))
|
||||
|
||||
(defclass unsubscribe-tags-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object unsubscribe-tags-event))
|
||||
(when-let* ((tag (payload object)))
|
||||
(db:unsubscribe-to-tag tag)))
|
||||
|
||||
(defclass update-last-refresh-subscribe-tags-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object update-last-refresh-subscribe-tags-event))
|
||||
(db:update-last-seen-status-subscribed-tag))
|
||||
|
||||
(defclass notify-fetched-new-tag-messages-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object notify-fetched-new-tag-messages-event))
|
||||
(loop for tag in (db:all-tags-with-new-message-fetched) do
|
||||
(let ((message (format nil
|
||||
(_ "Downloaded new messages for tag ~a")
|
||||
(db:tag->folder-name tag))))
|
||||
(ui:notify message))))
|
||||
|
||||
(defclass tag-mark-got-messages-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object tag-mark-got-messages-event))
|
||||
(loop for tag in (db:all-tags-with-new-message-fetched) do
|
||||
(db:mark-tag-got-new-messages tag)))
|
||||
|
||||
(defclass refresh-tag-window-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object refresh-tag-window-event))
|
||||
(tags-window:resync-rows-db specials:*tags-window*))
|
||||
|
||||
(defclass update-conversations-event (program-event
|
||||
event-with-timeline-and-folder)
|
||||
())
|
||||
|
||||
(defun add-new-conversations ()
|
||||
(let* ((new-conversations (api-client:conversations :root-only t))
|
||||
(all-conversations-id (db:all-conversations-id :remove-ignored nil))
|
||||
(new-conversations (remove-if (lambda (conversation)
|
||||
(find-if (lambda (a)
|
||||
(string= (api-client:id conversation)
|
||||
a))
|
||||
all-conversations-id))
|
||||
new-conversations)))
|
||||
(loop for new-conversation in new-conversations do
|
||||
(let ((root-id (client:conversation-root-id new-conversation)))
|
||||
(when (not (db:conversation-root-captured-p root-id))
|
||||
(db:add-conversation (api-client:id new-conversation)
|
||||
root-id))))))
|
||||
|
||||
(defun fetch-conversations (message-root-id conversation-folder)
|
||||
(let* ((conversation-tree (api-client:expand-conversations-tree message-root-id))
|
||||
(event (make-instance 'update-timeline-event
|
||||
:payload conversation-tree
|
||||
:timeline-type db:+default-converation-timeline+
|
||||
:folder conversation-folder
|
||||
:localp nil)))
|
||||
(push-event event)
|
||||
conversation-tree))
|
||||
|
||||
(defmethod process-event ((object update-conversations-event))
|
||||
(with-accessors ((new-timeline new-timeline)
|
||||
(new-folder new-folder)) object
|
||||
(with-notify-errors
|
||||
(add-new-conversations)
|
||||
(let* ((all-conversations (db:all-conversations)))
|
||||
(loop for conversation in all-conversations do
|
||||
(let* ((conversation-root (db:row-conversation-root-status-id conversation))
|
||||
(conversation-folder (db:row-conversation-folder conversation)))
|
||||
(fetch-conversations conversation-root conversation-folder)))
|
||||
;; refresh-ui
|
||||
(let ((refresh-thread (make-instance 'refresh-thread-windows-event
|
||||
:new-timeline new-timeline
|
||||
:new-folder new-folder))
|
||||
(refresh-conversation
|
||||
(make-instance 'refresh-conversations-window-event)))
|
||||
(push-event refresh-thread)
|
||||
(push-event refresh-conversation))))))
|
||||
|
||||
(defclass change-conversation-name-event (program-event)
|
||||
((old-name
|
||||
:initform nil
|
||||
:initarg :old-name
|
||||
:accessor old-name)
|
||||
(new-name
|
||||
:initform nil
|
||||
:initarg :new-name
|
||||
:accessor new-name)))
|
||||
|
||||
(defmethod process-event ((object change-conversation-name-event))
|
||||
(db:change-conversation-name (old-name object)
|
||||
(new-name object)))
|
||||
|
||||
(defclass refresh-conversations-window-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object refresh-conversations-window-event))
|
||||
(conversations-window:resync-rows-db specials:*conversations-window*))
|
||||
|
||||
(defclass ignore-conversations-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object ignore-conversations-event))
|
||||
(when-let* ((selected-row (line-oriented-window:selected-row
|
||||
specials:*conversations-window*))
|
||||
(folder (line-oriented-window:normal-text selected-row))
|
||||
(refresh-event (make-instance 'refresh-conversations-window-event)))
|
||||
(db:ignore-conversation folder)))
|
||||
|
||||
(defclass delete-conversations-event (program-event) ())
|
||||
|
||||
(defmethod process-event ((object delete-conversations-event))
|
||||
(when-let* ((selected-row (line-oriented-window:selected-row
|
||||
specials:*conversations-window*))
|
||||
(fields (line-oriented-window:selected-row-fields
|
||||
specials:*conversations-window*))
|
||||
(folder (line-oriented-window:normal-text selected-row))
|
||||
(id (db:conversation-id fields))
|
||||
(refresh-event (make-instance 'refresh-conversations-window-event)))
|
||||
(with-notify-errors
|
||||
(api-client:delete-conversation id)
|
||||
(db:delete-conversation folder))))
|
||||
|
||||
(defclass report-status-event (program-event)
|
||||
((status-id
|
||||
:initform nil
|
||||
:initarg :status-id
|
||||
:accessor status-id)
|
||||
(account-id
|
||||
:initform nil
|
||||
:initarg :account-id
|
||||
:accessor account-id)
|
||||
(comment
|
||||
:initform nil
|
||||
:initarg :comment
|
||||
:accessor comment)
|
||||
(forwardp
|
||||
:initform nil
|
||||
:initarg :forwardp
|
||||
:accessor forwardp)))
|
||||
|
||||
(defmethod process-event ((object report-status-event))
|
||||
(with-accessors ((status-id status-id)
|
||||
(account-id account-id)
|
||||
(comment comment)
|
||||
(forwardp forwardp)) object
|
||||
(with-notify-errors
|
||||
(api-client:make-report account-id status-id comment forwardp))))
|
||||
|
||||
(defclass add-crypto-data-event (program-event)
|
||||
((username
|
||||
:initform nil
|
||||
:initarg :username
|
||||
:accessor username)
|
||||
(key
|
||||
:initform nil
|
||||
:initarg :key
|
||||
:accessor key)))
|
||||
|
||||
(defmethod process-event ((object add-crypto-data-event))
|
||||
(with-accessors ((username username)
|
||||
(key key)) object
|
||||
(db:import-crypto-data (db:acct->id username)
|
||||
key)))
|
||||
|
||||
;;;; end events
|
||||
|
||||
(defun dispatch-program-events ()
|
||||
(when (event-available-p)
|
||||
(process-event (pop-event))))
|
|
@ -0,0 +1,102 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :queue)
|
||||
|
||||
(defparameter *queue* (misc:make-array-frame 0))
|
||||
|
||||
(defparameter *equal-function* #'equalp)
|
||||
|
||||
(defparameter *key-function* #'identity)
|
||||
|
||||
(defun push (val)
|
||||
(vector-push-extend val *queue*))
|
||||
|
||||
(defun pop ()
|
||||
(if (not (emptyp))
|
||||
(prog1
|
||||
(alexandria:first-elt *queue*)
|
||||
(setf *queue* (misc:safe-delete@ *queue* 0)))
|
||||
nil))
|
||||
|
||||
(defun find (element)
|
||||
(cl:find element *queue* :key *key-function* :test *equal-function*))
|
||||
|
||||
(defun emptyp ()
|
||||
(not (> (length *queue*) 0)))
|
||||
|
||||
(defmacro with-queue ((equal key) &body body)
|
||||
`(let ((*queue* (misc:make-array-frame 0))
|
||||
(*equal-function* ,equal)
|
||||
(*key-function* ,key))
|
||||
,@body))
|
||||
|
||||
(defclass simple-queue ()
|
||||
((container
|
||||
:initform (misc:make-array-frame 0)
|
||||
:accessor container)))
|
||||
|
||||
(defgeneric q-pop (object))
|
||||
|
||||
(defgeneric q-peek (object))
|
||||
|
||||
(defgeneric q-push (object value))
|
||||
|
||||
(defgeneric q-empty-p (object))
|
||||
|
||||
(defgeneric q-size (object))
|
||||
|
||||
(defgeneric q-sort (object predicate))
|
||||
|
||||
(defgeneric q-dbg-print (object))
|
||||
|
||||
(defmethod q-pop ((object simple-queue))
|
||||
(with-accessors ((container container)) object
|
||||
(let ((peek (q-peek object)))
|
||||
(if peek
|
||||
(progn
|
||||
(setf container (misc:safe-delete@ container 0))
|
||||
peek)
|
||||
nil))))
|
||||
|
||||
(defmethod q-push ((object simple-queue) value)
|
||||
(with-accessors ((container container)) object
|
||||
(vector-push-extend value container)))
|
||||
|
||||
(defmethod q-empty-p ((object simple-queue))
|
||||
(with-accessors ((container container)) object
|
||||
(misc:vector-empty-p container)))
|
||||
|
||||
(defmethod q-peek ((object simple-queue))
|
||||
(with-accessors ((container container)) object
|
||||
(if (not (q-empty-p object))
|
||||
(elt container 0)
|
||||
nil)))
|
||||
|
||||
(defmethod q-size ((object simple-queue))
|
||||
(length (container object)))
|
||||
|
||||
(defmethod q-sort ((object simple-queue) predicate)
|
||||
(with-accessors ((container container)) object
|
||||
(setf container (stable-sort container predicate))))
|
||||
|
||||
(defmethod q-dbg-print ((object simple-queue))
|
||||
(misc:dbg "--queue--")
|
||||
(loop
|
||||
for i from 0
|
||||
for a across (container object) do
|
||||
(misc:dbg "~a ~a" i a))
|
||||
(misc:dbg "----"))
|
|
@ -0,0 +1,518 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :rb-tree)
|
||||
|
||||
(alexandria:define-constant +rb-red+ :red :test #'eq)
|
||||
|
||||
(alexandria:define-constant +rb-black+ :black :test #'eq)
|
||||
|
||||
(alexandria:define-constant +rb-neg-black+ :neg-black :test #'eq)
|
||||
|
||||
(alexandria:define-constant +rb-black-black+ :black-black :test #'eq)
|
||||
|
||||
(alexandria:define-constant +rb-color+ :color :test #'eq)
|
||||
|
||||
(defun incf-black (color)
|
||||
(cond
|
||||
((eq color +rb-black+)
|
||||
+rb-black-black+)
|
||||
((eq color +rb-red+)
|
||||
+rb-black+)
|
||||
((eq color +rb-neg-black+)
|
||||
+rb-red+)))
|
||||
|
||||
(defun decf-black (color)
|
||||
(cond
|
||||
((eq color +rb-black-black+)
|
||||
+rb-black+)
|
||||
((eq color +rb-black+)
|
||||
+rb-red+)
|
||||
((eq color +rb-red+)
|
||||
+rb-neg-black+)))
|
||||
|
||||
(defclass rb-node (node)
|
||||
((color
|
||||
:initarg :color
|
||||
:initform +rb-black+
|
||||
:accessor color)))
|
||||
|
||||
(defgeneric balance (object))
|
||||
|
||||
(defgeneric bubble (object))
|
||||
|
||||
(defgeneric balancedp (object))
|
||||
|
||||
(defgeneric left-balance (object))
|
||||
|
||||
(defgeneric right-balance (object))
|
||||
|
||||
(defgeneric remove-max-node (object key key-datum compare equal))
|
||||
|
||||
(defgeneric %remove-node (object needle key key-datum compare equal))
|
||||
|
||||
(defgeneric remove-node (object needle &key key key-datum compare equal))
|
||||
|
||||
(defmethod node->string ((object rb-node))
|
||||
(if (null (data object))
|
||||
(format nil "leaf color ~a" (color object))
|
||||
(format nil "~a (~a)~% [~a] [~a]"
|
||||
(data object)
|
||||
(color object)
|
||||
(node->string (left object))
|
||||
(node->string (right object)))))
|
||||
|
||||
(defun make-rb-node (color data left right parent)
|
||||
(make-instance 'rb-node :color color :left left :right right :data data :parent parent))
|
||||
|
||||
(defun make-rb-leaf (color parent)
|
||||
(make-instance 'rb-node :color color :parent parent :left nil :right nil))
|
||||
|
||||
(defun make-root-rb-node (datum color)
|
||||
(let* ((tree (make-rb-node color datum nil nil nil))
|
||||
(l-leaf (make-rb-leaf +rb-black+ tree))
|
||||
(r-leaf (make-rb-leaf +rb-black+ tree)))
|
||||
(setf (left tree) l-leaf
|
||||
(right tree) r-leaf)
|
||||
tree))
|
||||
|
||||
(defmethod insert ((object rb-node) datum &key
|
||||
(key #'identity) (key-datum #'identity)
|
||||
(compare #'<) (equal #'=))
|
||||
(macrolet ((make-leaf-node (datum left right parent)
|
||||
`(make-rb-node +rb-red+ ,datum ,left ,right ,parent))
|
||||
(make-leaf (new-node)
|
||||
`(make-rb-leaf +rb-black+ ,new-node))
|
||||
(make-node (data left right parent)
|
||||
`(make-rb-node (color node) ,data ,left ,right ,parent)))
|
||||
(with-insert-local-function (make-node make-node make-leaf-node make-leaf
|
||||
left-balance right-balance)
|
||||
(let ((balanced (%insert object datum key key-datum compare equal)))
|
||||
(setf (color balanced) +rb-black+)
|
||||
balanced))))
|
||||
|
||||
(defmacro with-match-tree ((color left data right) tree &body body)
|
||||
`(and (or (not ,color)
|
||||
(eq ,color (color ,tree)))
|
||||
(let ((,data (data ,tree)))
|
||||
(declare (ignorable ,data))
|
||||
,(if (consp left)
|
||||
`(with-match-tree ,left (left ,tree)
|
||||
,(if (consp right)
|
||||
`(with-match-tree ,right (right ,tree)
|
||||
,@body)
|
||||
`(let ((,right (right ,tree)))
|
||||
(declare (ignorable ,right))
|
||||
,@body)))
|
||||
`(let ((,left (left ,tree)))
|
||||
(declare (ignorable ,left))
|
||||
,(if (consp right)
|
||||
`(with-match-tree ,right (right ,tree)
|
||||
,@body)
|
||||
`(let ((,right (right ,tree)))
|
||||
(declare (ignorable ,right))
|
||||
,@body)))))))
|
||||
|
||||
(defmethod balance ((object rb-node))
|
||||
(macrolet ((setf-parent (new-node)
|
||||
`(setf (parent (left ,new-node)) ,new-node
|
||||
(parent (right ,new-node)) ,new-node
|
||||
(parent (left (left ,new-node))) (left ,new-node)
|
||||
(parent (right (left ,new-node))) (left ,new-node)
|
||||
(parent (right (right ,new-node))) (right ,new-node)
|
||||
(parent (left (right ,new-node))) (right ,new-node))))
|
||||
(with-match-tree (+rb-black+ (+rb-red+ (+rb-red+ a x b) y c) z d) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black+ (+rb-red+ a x (+rb-red+ b y c)) z d) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black+ a x (+rb-red+ (+rb-red+ b y c) z d)) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black+ a x (+rb-red+ b y (+rb-red+ c z d))) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
;; for deletion
|
||||
(with-match-tree (+rb-black-black+ (+rb-red+ a x (+rb-red+ b y c)) z d) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black-black+ (+rb-red+ (+rb-red+ a x b) y c) z d) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black-black+ a x (+rb-red+ (+rb-red+ b y c) z d)) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
|
||||
(with-match-tree (+rb-black-black+ a x (+rb-red+ b y (+rb-red+ c z d))) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
(with-match-tree (+rb-black-black+ (+rb-neg-black+ (+rb-black+ a w b)
|
||||
x
|
||||
(+rb-black+ c y d))
|
||||
z
|
||||
e) object
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(balance (make-rb-node +rb-black+
|
||||
x
|
||||
(make-rb-node +rb-red+ w a b nil)
|
||||
c
|
||||
nil))
|
||||
(make-rb-node +rb-black+ z d e nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
(balance new-node))))
|
||||
|
||||
(with-match-tree (+rb-black-black+ a
|
||||
x
|
||||
(+rb-neg-black+ (+rb-black+ b y c)
|
||||
z
|
||||
(+rb-black+ j n i))) object
|
||||
|
||||
(return-from balance
|
||||
(let ((new-node (make-rb-node +rb-black+
|
||||
y
|
||||
(make-rb-node +rb-black+
|
||||
x
|
||||
a
|
||||
b
|
||||
nil)
|
||||
(balance (make-rb-node +rb-black+
|
||||
z
|
||||
c
|
||||
(make-rb-node +rb-red+ n j i nil)
|
||||
nil))
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
(balance new-node))))
|
||||
|
||||
object))
|
||||
|
||||
(defmethod bubble ((object rb-node))
|
||||
(if (or (eq (color (left object)) +rb-black-black+)
|
||||
(eq (color (right object)) +rb-black-black+))
|
||||
;; TODO reconstruct-parent!
|
||||
(balance (make-rb-node (incf-black (color object))
|
||||
(data object)
|
||||
(make-rb-node (decf-black (color (left object)))
|
||||
(data (left object))
|
||||
(left (left object))
|
||||
(right (left object))
|
||||
nil)
|
||||
(make-rb-node (decf-black (color (right object)))
|
||||
(data (right object))
|
||||
(left (right object))
|
||||
(right (right object))
|
||||
nil)
|
||||
nil))
|
||||
(balance object)))
|
||||
|
||||
(defmethod left-balance ((object rb-node))
|
||||
(macrolet ((setf-parent (new-node)
|
||||
`(setf (parent (left ,new-node)) ,new-node
|
||||
(parent (right ,new-node)) ,new-node
|
||||
(parent (left (left ,new-node))) (left ,new-node)
|
||||
(parent (right (left ,new-node))) (left ,new-node)
|
||||
(parent (right (right ,new-node))) (right ,new-node)
|
||||
(parent (left (right ,new-node))) (right ,new-node))))
|
||||
(with-match-tree (+rb-black+ (+rb-red+ (+rb-red+ a x b) y c) z d) object
|
||||
(return-from left-balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
(with-match-tree (+rb-black+ (+rb-red+ a x (+rb-red+ b y c)) z d) object
|
||||
(return-from left-balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
object))
|
||||
|
||||
(defmethod right-balance ((object rb-node))
|
||||
(macrolet ((setf-parent (new-node)
|
||||
`(setf (parent (left ,new-node)) ,new-node
|
||||
(parent (right ,new-node)) ,new-node
|
||||
(parent (left (left ,new-node))) (left ,new-node)
|
||||
(parent (right (left ,new-node))) (left ,new-node)
|
||||
(parent (right (right ,new-node))) (right ,new-node)
|
||||
(parent (left (right ,new-node))) (right ,new-node))))
|
||||
(with-match-tree (+rb-black+ a x (+rb-red+ (+rb-red+ b y c) z d)) object
|
||||
(return-from right-balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
(with-match-tree (+rb-black+ a x (+rb-red+ b y (+rb-red+ c z d))) object
|
||||
(return-from right-balance
|
||||
(let ((new-node (make-rb-node +rb-red+ y
|
||||
(make-rb-node +rb-black+ x a b nil)
|
||||
(make-rb-node +rb-black+ z c d nil)
|
||||
(parent object))))
|
||||
(setf-parent new-node)
|
||||
new-node)))
|
||||
object))
|
||||
|
||||
(defmethod map ((object rb-node) function)
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(make-rb-leaf color nil)
|
||||
(make-rb-node color (funcall function data)
|
||||
(map left function)
|
||||
(map right function) nil))))
|
||||
|
||||
(defmethod map-node ((object rb-node) function)
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(funcall function object (make-rb-leaf color object))
|
||||
(funcall function object (make-rb-node color data
|
||||
(map-node left function)
|
||||
(map-node right function) nil)))))
|
||||
|
||||
|
||||
(defmethod reconstruct-parent ((object rb-node) &optional (parent (parent object)))
|
||||
(with-accessors ((color color) (data data) (left left) (right right)) object
|
||||
(if (leafp object)
|
||||
(make-rb-leaf color parent)
|
||||
(make-rb-node color data
|
||||
(reconstruct-parent left object)
|
||||
(reconstruct-parent right object) parent))))
|
||||
|
||||
(defmethod remove-max-node ((object node) key key-datum compare equal)
|
||||
(if (leafp (right object))
|
||||
(%remove-node object (data object) key key-datum compare equal)
|
||||
(bubble (make-rb-node (color object)
|
||||
(data object)
|
||||
(left object)
|
||||
(remove-max-node (right object)
|
||||
key
|
||||
key-datum
|
||||
compare
|
||||
equal)
|
||||
nil))))
|
||||
|
||||
(defmethod %remove-node ((object node) needle key key-datum compare equal)
|
||||
(cond
|
||||
((leafp object)
|
||||
object)
|
||||
((funcall equal (%key key (data object)) (%key key-datum needle)) ;; equal, delete
|
||||
(cond
|
||||
((and (all-children-leaf-p object) ; no children, red color
|
||||
(eq (color object) +rb-red+))
|
||||
(make-rb-leaf +rb-black+ nil))
|
||||
((and (all-children-leaf-p object) ; no children, black color
|
||||
(eq (color object) +rb-black+))
|
||||
(make-rb-leaf +rb-black-black+ nil))
|
||||
((and (leafp (left object)) ;; one child on right
|
||||
(not (leafp (right object))))
|
||||
(make-rb-node +rb-black+
|
||||
(data (right object))
|
||||
(left (right object))
|
||||
(right (right object))
|
||||
nil))
|
||||
((and (leafp (right object)) ; one child on left
|
||||
(not (leafp (left object))))
|
||||
(make-rb-node +rb-black+
|
||||
(data (left object))
|
||||
(left (left object))
|
||||
(right (left object))
|
||||
nil))
|
||||
(t ; two children
|
||||
(let* ((max-node (find-max-node (left object)))
|
||||
(max-data (data max-node)))
|
||||
(bubble (make-rb-node (color object)
|
||||
max-data
|
||||
(remove-max-node (left object)
|
||||
key
|
||||
key
|
||||
compare
|
||||
equal)
|
||||
(right object)
|
||||
nil)))))) ;; end removing block
|
||||
((funcall compare (%key key-datum needle) (%key key (data object)))
|
||||
(bubble (make-rb-node (color object)
|
||||
(data object)
|
||||
(%remove-node (left object) needle key key-datum compare equal)
|
||||
(right object)
|
||||
nil)))
|
||||
(t ; go right, needle is greater then this node
|
||||
(bubble (make-rb-node (color object)
|
||||
(data object)
|
||||
(left object)
|
||||
(%remove-node (right object) needle key key-datum compare equal)
|
||||
nil)))))
|
||||
|
||||
(defmethod remove-node ((object rb-node) needle &key
|
||||
(key #'identity) (key-datum #'identity)
|
||||
(compare #'<) (equal #'=))
|
||||
(let ((new (%remove-node object needle key key-datum compare equal)))
|
||||
(setf (color new) +rb-black+)
|
||||
new))
|
||||
|
||||
(defmethod node->dot ((object rb-node))
|
||||
(labels ((color->hex (node)
|
||||
(cond
|
||||
((eq (color node) +rb-red+)
|
||||
"#ff0000")
|
||||
((eq (color node) +rb-black-black+)
|
||||
"#ff00ff")
|
||||
((eq (color node) +rb-neg-black+)
|
||||
"#a1a1a1")
|
||||
(t
|
||||
"#ffffff")))
|
||||
(nodes ()
|
||||
(append
|
||||
(list
|
||||
`(:node ((:id ,(format nil "~a" (data object)))
|
||||
(:label ,(format nil "~ap~a" (data object)
|
||||
(data (parent object))))
|
||||
(:style "filled")
|
||||
(:fillcolor ,(color->hex object)))))
|
||||
(if (not (leafp (left object)))
|
||||
(node->dot (left object))
|
||||
(list
|
||||
`(:node ((:id ,(format nil "nil-l~a" (data object)))
|
||||
(:label "nil")
|
||||
(:style "filled")
|
||||
(:fillcolor ,(color->hex (left object)))))))
|
||||
(if (not (leafp (right object)))
|
||||
(node->dot (right object))
|
||||
(list
|
||||
`(:node ((:id ,(format nil "nil-r~a" (data object)))
|
||||
(:label "nil")
|
||||
(:style "filled")
|
||||
(:fillcolor ,(color->hex (right object)))))))))
|
||||
(edges ()
|
||||
(append
|
||||
(if (data (left object))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "~a" (data (left object)))))))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "nil-l~a" (data object)))))))
|
||||
|
||||
(if (data (right object))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "~a" (data (right object)))))))
|
||||
(list `(:edge
|
||||
((:from ,(format nil "~a" (data object)))
|
||||
(:to ,(format nil "nil-r~a" (data object))))))))))
|
||||
(append (nodes) (edges))))
|
||||
|
||||
(defmethod to-sexp ((object rb-node))
|
||||
(let ((*print-circle* t))
|
||||
(list +data+ (to-sexp (data object))
|
||||
+rb-color+ (color object)
|
||||
+left+ (to-sexp (left object))
|
||||
+right+ (to-sexp (right object))
|
||||
+parent+ (to-sexp (data (parent object))))))
|
||||
|
||||
(defmethod from-sexp ((object rb-node) sexp)
|
||||
(declare (ignorable object))
|
||||
(labels ((%from-sexp (sexp)
|
||||
(if (null sexp)
|
||||
(make-rb-leaf +rb-black+ nil)
|
||||
(make-rb-node (getf sexp +rb-color+)
|
||||
(getf sexp +data+)
|
||||
(from-sexp object (getf sexp +left+))
|
||||
(from-sexp object (getf sexp +right+)) nil))))
|
||||
(let ((new-tree (%from-sexp sexp)))
|
||||
(reconstruct-parent new-tree))))
|
||||
|
||||
(defmethod balancedp ((object rb-node))
|
||||
(let ((black-paths-count '()))
|
||||
(labels ((all-red-child-black-p (node)
|
||||
(map-node node #'(lambda (n b)
|
||||
(declare (ignore b))
|
||||
(if (or (eq (color n) +rb-black+)
|
||||
(and (eq (color n) +rb-red+)
|
||||
(eq (color (left n)) +rb-black+)
|
||||
(eq (color (right n)) +rb-black+)))
|
||||
t
|
||||
(return-from all-red-child-black-p nil)))))
|
||||
(all-path-black-same-length (node &optional (ct 0))
|
||||
|
||||
(when (eq (color node) +rb-black+)
|
||||
(incf ct))
|
||||
(if (leafp node)
|
||||
(progn
|
||||
(push ct black-paths-count)
|
||||
(decf ct))
|
||||
(progn
|
||||
(all-path-black-same-length (left node) ct)
|
||||
(all-path-black-same-length (right node) ct)))))
|
||||
(all-path-black-same-length object)
|
||||
(and (every #'(lambda (a) (= a (elt black-paths-count 0))) black-paths-count)
|
||||
(all-red-child-black-p object)))))
|
|
@ -0,0 +1,69 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
;; published by the Free Software Foundation, either version 3 of the
|
||||
;; License, or (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details. You should have received
|
||||
;; a copy of the GNU General Public License along with this program.
|
||||
;; If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :resources-utils)
|
||||
|
||||
(alexandria:define-constant +virtual-fs-dir-separator+ "/" :test #'string=)
|
||||
|
||||
(alexandria:define-constant +virtual-fs-dir-separator-regexp+ "\\/" :test #'string=)
|
||||
|
||||
(defun construct-path (p)
|
||||
(let ((splitted (split +virtual-fs-dir-separator-regexp+ p)))
|
||||
(strcat *directory-sep* (join-with-strings splitted *directory-sep*)
|
||||
(if (cl-ppcre:scan (strcat +virtual-fs-dir-separator-regexp+ "$") p)
|
||||
*directory-sep*))))
|
||||
|
||||
(defun home-datadir ()
|
||||
(concatenate 'string
|
||||
(pathname->namestring (uiop:xdg-data-home +program-name+))
|
||||
"/"))
|
||||
|
||||
(defun home-confdir ()
|
||||
(pathname->namestring (uiop:xdg-config-home +program-name+)))
|
||||
|
||||
(defun init ()
|
||||
#+debug-mode
|
||||
(progn
|
||||
(when (not (directory-exists-p (home-datadir)))
|
||||
(misc:dbg "creating ~a" (home-datadir)))
|
||||
(when (not (directory-exists-p (home-confdir)))
|
||||
(misc:dbg "creating ~a" (home-confdir))))
|
||||
(fs:make-directory (home-datadir))
|
||||
(fs:make-directory (home-confdir)))
|
||||
|
||||
(defun get-resource-file (system-dir home-dir path)
|
||||
(let ((system-file (fs:cat-parent-dir system-dir path))
|
||||
(home-file (fs:cat-parent-dir home-dir path)))
|
||||
(cond
|
||||
((fs:file-exists-p home-file)
|
||||
home-file)
|
||||
((fs:file-exists-p system-file)
|
||||
system-file)
|
||||
(t
|
||||
(let ((msg (_ "Unrecoverable error: cannot find ~s in either ~s or ~s.")))
|
||||
(restart-case
|
||||
(error (format nil msg path system-file home-file))
|
||||
(return-home-filename (e)
|
||||
(declare (ignore e))
|
||||
home-file)
|
||||
(return-system-filename (e)
|
||||
(declare (ignore e))
|
||||
system-file)))))))
|
||||
|
||||
(defun get-config-file (path)
|
||||
(get-resource-file +sys-conf-dir+ (home-confdir) path))
|
||||
|
||||
(defun get-data-file (path)
|
||||
(get-resource-file +sys-data-dir+ (home-datadir) path))
|
|
@ -0,0 +1,192 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :sending-message)
|
||||
|
||||
(define-constant +header-send-window-height+ 5 :test #'=)
|
||||
|
||||
(defclass message-ready-to-send ()
|
||||
((subject
|
||||
:initform nil
|
||||
:initarg :subject
|
||||
:accessor subject)
|
||||
(attachments
|
||||
:initform ()
|
||||
:initarg :attachments
|
||||
:accessor attachments)
|
||||
(reply-to
|
||||
:initform ()
|
||||
:initarg :reply-to
|
||||
:accessor reply-to
|
||||
:documentation "The id of table 'status' you are replying to.")
|
||||
(visibility
|
||||
:initform +status-public-visibility+
|
||||
:initarg :visibility
|
||||
:accessor visibility
|
||||
:documentation "One of swconf:*allowed-status-visibility*.")
|
||||
(body
|
||||
:initform nil
|
||||
:initarg :body
|
||||
:accessor body)))
|
||||
|
||||
(defmethod print-object ((object message-ready-to-send) stream)
|
||||
(print-unreadable-object (object stream :type t)
|
||||
(with-accessors ((subject subject)
|
||||
(attachments attachments)
|
||||
(reply-to reply-to)
|
||||
(body body)) object
|
||||
(format stream
|
||||
"~@[subj: ~a ~] ~@[reply-to: ~a ~] ~@[attach: ~a ~] ~a"
|
||||
subject reply-to attachments body))))
|
||||
|
||||
(defclass confirm-sending-window (focus-marked-window simple-line-navigation-window)
|
||||
((screen
|
||||
:initarg :screen
|
||||
:initform nil
|
||||
:accessor screen)
|
||||
(message-data
|
||||
:initarg :message-data
|
||||
:initform (make-instance 'message-ready-to-send)
|
||||
:accessor message-data
|
||||
:type message-ready-to-send)
|
||||
(style
|
||||
:initarg :style
|
||||
:initform nil
|
||||
:accessor style)))
|
||||
|
||||
(defmethod refresh-config :after ((object confirm-sending-window))
|
||||
(with-accessors ((screen screen)
|
||||
(croatoan-window croatoan-window)
|
||||
(bgcolor bgcolor)
|
||||
(fgcolor fgcolor)
|
||||
(top-row-padding top-row-padding)
|
||||
(style style)) object
|
||||
(let* ((theme-style (swconf:form-style swconf:+key-input-dialog+))
|
||||
(fg (swconf:foreground theme-style))
|
||||
(bg (swconf:background theme-style))
|
||||
(width (truncate (/ (win-width screen)
|
||||
2)))
|
||||
(height (truncate (/ (win-height screen)
|
||||
2)))
|
||||
(y (truncate (- (/ (win-height screen) 2)
|
||||
(/ height 2))))
|
||||
(x (truncate (- (/ (win-width screen) 2)
|
||||
(/ width 2))))
|
||||
(attach-y-start +header-send-window-height+))
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background bg))
|
||||
(setf (bgcolor croatoan-window) bg)
|
||||
(setf (fgcolor croatoan-window) fg)
|
||||
(setf style theme-style)
|
||||
(win-resize object width height)
|
||||
(win-move object x y)
|
||||
(setf (top-row-padding object) attach-y-start)
|
||||
object)))
|
||||
|
||||
(defmethod draw :after ((object confirm-sending-window))
|
||||
(with-accessors ((message-data message-data)
|
||||
(style style)) object
|
||||
(with-accessors ((reply-to reply-to)
|
||||
(attachments attachments)
|
||||
(body body)
|
||||
(subject subject)
|
||||
(visibility visibility)) message-data
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(let* ((bgcolor (bgcolor croatoan-window))
|
||||
(fgcolor (fgcolor croatoan-window))
|
||||
(input-bg (swconf:input-background style))
|
||||
(input-fg (swconf:input-foreground style))
|
||||
(user (if reply-to
|
||||
(db:status-id->username reply-to)
|
||||
(_ "none")))
|
||||
(label-reply-raw (_ "Reply to: "))
|
||||
(label-subject-raw (_ "Subject:"))
|
||||
(label-visibility-raw (_ "Visibility:"))
|
||||
(label-reply-length-raw (length label-reply-raw))
|
||||
(label-subject-raw-length (length label-subject-raw))
|
||||
(label-visibility-raw-length (length label-visibility-raw))
|
||||
(max-field-length (max label-reply-length-raw
|
||||
label-subject-raw-length
|
||||
label-visibility-raw-length))
|
||||
(label-subject (text-utils:right-padding label-subject-raw
|
||||
max-field-length))
|
||||
(label-reply (text-utils:right-padding label-reply-raw
|
||||
max-field-length))
|
||||
(label-visibility (text-utils:right-padding label-visibility-raw
|
||||
max-field-length))
|
||||
(value-max-length (- (win-width-no-border object)
|
||||
max-field-length))
|
||||
(label-attachments (_ "Attachments")))
|
||||
(flet ((print-field (text x y bg fg &key (inverse nil))
|
||||
(print-text object text x y
|
||||
:bgcolor (if inverse
|
||||
fg
|
||||
bg)
|
||||
:fgcolor (if inverse
|
||||
bg
|
||||
fg))))
|
||||
(print-field label-reply 1 1 bgcolor fgcolor)
|
||||
(print-field (right-padding user
|
||||
value-max-length)
|
||||
max-field-length
|
||||
1
|
||||
input-bg input-fg)
|
||||
(print-field label-subject 1 2 bgcolor fgcolor)
|
||||
(print-field (right-padding subject
|
||||
value-max-length)
|
||||
max-field-length
|
||||
2
|
||||
input-bg input-fg)
|
||||
(print-field label-visibility 1 3 bgcolor fgcolor)
|
||||
(print-field (right-padding visibility
|
||||
value-max-length)
|
||||
max-field-length
|
||||
3
|
||||
input-bg input-fg)
|
||||
(print-field (right-padding (text-ellipsize label-attachments
|
||||
(win-width-no-border object))
|
||||
(win-width-no-border object))
|
||||
1
|
||||
4
|
||||
bgcolor fgcolor
|
||||
:inverse t)))))))
|
||||
|
||||
(defun init (message-data screen)
|
||||
(flet ((make-rows (data bg fg)
|
||||
(mapcar #'(lambda (a)
|
||||
(make-instance 'line
|
||||
:normal-text a
|
||||
:selected-text a
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg))
|
||||
data)))
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *send-message-window*
|
||||
(make-instance 'confirm-sending-window
|
||||
:uses-border-p t
|
||||
:screen screen
|
||||
:keybindings keybindings:*send-message-keymap*
|
||||
:croatoan-window low-level-window
|
||||
:message-data message-data))
|
||||
(refresh-config *send-message-window*)
|
||||
(setf (rows *send-message-window*)
|
||||
(make-rows (attachments message-data)
|
||||
(bgcolor low-level-window)
|
||||
(fgcolor low-level-window)))
|
||||
(setf (row-selected-index *send-message-window*) 0)
|
||||
*send-message-window*)))
|
|
@ -0,0 +1,895 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :software-configuration)
|
||||
|
||||
;; CONFIG := (ENTRIES)*
|
||||
;; ENTRIES := COMMENT*
|
||||
;; (USE-FILE
|
||||
;; | COLOR-RE-ASSIGN
|
||||
;; | SERVER-ASSIGN
|
||||
;; | USERNAME-ASSIGN
|
||||
;; | GENERIC-ASSIGN)
|
||||
;; COMMENT*
|
||||
;; SERVER-ASSIGN := SERVER-KEY BLANKS ASSIGN BLANKS GENERIC-VALUE BLANKS
|
||||
;; USERNAME-ASSIGN := USERNAME-KEY BLANKS ASSIGN BLANKS GENERIC-VALUE BLANKS
|
||||
;; GENERIC-ASSIGN := (and key blanks assign blanks
|
||||
;; (or quoted-string
|
||||
;; hexcolor
|
||||
;; colorname
|
||||
;; generic-value) ; the order in this list *is* important
|
||||
;; blanks)
|
||||
;; COLOR-RE-ASSIGN := COLOR-RE-KEY ASSIGN REGEXP FG-COLOR (? ATTRIBUTE-VALUE)
|
||||
;; USE-FILE := (AND USE BLANKS FILEPATH BLANKS)
|
||||
;; KEY := FIELD (FIELD-SEPARATOR KEY)*
|
||||
;; BLANKS := (BLANK)*
|
||||
;; FILEPATH := QUOTED-STRING
|
||||
;; USE := "use"
|
||||
;; SERVER-KEY := "server"
|
||||
;; USERNAME-KEY := "username"
|
||||
;; COLOR-RE-KEY := "color-regexp"
|
||||
;; REGEXP := QUOTED-STRING
|
||||
;; QUOTED-STRING := #\" (not #\") #\"
|
||||
;; FIELD := ( (or ESCAPED-CHARACTER
|
||||
;; (not #\# ASSIGN BLANK FIELD-SEPARATOR) )*
|
||||
;; COMMENT := BLANKS #\# (not #\Newline)* BLANKS
|
||||
;; FIELD-SEPARATOR := #\.
|
||||
;; GENERIC-VALUE := KEY
|
||||
;; ASSIGN := #\=
|
||||
;; BLANK := (or #\space #\Newline #\Tab)
|
||||
;; BG-COLOR := COLOR
|
||||
;; FG-COLOR := COLOR
|
||||
;; COLOR := HEX-COLOR | COLOR-NAME
|
||||
;; HEX-COLOR := HEXCOLOR-PREFIX
|
||||
;; HEXDIGIT HEXDIGIT -> red
|
||||
;; HEXDIGIT HEXDIGIT -> green
|
||||
;; HEXDIGIT HEXDIGIT -> blue
|
||||
;; ESCAPED-CHARACTER := #\\ any-character
|
||||
;; HEXCOLOR-PREFIX := #\#
|
||||
;; HEX-DIGIT := (and (character-ranges #\0 #\9)
|
||||
;; (character-ranges #\a #\f)
|
||||
;; (character-ranges #\A #\f)
|
||||
;; ATTRIBUTE-VALUE := "bold"
|
||||
;; | "italic"
|
||||
;; | "underline"
|
||||
;; | "blink"
|
||||
;; COLOR-NAME := "black"
|
||||
;; | "red"
|
||||
;; | "green"
|
||||
;; | "yellow"
|
||||
;; | "blue"
|
||||
;; | "magenta"
|
||||
;; | "cyan"
|
||||
;; | "white"
|
||||
|
||||
(define-constant +conf-filename+ "main.conf" :test #'string=)
|
||||
|
||||
(define-constant +field-separator-value+ "." :test #'string=)
|
||||
|
||||
(define-constant +field-separator+ :field-separator :test #'eq)
|
||||
|
||||
(defrule blank (or #\space #\Newline #\Tab)
|
||||
(:constant nil))
|
||||
|
||||
(defrule blanks (* blank)
|
||||
(:constant nil))
|
||||
|
||||
(defrule assign #\=
|
||||
(:constant nil))
|
||||
|
||||
(defrule comment (and blanks #\# (* (not #\Newline)) blanks)
|
||||
(:constant nil))
|
||||
|
||||
(defrule hexcolor-prefix #\#)
|
||||
|
||||
(defrule hex-digit
|
||||
(or (character-ranges (#\0 #\9))
|
||||
(character-ranges (#\a #\f))
|
||||
(character-ranges (#\A #\F))))
|
||||
|
||||
(defrule hexcolor
|
||||
(and hexcolor-prefix
|
||||
hex-digit hex-digit ; r
|
||||
hex-digit hex-digit ; g
|
||||
hex-digit hex-digit) ; b
|
||||
(:text t)
|
||||
(:function (lambda (a) (parse-integer a :start 1 :radix 16))))
|
||||
|
||||
(defun keywordize (a)
|
||||
(make-keyword (string-upcase a)))
|
||||
|
||||
(defrule colorname
|
||||
(or "black"
|
||||
"red"
|
||||
"green"
|
||||
"yellow"
|
||||
"blue"
|
||||
"magenta"
|
||||
"cyan"
|
||||
"white")
|
||||
(:function keywordize))
|
||||
|
||||
(defrule escaped-character (and #\\ character)
|
||||
(:function (lambda (a) (list (second a)))))
|
||||
|
||||
(defrule field-separator #\.)
|
||||
|
||||
(defrule field
|
||||
(* (or escaped-character
|
||||
(not (or #\# assign field-separator blank))))
|
||||
|
||||
(:text t))
|
||||
|
||||
;; this rule is not actually part of the grammar but jus a convenience
|
||||
;; function to remove duplicated code (see rules: key and value)
|
||||
(defrule fields
|
||||
(and field
|
||||
(? (and field-separator fields)))
|
||||
(:function flatten))
|
||||
|
||||
(defrule key fields
|
||||
(:function (lambda (a)
|
||||
(mapcar (lambda (element)
|
||||
(if (string= +field-separator-value+ element)
|
||||
nil
|
||||
(format-keyword element)))
|
||||
a)))
|
||||
(:function remove-if-null))
|
||||
|
||||
(defrule generic-value fields
|
||||
(:text t))
|
||||
|
||||
(defrule generic-assign
|
||||
(and key blanks assign blanks
|
||||
(or quoted-string
|
||||
hexcolor
|
||||
colorname
|
||||
generic-value) ; the order in this list *is* important
|
||||
blanks)
|
||||
(:function remove-if-null))
|
||||
|
||||
(defrule quoted-string (and #\" (+ (not #\")) #\")
|
||||
(:function (lambda (a) (second a)))
|
||||
(:text t))
|
||||
|
||||
(defrule regexp quoted-string)
|
||||
|
||||
(defrule color-re-key "color-regexp"
|
||||
(:constant :color-re))
|
||||
|
||||
(defclass color-re-assign ()
|
||||
((re
|
||||
:initform nil
|
||||
:initarg :re
|
||||
:accessor re)
|
||||
(color-name
|
||||
:initform nil
|
||||
:initarg :color-name
|
||||
:accessor color-name)
|
||||
(color-value
|
||||
:initform nil
|
||||
:initarg :color-value
|
||||
:accessor color-value)
|
||||
(attributes
|
||||
:initform nil
|
||||
:initarg :attributes
|
||||
:accessor attributes))
|
||||
(:documentation "A color assign based on a regular expression. Slots
|
||||
color-name and color-value are mutually exclusive"))
|
||||
|
||||
(defmethod print-object ((object color-re-assign) stream)
|
||||
(print-unreadable-object (object stream :type t :identity nil)
|
||||
(with-accessors ((re re)
|
||||
(color-name color-name)
|
||||
(color-value color-value)
|
||||
(attributes attributes)) object
|
||||
(format stream "re: ~s colorname: ~s colorvalue: ~s attributes ~a"
|
||||
re color-name color-value attributes))))
|
||||
|
||||
(defun make-color-re-assign (re color-name color-value attributes)
|
||||
(assert (and (or color-name
|
||||
color-value)
|
||||
(or (null color-name)
|
||||
(null color-value))))
|
||||
(make-instance 'color-re-assign
|
||||
:re re
|
||||
:color-name color-name
|
||||
:color-value color-value
|
||||
:attributes attributes))
|
||||
|
||||
(defun build-color-re-assign (parsed)
|
||||
(let* ((clean (remove-if-null parsed))
|
||||
(re (second parsed))
|
||||
(color (third parsed))
|
||||
(color-name-p (keywordp color))
|
||||
(attributes (first (fourth parsed))))
|
||||
(list (first clean)
|
||||
(make-color-re-assign re
|
||||
(and color-name-p color)
|
||||
(and (not color-name-p) color)
|
||||
attributes))))
|
||||
(defrule attribute-value (or "bold"
|
||||
"italic"
|
||||
"underline"
|
||||
"blink")
|
||||
(:text t)
|
||||
(:function tui-utils:text->tui-attribute))
|
||||
|
||||
(defrule color-re-assign
|
||||
(and color-re-key blanks
|
||||
assign blanks regexp blanks
|
||||
(or hexcolor colorname) blanks
|
||||
(? (and attribute-value blanks)))
|
||||
(:function remove-if-null)
|
||||
(:function build-color-re-assign))
|
||||
|
||||
(defrule server-key "server"
|
||||
(:constant :server))
|
||||
|
||||
(defrule username-key "username"
|
||||
(:constant :username))
|
||||
|
||||
(defrule server-assign
|
||||
(and server-key blanks assign blanks generic-value blanks)
|
||||
(:function remove-if-null))
|
||||
|
||||
(defrule username-assign
|
||||
(and username-key blanks assign blanks generic-value blanks)
|
||||
(:function remove-if-null))
|
||||
|
||||
(defrule filepath quoted-string)
|
||||
|
||||
(defparameter *already-included-files* ())
|
||||
|
||||
(defrule use-file (and "use" blanks filepath blanks)
|
||||
(:function (lambda (a)
|
||||
(let ((file (third a)))
|
||||
(if (find file *already-included-files* :test #'string=)
|
||||
(error "Cyclic include of file ~s detected" file)
|
||||
(progn
|
||||
(push file *already-included-files*)
|
||||
(load-config-file (third a))))
|
||||
nil))))
|
||||
|
||||
(defrule entries
|
||||
(and (* comment)
|
||||
(or use-file
|
||||
color-re-assign
|
||||
server-assign
|
||||
username-assign
|
||||
generic-assign)
|
||||
(* comment))
|
||||
(:function second))
|
||||
|
||||
(defrule config (* entries)
|
||||
(:function remove-if-null))
|
||||
|
||||
(defgeneric parse-config (object))
|
||||
|
||||
(defmethod parse-config ((object string))
|
||||
(parse 'config object))
|
||||
|
||||
(defmethod parse-config ((object pathname))
|
||||
(parse-config (fs:slurp-file object)))
|
||||
|
||||
(defparameter *software-configuration* ())
|
||||
|
||||
(defmacro gen-key-constant (name)
|
||||
`(define-constant ,(format-fn-symbol t "+key-~a+" name)
|
||||
,(format-keyword name)
|
||||
:test #'eq))
|
||||
|
||||
(defmacro gen-key-constants (&rest names)
|
||||
`(progn
|
||||
,@(loop for name in names collect
|
||||
`(gen-key-constant ,name))))
|
||||
|
||||
(gen-key-constants background
|
||||
foreground
|
||||
title
|
||||
start
|
||||
end
|
||||
left
|
||||
right
|
||||
stopper
|
||||
width
|
||||
height
|
||||
error
|
||||
info
|
||||
window
|
||||
header
|
||||
focus
|
||||
prefix
|
||||
postfix
|
||||
value
|
||||
attribute
|
||||
new-message
|
||||
mark
|
||||
crypted
|
||||
histogram
|
||||
error-dialog
|
||||
info-dialog
|
||||
input-dialog
|
||||
help-dialog
|
||||
notify-window
|
||||
life
|
||||
quick-help
|
||||
more-choices
|
||||
modeline
|
||||
date-format
|
||||
locked
|
||||
unlocked
|
||||
account
|
||||
main-window
|
||||
thread-window
|
||||
message-window
|
||||
attachment-header
|
||||
max-numbers-allowed-attachments
|
||||
max-message-length
|
||||
max-report-comment-length
|
||||
reply-quoted-character
|
||||
line-position-mark
|
||||
favourite
|
||||
sensitive
|
||||
boosted
|
||||
tags-window
|
||||
conversations-window
|
||||
keybindings-window
|
||||
suggestions-window
|
||||
open-attach-window
|
||||
command-window
|
||||
command-separator
|
||||
tree
|
||||
branch
|
||||
arrow
|
||||
data
|
||||
data-leaf
|
||||
leaf
|
||||
branch
|
||||
spacer
|
||||
vertical-line
|
||||
editor
|
||||
username
|
||||
server
|
||||
message
|
||||
selected
|
||||
deleted
|
||||
input
|
||||
read
|
||||
unread
|
||||
color-re
|
||||
purge-history-days-offset
|
||||
purge-cache-days-offset)
|
||||
|
||||
(defun load-config-file (&optional (virtual-filepath +conf-filename+))
|
||||
(let* ((file (res:get-config-file virtual-filepath))
|
||||
(tree (parse-config (fs:namestring->pathname file))))
|
||||
(loop for entry in tree do
|
||||
(let ((key (first entry))
|
||||
(value (second entry)))
|
||||
(cond
|
||||
((eq +key-color-re+ key)
|
||||
(setf (access:accesses *software-configuration* key)
|
||||
(append (access:accesses *software-configuration* key)
|
||||
(list value))))
|
||||
((keywordp key)
|
||||
(setf (access:accesses *software-configuration* key) value))
|
||||
(t
|
||||
(multiple-value-bind (rest all)
|
||||
(apply #'access:set-accesses value *software-configuration* key)
|
||||
(declare (ignore rest))
|
||||
(setf *software-configuration* all))))))
|
||||
*software-configuration*))
|
||||
|
||||
;;;; end of parser
|
||||
|
||||
(defparameter *allowed-status-visibility* '("public" "unlisted" "private" "direct")
|
||||
"- public Visible to everyone, shown in public timelines;
|
||||
- unlisted Visible to public, but not included in public timelines;
|
||||
- private Visible to followers only, and to any mentioned users;
|
||||
- direct Visible only to mentioned users.")
|
||||
|
||||
(defparameter *allowed-attachment-type* '("unknown" "image" "gifv" "video" "audio"))
|
||||
|
||||
;;;; interface
|
||||
|
||||
(defun crypted-mark-value ()
|
||||
(or (access:accesses *software-configuration*
|
||||
+key-crypted+
|
||||
+key-mark+
|
||||
+key-value+)
|
||||
(_ "This message was crypted.")))
|
||||
|
||||
(defun quick-help-header-colors ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-quick-help+
|
||||
+key-header+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-quick-help+
|
||||
+key-header+
|
||||
+key-foreground+)
|
||||
(tui-utils:text->tui-attribute (access:accesses *software-configuration*
|
||||
+key-quick-help+
|
||||
+key-header+
|
||||
+key-attribute+))))
|
||||
|
||||
(defun window-titles-end (side)
|
||||
(assert (member side (list +key-left+ +key-right+)))
|
||||
(access:accesses *software-configuration*
|
||||
+key-window+
|
||||
+key-title+
|
||||
side
|
||||
+key-stopper+
|
||||
+key-value+))
|
||||
|
||||
(defun window-titles-ends ()
|
||||
(multiple-value-bind (x y focus-value)
|
||||
(config-win-focus-mark)
|
||||
(declare (ignore x y))
|
||||
(values (window-titles-end +key-left+)
|
||||
(window-titles-end +key-right+)
|
||||
(+ 2 (length focus-value)))))
|
||||
|
||||
(defun tags-histogram-foreground ()
|
||||
(access:accesses *software-configuration*
|
||||
+key-tags-window+
|
||||
+key-histogram+
|
||||
+key-foreground+))
|
||||
|
||||
(defun tags-new-message-mark ()
|
||||
(access:accesses *software-configuration*
|
||||
+key-tags-window+
|
||||
+key-new-message+
|
||||
+key-mark+
|
||||
+key-value+))
|
||||
|
||||
(defun conversation-window-message-count-colors (key-read/unread)
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-conversations-window+
|
||||
key-read/unread
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-conversations-window+
|
||||
key-read/unread
|
||||
+key-background+)))
|
||||
|
||||
(defun conversation-window-read-colors ()
|
||||
(multiple-value-bind (fg bg)
|
||||
(conversation-window-message-count-colors +key-read+)
|
||||
(values fg bg)))
|
||||
|
||||
(defun conversation-window-unread-colors ()
|
||||
(multiple-value-bind (fg bg)
|
||||
(conversation-window-message-count-colors +key-unread+)
|
||||
(values fg bg)))
|
||||
|
||||
(defun max-message-length ()
|
||||
(num:parse-number-default (access:accesses *software-configuration*
|
||||
+key-max-message-length+)
|
||||
500))
|
||||
|
||||
(defun max-report-comment-length ()
|
||||
(num:parse-number-default (access:accesses *software-configuration*
|
||||
+key-max-report-comment-length+)
|
||||
100))
|
||||
|
||||
(defun quote-char ()
|
||||
(or (access:accesses *software-configuration*
|
||||
+key-reply-quoted-character+)
|
||||
"> "))
|
||||
|
||||
(defun max-attachments-allowed ()
|
||||
(num:parse-number-default (access:accesses *software-configuration*
|
||||
+key-max-numbers-allowed-attachments+)
|
||||
4))
|
||||
|
||||
(defun external-editor ()
|
||||
(access:accesses *software-configuration*
|
||||
+key-editor+))
|
||||
|
||||
(defun color-regexps ()
|
||||
(access:accesses *software-configuration*
|
||||
+key-color-re+))
|
||||
|
||||
(defmacro gen-win-key-access (fn-suffix key)
|
||||
`(defun ,(misc:format-fn-symbol t "win-~a" fn-suffix) (win-key)
|
||||
(access:accesses *software-configuration*
|
||||
win-key
|
||||
,key)))
|
||||
|
||||
(gen-win-key-access bg +key-background+)
|
||||
|
||||
(gen-win-key-access fg +key-foreground+)
|
||||
|
||||
(gen-win-key-access height +key-height+)
|
||||
|
||||
(gen-win-key-access width +key-width+)
|
||||
|
||||
(defmacro gen-simple-access ((fn-name &key (transform-value-fn 'identity)) &rest keys)
|
||||
`(defun ,(misc:format-fn-symbol t "config-~a" fn-name) ()
|
||||
(,transform-value-fn (access:accesses *software-configuration*
|
||||
,@keys))))
|
||||
|
||||
(gen-simple-access (purge-history-days-offset
|
||||
:transform-value-fn
|
||||
(lambda (a)
|
||||
(num:safe-parse-number a
|
||||
:fix-fn (lambda (e)
|
||||
(declare (ignore e))
|
||||
100))))
|
||||
+key-purge-history-days-offset+)
|
||||
|
||||
(gen-simple-access (purge-cage-days-offset
|
||||
:transform-value-fn
|
||||
(lambda (a)
|
||||
(num:safe-parse-number a
|
||||
:fix-fn (lambda (e)
|
||||
(declare (ignore e))
|
||||
100))))
|
||||
+key-purge-history-days-offset+)
|
||||
|
||||
(gen-simple-access (notification-life
|
||||
:transform-value-fn
|
||||
(lambda (a)
|
||||
(num:safe-parse-number a
|
||||
:fix-fn (lambda (e)
|
||||
(declare (ignore e))
|
||||
100))))
|
||||
+key-notify-window+
|
||||
+key-life+)
|
||||
|
||||
(gen-simple-access (server-name)
|
||||
+key-server+)
|
||||
|
||||
(gen-simple-access (username)
|
||||
+key-username+)
|
||||
|
||||
(defun config-win-focus-mark ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-window+
|
||||
+key-focus+
|
||||
+key-mark+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-window+
|
||||
+key-focus+
|
||||
+key-mark+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-window+
|
||||
+key-focus+
|
||||
+key-mark+
|
||||
+key-value+)))
|
||||
|
||||
(defun command-separator-config-values ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-command-separator+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-command-separator+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-command-separator+
|
||||
+key-value+)))
|
||||
|
||||
(defun command-error-message-colors ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-error+
|
||||
+key-message+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-error+
|
||||
+key-message+
|
||||
+key-foreground+)
|
||||
(tui-utils:text->tui-attribute (access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-error+
|
||||
+key-message+
|
||||
+key-attribute+))))
|
||||
|
||||
(defun command-info-message-colors ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-info+
|
||||
+key-message+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-info+
|
||||
+key-message+
|
||||
+key-foreground+)
|
||||
(tui-utils:text->tui-attribute (access:accesses *software-configuration*
|
||||
+key-command-window+
|
||||
+key-info+
|
||||
+key-message+
|
||||
+key-attribute+))))
|
||||
|
||||
|
||||
(defun tree-config-colors (tree-win-holder)
|
||||
(values (access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-branch+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-arrow+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-data+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-data-leaf+
|
||||
+key-foreground+)))
|
||||
|
||||
(defun tree-config-rendering-values (tree-win-holder)
|
||||
(values (access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-arrow+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-leaf+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-branch+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-spacer+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
tree-win-holder
|
||||
+key-tree+
|
||||
+key-vertical-line+
|
||||
+key-value+)))
|
||||
|
||||
(defun make-tree-colormap (window-key)
|
||||
(let ((tree-color-map ()))
|
||||
(flet ((add-color-pair (key color)
|
||||
(setf tree-color-map (acons key color tree-color-map))))
|
||||
(multiple-value-bind (branch-color arrow-color data-color leaf-color)
|
||||
(swconf:tree-config-colors window-key)
|
||||
(add-color-pair :branch branch-color)
|
||||
(add-color-pair :arrow arrow-color)
|
||||
(add-color-pair :data data-color)
|
||||
(add-color-pair :data-leaf leaf-color))
|
||||
tree-color-map)))
|
||||
|
||||
(defun thread-message-symbol-lookup (field key)
|
||||
(access:accesses *software-configuration*
|
||||
+key-thread-window+
|
||||
+key-message+
|
||||
field
|
||||
key))
|
||||
|
||||
(defun thread-message-symbol-value (field)
|
||||
(thread-message-symbol-lookup field +key-value+))
|
||||
|
||||
(defun thread-message-symbol-fg (field)
|
||||
(thread-message-symbol-lookup field +key-foreground+))
|
||||
|
||||
(defun thread-message-symbol (field)
|
||||
(values (thread-message-symbol-value field)
|
||||
(thread-message-symbol-fg field)))
|
||||
|
||||
(defun thread-message-colors (key)
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-thread-window+
|
||||
+key-message+
|
||||
key
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-thread-window+
|
||||
+key-message+
|
||||
key
|
||||
+key-foreground+)
|
||||
(tui-utils:text->tui-attribute (access:accesses *software-configuration*
|
||||
+key-thread-window+
|
||||
+key-message+
|
||||
key
|
||||
+key-attribute+))))
|
||||
(defun thread-message-read-colors ()
|
||||
(multiple-value-bind (bg fg attribute)
|
||||
(thread-message-colors +key-read+)
|
||||
(values bg fg attribute)))
|
||||
|
||||
(defun thread-message-unread-colors ()
|
||||
(multiple-value-bind (bg fg attribute)
|
||||
(thread-message-colors +key-unread+)
|
||||
(values bg fg attribute)))
|
||||
|
||||
(defun thread-message-selected-colors ()
|
||||
(multiple-value-bind (bg fg attribute)
|
||||
(thread-message-colors +key-selected+)
|
||||
(values bg fg attribute)))
|
||||
|
||||
(defun thread-message-deleted-colors ()
|
||||
(multiple-value-bind (bg fg attribute)
|
||||
(thread-message-colors +key-deleted+)
|
||||
(values bg fg attribute)))
|
||||
|
||||
(defun modeline-colors (window-key)
|
||||
(values (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-modeline+
|
||||
+key-background+)
|
||||
(access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-modeline+
|
||||
+key-foreground+)))
|
||||
|
||||
(defun modeline-fmt (window-key)
|
||||
(access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-modeline+
|
||||
+key-value+))
|
||||
|
||||
(defun date-fmt (window-key)
|
||||
(let* ((raw (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-date-format+
|
||||
+key-value+)))
|
||||
(date-formatter:expand-date-formatter-spec raw)))
|
||||
|
||||
(defun locked/unlocked-value (key-window locked)
|
||||
(let ((key-locked (if locked
|
||||
+key-locked+
|
||||
+key-unlocked+)))
|
||||
(access:accesses *software-configuration*
|
||||
key-window
|
||||
key-locked
|
||||
+key-value+)))
|
||||
|
||||
(defun locked/unlocked-account-mark-value (key-window locked)
|
||||
(let ((key-locked (if locked
|
||||
+key-locked+
|
||||
+key-unlocked+)))
|
||||
(access:accesses *software-configuration*
|
||||
key-window
|
||||
+key-account+
|
||||
key-locked
|
||||
+key-mark+
|
||||
+key-value+)))
|
||||
|
||||
(defun message-window-locked-account-mark ()
|
||||
(locked/unlocked-account-mark-value +key-message-window+ t))
|
||||
|
||||
(defun message-window-unlocked-account-mark ()
|
||||
(locked/unlocked-account-mark-value +key-message-window+ nil))
|
||||
|
||||
(defun message-window-account-locking-status-mark (locking-value)
|
||||
(if locking-value
|
||||
(message-window-locked-account-mark)
|
||||
(message-window-unlocked-account-mark)))
|
||||
|
||||
(defun message-window-line-mark-values ()
|
||||
"return three values: mark string fg and bg"
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-line-position-mark+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-line-position-mark+
|
||||
+key-foreground+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-line-position-mark+
|
||||
+key-background+)))
|
||||
|
||||
(defun message-window-attachments-header ()
|
||||
(values (access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-attachment-header+
|
||||
+key-prefix+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-attachment-header+
|
||||
+key-postfix+
|
||||
+key-value+)
|
||||
(access:accesses *software-configuration*
|
||||
+key-message-window+
|
||||
+key-attachment-header+
|
||||
+key-value+)))
|
||||
|
||||
(defclass form-style ()
|
||||
((background
|
||||
:initform :black
|
||||
:initarg :background
|
||||
:accessor background)
|
||||
(foreground
|
||||
:initform :white
|
||||
:initarg :foreground
|
||||
:accessor foreground)
|
||||
(input-background
|
||||
:initform :black
|
||||
:initarg :input-background
|
||||
:accessor input-background)
|
||||
(input-foreground
|
||||
:initform :white
|
||||
:initarg :input-foreground
|
||||
:accessor input-foreground)
|
||||
(selected-background
|
||||
:initform :black
|
||||
:initarg :selected-background
|
||||
:accessor selected-background)
|
||||
(selected-foreground
|
||||
:initform :white
|
||||
:initarg :selected-foreground
|
||||
:accessor selected-foreground)))
|
||||
|
||||
(defmethod print-object ((object form-style) stream)
|
||||
(print-unreadable-object (object stream :type t)
|
||||
(with-accessors ((background background)
|
||||
(foreground foreground)
|
||||
(input-background input-background)
|
||||
(input-foreground input-foreground)
|
||||
(selected-background selected-background)
|
||||
(selected-foreground selected-foreground)) object
|
||||
(format stream
|
||||
"fg ~a bg ~a input-fg ~a input-bg ~a selected-fg ~a selected-bg ~a"
|
||||
foreground
|
||||
background
|
||||
input-foreground
|
||||
input-background
|
||||
selected-foreground
|
||||
selected-background))))
|
||||
|
||||
(defun form-style (window-key)
|
||||
(make-instance 'form-style
|
||||
:background (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-background+)
|
||||
:foreground (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-foreground+)
|
||||
:selected-background (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-input+
|
||||
+key-selected+
|
||||
+key-background+)
|
||||
:selected-foreground (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-input+
|
||||
+key-selected+
|
||||
+key-foreground+)
|
||||
:input-background (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-input+
|
||||
+key-background+)
|
||||
:input-foreground (access:accesses *software-configuration*
|
||||
window-key
|
||||
+key-input+
|
||||
+key-foreground+)))
|
|
@ -0,0 +1,50 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :specials)
|
||||
|
||||
(defparameter *main-window* nil
|
||||
"The main window of the program. AKA the screen")
|
||||
|
||||
(defparameter *keybindings-suggestions-window* nil
|
||||
"The window to show suggestions for keybindings.")
|
||||
|
||||
(defparameter *strings-suggestions-window* nil
|
||||
"The window to show suggestions for keybindings.")
|
||||
|
||||
(defparameter *command-window* nil
|
||||
"The window to deal with user key input.")
|
||||
|
||||
(defparameter *thread-window* nil
|
||||
"The threaded messages window.")
|
||||
|
||||
(defparameter *message-window* nil
|
||||
"The window where a single message is rendered.")
|
||||
|
||||
(defparameter *send-message-window* nil
|
||||
"The window shown to confirm sending a new message.")
|
||||
|
||||
(defparameter *follow-requests-window* nil
|
||||
"The window shown to accept follow requests.")
|
||||
|
||||
(defparameter *tags-window* nil
|
||||
"The window shown to manage tags subscriptions.")
|
||||
|
||||
(defparameter *conversations-window* nil
|
||||
"The window that shows conversations.")
|
||||
|
||||
(defparameter *open-attach-window* nil
|
||||
"The window that shows attachments for a message.")
|
|
@ -0,0 +1,45 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :stack)
|
||||
|
||||
(defparameter *stack* (misc:make-array-frame 0))
|
||||
|
||||
(defparameter *equal-function* #'equalp)
|
||||
|
||||
(defparameter *key-function* #'identity)
|
||||
|
||||
(defun push (val)
|
||||
(vector-push-extend val *stack*))
|
||||
|
||||
(defun pop ()
|
||||
(if (not (emptyp))
|
||||
(prog1
|
||||
(alexandria:last-elt *stack*)
|
||||
(setf *stack* (misc:safe-delete@ *stack* (1- (length *stack*)))))
|
||||
nil))
|
||||
|
||||
(defun find (element)
|
||||
(cl:find element *stack* :key *key-function* :test *equal-function*))
|
||||
|
||||
(defun emptyp ()
|
||||
(not (> (length *stack*) 0)))
|
||||
|
||||
(defmacro with-stack ((equal key) &body body)
|
||||
`(let ((*stack* (misc:make-array-frame 0))
|
||||
(*equal-function* ,equal)
|
||||
(*key-function* ,key))
|
||||
,@body))
|
|
@ -0,0 +1,52 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2018 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :suggestions-window)
|
||||
|
||||
(defclass suggestions-window (wrapper-window)
|
||||
((paginated-info
|
||||
:initform nil
|
||||
:initarg :paginated-info
|
||||
:accessor paginated-info)
|
||||
(current-page
|
||||
:initform 0
|
||||
:initarg :current-page
|
||||
:accessor current-page)))
|
||||
|
||||
(defun draw-pagination-info (win)
|
||||
(with-accessors ((paginated-info paginated-info)
|
||||
(current-page current-page)) win
|
||||
(let* ((msg (format nil
|
||||
(_ "Page ~a of ~a")
|
||||
(1+ current-page)
|
||||
(length paginated-info)))
|
||||
(msg-x (calc-center-on-window-width win msg))
|
||||
(msg-y (calc-bottom-of-window-height win)))
|
||||
(print-text win msg msg-x msg-y))))
|
||||
|
||||
(defmethod refresh-config :after ((object suggestions-window))
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(refresh-config-colors object swconf:+key-suggestions-window+)
|
||||
(refresh-config-sizes object swconf:+key-suggestions-window+)
|
||||
(let ((y (- (win-height *main-window*)
|
||||
(win-height object)
|
||||
+command-window-height+)))
|
||||
(win-move object 0 y))))
|
||||
|
||||
(defmethod draw ((object suggestions-window))
|
||||
(win-raise-to-top object))
|
||||
|
||||
(defgeneric update-suggestions (object hint &key &allow-other-keys))
|
|
@ -0,0 +1,150 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :tags-window)
|
||||
|
||||
(defclass tags-window (focus-marked-window
|
||||
simple-line-navigation-window
|
||||
title-window
|
||||
border-window)
|
||||
((new-messages-mark
|
||||
:initarg :new-messages-mark
|
||||
:initform ()
|
||||
:accessor new-messages-mark)
|
||||
(histogram-fg
|
||||
:initarg :histogram-fg
|
||||
:initform ()
|
||||
:accessor histogram-fg)))
|
||||
|
||||
(defmethod refresh-config :after ((object tags-window))
|
||||
(with-accessors ((croatoan-window croatoan-window)
|
||||
(histogram-fg histogram-fg)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)
|
||||
(new-messages-mark new-messages-mark)) object
|
||||
(let* ((theme-style (swconf:form-style swconf:+key-tags-window+))
|
||||
(fg (swconf:foreground theme-style))
|
||||
(bg (swconf:background theme-style))
|
||||
(selected-fg (swconf:selected-foreground theme-style))
|
||||
(selected-bg (swconf:selected-background theme-style))
|
||||
(new-message-value (swconf:tags-new-message-mark))
|
||||
(width (- (win-width *main-window*)
|
||||
(win-width *thread-window*)))
|
||||
(raw-height (swconf:win-height swconf:+key-tags-window+))
|
||||
(height (main-window:parse-subwin-h raw-height))
|
||||
(y 0)
|
||||
(x 0))
|
||||
(setf selected-line-fg selected-fg)
|
||||
(setf selected-line-bg selected-bg)
|
||||
(setf new-messages-mark new-message-value)
|
||||
(setf histogram-fg (swconf:tags-histogram-foreground))
|
||||
(setf (background croatoan-window)
|
||||
(tui:make-background bg))
|
||||
(setf (bgcolor croatoan-window) bg)
|
||||
(setf (fgcolor croatoan-window) fg)
|
||||
(win-resize object width height)
|
||||
(win-move object x y)
|
||||
object)))
|
||||
|
||||
(defmethod draw :before ((object tags-window))
|
||||
(with-accessors ((rows rows)
|
||||
(histogram-fg histogram-fg)
|
||||
(single-row-height single-row-height)
|
||||
(top-row-padding top-row-padding)
|
||||
(new-messages-mark new-messages-mark)) object
|
||||
(win-clear object)
|
||||
(with-croatoan-window (croatoan-window object)
|
||||
(let ((histogram-width (- (win-width-no-border object)
|
||||
(length new-messages-mark))))
|
||||
(loop
|
||||
for y from (+ 2 top-row-padding) by single-row-height
|
||||
for row-fields in (mapcar #'fields rows) do
|
||||
(let* ((histogram-data (fields-histogram row-fields))
|
||||
(length-histogram-data (length histogram-data))
|
||||
(histogram-visualized-data (safe-subseq histogram-data
|
||||
(- length-histogram-data
|
||||
histogram-width)
|
||||
length-histogram-data))
|
||||
(histogram (cl-spark:spark histogram-visualized-data))
|
||||
(got-new-messages-p (getf row-fields :got-new-message-p)))
|
||||
(print-text object
|
||||
histogram
|
||||
1 y
|
||||
:bgcolor (bgcolor croatoan-window)
|
||||
:fgcolor histogram-fg)
|
||||
(when got-new-messages-p
|
||||
(print-text object new-messages-mark nil nil
|
||||
:bgcolor (bgcolor croatoan-window)
|
||||
:fgcolor histogram-fg))))))))
|
||||
|
||||
(defmethod resync-rows-db ((object tags-window) &key (redraw t) (suggested-message-index nil))
|
||||
(with-accessors ((rows rows)
|
||||
(selected-line-bg selected-line-bg)
|
||||
(selected-line-fg selected-line-fg)) object
|
||||
(flet ((make-rows (line-fields bg fg)
|
||||
(mapcar (lambda (fields)
|
||||
(let ((text (db:tag->folder-name (fields-tag fields))))
|
||||
(make-instance 'line
|
||||
:fields fields
|
||||
:normal-text text
|
||||
:selected-text text
|
||||
:normal-bg bg
|
||||
:normal-fg fg
|
||||
:selected-bg fg
|
||||
:selected-fg bg)))
|
||||
line-fields)))
|
||||
(let ((line-fields (make-tag-line-fields)))
|
||||
(setf rows (make-rows line-fields
|
||||
selected-line-bg
|
||||
selected-line-fg))
|
||||
(when suggested-message-index
|
||||
(select-row object suggested-message-index))
|
||||
(when redraw
|
||||
(draw object))))))
|
||||
|
||||
(defun fields-tag (fields)
|
||||
(getf fields :tag))
|
||||
|
||||
(defun fields-histogram (fields)
|
||||
(getf fields :histogram))
|
||||
|
||||
(defun make-tag-line-fields ()
|
||||
(let* ((all-tags-name (db:all-subscribed-tags-name :sort-data t))
|
||||
(all-tags (db:all-subscribed-tags :sort-data t))
|
||||
(all-histograms (loop for tag-name in all-tags-name collect
|
||||
(db:tag-histogram tag-name))))
|
||||
(loop
|
||||
for tag in all-tags
|
||||
for histogram in all-histograms collect
|
||||
(list :tag (db:row-id tag)
|
||||
:got-new-message-p (db:row-tag-got-new-message tag)
|
||||
:histogram histogram))))
|
||||
|
||||
(defun init ()
|
||||
(let* ((low-level-window (make-croatoan-window :enable-function-keys t)))
|
||||
(setf *tags-window*
|
||||
(make-instance 'tags-window
|
||||
:title (_ "Subscribed tags")
|
||||
:single-row-height 3
|
||||
:uses-border-p t
|
||||
:keybindings keybindings:*tags-keymap*
|
||||
:croatoan-window low-level-window))
|
||||
(refresh-config *tags-window*)
|
||||
(resync-rows-db *tags-window* :redraw nil)
|
||||
(when (rows *tags-window*)
|
||||
(select-row *tags-window* 0))
|
||||
(draw *tags-window*)
|
||||
*tags-window*))
|
|
@ -0,0 +1,22 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(in-package :all-tests)
|
||||
|
||||
(defsuite all-suite ())
|
||||
|
||||
(defun run-all-tests (&key (use-debugger nil))
|
||||
(clunit:run-suite 'all-suite :use-debugger use-debugger))
|
|
@ -0,0 +1,54 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :box-tests)
|
||||
|
||||
(defsuite box-suite (all-suite))
|
||||
|
||||
(defun shared-boxes ()
|
||||
(let ((contents (vector nil)))
|
||||
(values (box contents)
|
||||
(box contents))))
|
||||
|
||||
(deftest test-unbox (box-suite)
|
||||
(assert-true (numberp (unbox 1)))
|
||||
(assert-true (= (unbox 1) (unbox 1))))
|
||||
|
||||
(deftest test-shared (box-suite)
|
||||
(multiple-value-bind (box-1 box-2)
|
||||
(shared-boxes)
|
||||
(assert-false (eq box-1 box-2))
|
||||
(assert-true (eq (unbox box-1) (unbox box-2)))
|
||||
(assert-true (eq (unbox 1) (unbox 1)))))
|
||||
|
||||
(defun shared-dboxes ()
|
||||
(let ((contents (vector 'a)))
|
||||
(values (dbox contents)
|
||||
(dbox contents))))
|
||||
|
||||
(deftest test-dbox-shared (box-suite)
|
||||
(multiple-value-bind (dbox-1 dbox-2)
|
||||
(shared-dboxes)
|
||||
(assert-false (eq dbox-1 dbox-2))
|
||||
(assert-true (eq (dunbox dbox-1) (dunbox dbox-2)))
|
||||
(assert-true
|
||||
(progn
|
||||
(setf (unbox dbox-1) (unbox dbox-2))
|
||||
(setf (dunbox dbox-1) "foo")
|
||||
(eq (dunbox dbox-1)
|
||||
(dunbox dbox-2)))
|
||||
(format nil "~a ~a" dbox-1 dbox-2))))
|
|
@ -0,0 +1,25 @@
|
|||
;; tinmop: an humble mastodon client
|
||||
;; Copyright (C) 2020 cage
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program.
|
||||
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
|
||||
|
||||
(in-package :misc-tests)
|
||||
|
||||
(defsuite misc-suite (all-suite))
|
||||
|
||||
(deftest test-shuffle (misc-suite)
|
||||
(assert-true
|
||||
(let ((bag (loop repeat 1000 collect (num:lcg-next-upto 1000))))
|
||||
(null (set-difference bag (shuffle bag))))))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue