aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAgeFilesLines
...
* config: warn for all deprecated settingsRobin Jarry2023-02-023-27/+50
| | | | | | | | | | index-format may be used in contextual [ui] sections. Display a warning for every converted section so that users don't miss any. Fixes: 535300cfdbfc ("config: add columns based index format") Reported-by: Bence Ferdinandy <bence@ferdinandy.com> Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* config: add option for SMTP HELO/EHLO local domainKarel D. Kopecký2023-02-023-2/+22
| | | | | | | | | | | | | | | | Expose go-smtp functionality for setting the domain name as a config option. This allows aerc to communicate with SMTP servers with strict antispam measures without relying on sendmail. In theory, this should be set to a fully qualified domain name, but some servers simply forbid the use of "localhost", so it is reasonable to let the user set whatever value works for them. For comparison, this is equivalent to the functionality of the "domain" option of msmtp. Signed-off-by: Karel D. Kopecký <kdk@freeshell.de> Acked-by: Robin Jarry <robin@jarry.cc>
* colorize: restore previous default themeRobin Jarry2023-02-026-122/+122
| | | | | | | | | | | Restore the default theme from the previous colorize awk script. It is more colorful and may be more appealing to new users out of the box. Since colorize is now configurable via stylesets, power users can do whatever they like. Requested-by: Andrea Pappacoda <andrea@pappacoda.it> Signed-off-by: Robin Jarry <robin@jarry.cc> Agreed-by: Bence Ferdinandy <bence@ferdinandy.com>
* viewer: use textproto.WriteHeaderRobin Jarry2023-02-021-16/+13
| | | | | | | | | When [ui].show-headers is true, use textproto.WriteHeader instead of manually writing the header values. This allows displaying the original header buffer with on-the-wire format (and with lines wrapped). Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* templates: add trimSignature functionMoritz Poldrack2023-01-294-2/+30
| | | | | | | | | | | Some contacts, especially corporate, include a wall of text in their signatures. To not clutter the reply chain, this commit introduces a new function to the templating engine that removes the signature from a message. Link: https://learn.microsoft.com/en-us/microsoft-365/admin/setup/create-signatures-and-disclaimers Signed-off-by: Moritz Poldrack <git@moritz.sh> Acked-by: Robin Jarry <robin@jarry.cc>
* config: remove unused field "RenderAccountTabs"Tim Culverhouse2023-01-291-2/+0
| | | | | | | | | Remove unused "RenderAccountTabs" field from config. This field is not used anywhere in the codebase and does not appear anywhere in the docs nor default configs. Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
* dirlist: simplify getRUEString logicTim Culverhouse2023-01-291-14/+7
| | | | | | | Reuse the newly-added GetRUECount method to simplify getRUEString Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
* tabs: use template for account tab nameTim Culverhouse2023-01-298-1/+86
| | | | | | | | | | | Use a go template to render the account tab display. Add config option for setting a specific template for the account. Add a method on Tab to allow setting a title, which may be different than the tab Name. The default template is {{.Account}}. Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
* log: log account name in worker message logsTim Culverhouse2023-01-291-8/+8
| | | | | | | Add account name to log of all Post/Process Message/Action logs. Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
* wrap: be more robust with localeRobin Jarry2023-01-281-7/+36
| | | | | | | | | | | | | | | | | | | | | | | On (some?) MacOS systems there is no C.UTF-8 locale available. Instead there is a non-standard "UTF-8" (encoding only) replacement. Running wrap on MacOS results in an error: error: failed to set locale: Bad file descriptor Instead of expecting that C.UTF-8 will always be available, try to use the user set locale (either from the $LC_ALL or $LANG environment variables). If these variables are unset or if they are set to an invalid/non-existent locale, fallback on C.UTF-8. If C.UTF-8 is not available, make one last desperate attempt for this UTF-8 non-standard locale (MacOS only). aerc will always send UTF-8 encoded text to the filter commands, If the locale that we managed to load does not use the UTF-8 character encoding, exit with an explicit error instead of risking undefined behaviour. Reported-by: Ben Cohen <ben@bencohen.net> Signed-off-by: Robin Jarry <robin@jarry.cc>
* filters: make colorize URL regex more strictAndrea Pappacoda2023-01-281-4/+4
| | | | | | | | | | | | | | | | | | | | | | | The previous URL regex was too lax, allowing all "[:graph:]" characters after the protocol:// part. This caused the script to mark as part of an URL also things like ">", which is commonly used as a URL delimiter in plain text and Markdown; the url() function tried to account for this with some heuristic to remove trailing characters, but it didn't always work (see the screenshots below). As RFC 3986 specifies the list of allowed characters in URLs, we can simply make our regex stricter and only mark characters as part of an URL if they match the allowed set. As the number of allowed characters has been reduced, the aforementioned heuristic has been slightly simplified. I've also removed the backslash escapes from the bracket expressions, as POSIX regular expressions do not require them; the only characters that need special handling are ']' and '-', which need to be placed at the start and at the end of the expression, respectively. Signed-off-by: Andrea Pappacoda <andrea@pappacoda.it> Acked-by: Robin Jarry <robin@jarry.cc>
* stylesets: add solarized themeJens Grassel2023-01-282-0/+55
| | | | | | | | | This adds a styleset using the solarized theme to aerc which affects the tui and the colorize wrapper. Link: https://ethanschoonover.com/solarized/ Signed-off-by: Jens Grassel <jens@wegtam.com> Acked-by: Robin Jarry <robin@jarry.cc>
* ci: fix obscure build errorRobin Jarry2023-01-261-3/+1
| | | | | | | | | | | | | | | | | Fix the following obscure build error: gcc -O2 -g -Wall -Wextra -Werror -Wformat-security -Wstack-protector -fstack-protector-strong -fanalyzer gcc: fatal error: no input files The issue is that CFLAGS contains newline characters '\n' because of the yaml > continuation. I had misinterpreted the spec hoping that the final newline will also be stripped which is not. Put all CFLAGS on a single line. Link: https://yaml.org/spec/1.0/#id2567537 Fixes: 3191ee171c43 ("filters: rewrite wrap in c") Signed-off-by: Robin Jarry <robin@jarry.cc>
* compose: allow sending format=flowed messagesRobin Jarry2023-01-265-8/+33
| | | | | | | | | | | | | | | | | | | | Allow composing and sending messages with: Content-Type: text/plain; Format=Flowed This requires additional configuration in the text editor to actually produce the required trailing spaces at the end of lines that are part of the same paragraph. For example, with vim: "~/.vim/ftplugin/mail.vim setlocal textwidth=72 setlocal formatoptions=1jnwtcql setlocal comments+=nb:> Link: https://www.rfc-editor.org/rfc/rfc3676.html Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Moritz Poldrack <moritz@poldrack.dev>
* viewer: allow piping full headers in a filterRobin Jarry2023-01-264-9/+65
| | | | | | | | Allow defining a .headers special filter command that will be used only to process email headers (when [viewer].show-headers=true). Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Kt Programs <ktprograms@gmail.com>
* viewer: remove local redundant showHeaders variablesRobin Jarry2023-01-261-29/+25
| | | | | | | The config objects are now globally available. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Kt Programs <ktprograms@gmail.com>
* config: remove unused ui.show-header settingRobin Jarry2023-01-261-10/+6
| | | | | | | This has setting never been used. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Kt Programs <ktprograms@gmail.com>
* filters: rewrite colorize in cRobin Jarry2023-01-2619-188/+1030
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Since its introduction, we had multiple issues with the colorize awk script with regard to non-GNU awk compatibility. Also, this script is standalone and the color theme must be hard coded into it. Reading from an external configuration file (aerc's styleset) from a non-GNU awk is close to impossible (and even far from trivial with GNU awk). Rewrite the builtin colorize filter in C to allow getting the color theme from aerc's active styleset. The theme is configured using the existing styleset syntax and attributes under a separate [viewer] section (see examples and man page). Export the active styleset file path to AERC_STYLESET env var when invoking the filter command so that colorize can access it and use it. I have tested compilation (with clang-analyzer and gcc -fanalyzer) and basic operation on FreeBSD, Fedora (glibc) and Alpine (muslibc). More tests would probably be required on MacOSX and older Linux distros. I also added test vectors to give some confidence that this works as expected. The execution with these vectors passed valgrind --leak-check=full without errors. NB: the default theme has changed to be more minimal. Sample stylesets have more colorful examples. The awk -v theme=xxx option is no longer supported. usage: colorize [-h] [-s FILE] [-f FILE] options: -h show this help message -s FILE use styleset file (default $AERC_STYLESET) -f FILE read from filename (default stdin) Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Moritz Poldrack <moritz@poldrack.dev>
* filters: rewrite wrap in cRobin Jarry2023-01-2618-485/+1448
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This utility introduced in commit c9524d265793 ("filters: add wrap utility written in go") allows to reflow text to view emails that have very long lines without breaking quotes, lists and indentation. For such a simple task, go produces a binary that is 2.0M bytes on disk. After stripping debugging symbols, it can be reduced to 1.2M bytes. All of this for 267 lines of source code. This is a bit ridiculous, provided people may load this binary into memory multiple times per minute. This tool is a small side-project that seems not suitable for golang. Rewrite it in C. It now only depends on a POSIX libc to run. It is safe to assume that there is one available on all *NIX systems in the world of 2023. The resulting binary is now 27K bytes (15K after stripping). To build it, a C compiler and libc headers are required. These should most likely be available since they are dependencies of the go compiler toolchain. I have tested compilation (with clang-analyzer and gcc -fanalyzer) and basic operation on FreeBSD, Fedora (glibc) and Alpine (musl libc). More tests would probably be required on MacOSX and older Linux distros. I also added test vectors to give some confidence that this works as expected. Update CI with aggressive gcc hardening flags and to run these tests with valgrind --leak-check=full. Command line options are unchanged: usage: wrap [-h] [-w INT] [-r] [-l INT] [-f FILE] Wrap text without messing up email quotes. options: -h show this help message -w INT preferred wrap margin (default 80) -r reflow all paragraphs even if no trailing space -l INT minimum percentage of letters in a line to be considered a paragaph -f FILE read from filename (default stdin) Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com> Tested-by: Maxwell G <gotmax@e.email>
* stylesets: add support for dim textRobin Jarry2023-01-262-1/+17
| | | | | | | Allow defining <obj>.dim=true to turn on half-bright text. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Moritz Poldrack <moritz@poldrack.dev>
* switch-account: also switch account configurationBence Ferdinandy2023-01-262-1/+2
| | | | | | | | | When switching accounts, headers were updated, but not the account configurations in the composer. Switch the account config also and add a debug log to send with the uri being used. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
* imap,smtp: cache and cycle XOAUTH2 refresh tokenDean2023-01-262-2/+40
| | | | | | | | | | | | | | | | | | | | | | | | Normally for emails with xoauth2, the help page says to pass the refresh token as the password. When the refresh token expires, aerc can't fetch the access token, and you must get a new refresh token from the external script. This patch implements a cycle of refresh tokens so you only need to use an external script to fetch the refresh token once. Once you have fetched the initial refresh token (with an external script like mutt_xoauth2.py or https://github.com/gaoDean/oauthRefreshToken), that refresh token is inputted as the password to aerc (as normal) to fetch the access token. Before this patch aerc used to only fetch the access token, but now it fetches that and a new refresh token, which it caches in $XDG_CONFIG_HOME/aerc/<account>-xoauth2.token. In the next opening of aerc, aerc will pull the refresh token from the cache, and use it instead of the inputted refresh token from the password. If it is not present in the cache, the refresh token is taken from the password as usual. Signed-off-by: Dean <gao.dean@hotmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
* logging: add trace to every command callBence Ferdinandy2023-01-261-0/+2
| | | | | | | | | A previous commit introduced a trace loglevel, but aerc doesn't make much use of it. Logging is not very informative either. Add a log.Tracef to every command and log the provided arguments as well. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
* status: print appropriate log message on pushBence Ferdinandy2023-01-261-0/+4
| | | | | | | | Increase logging usability, by logging all messages pushed to status at an appropriate loglevel. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
* msgstore: fix new-email trigger firing for all unread messagesRobin Jarry2023-01-241-2/+2
| | | | | | | | Unfortunate typo. Fixes: 5677f93ff8e0 ("model: change flags array to bitmask") Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Ben Cohen <ben@bencohen.net>
* templates: correctly return week format when necessaryNojus Gudinavičius2023-01-111-1/+1
| | | | | | | | | This fixes a bug when thisDayTimeFmt was returned for both this day and this week. Fixes: d758441fe0c4 ("templates: add more fields and functions") Signed-off-by: Nojus Gudinavičius <nojus.gudinavicius@gmail.com> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* spinner: add spinner-interval config optionDean2023-01-113-9/+18
| | | | | | | | | | The default 200ms between each spinner frame can be unsuitable for spinners with many frames, so this adds a spinner-interval config option with type `time.Duration` to specify the interval between frames. The default is still the usual 200ms. Signed-off-by: Dean <gao.dean@hotmail.com> Acked-by: Moritz Poldrack <moritz@poldrack.dev>
* config: disable ini reflection for pgp-error-levelRobin Jarry2023-01-111-1/+1
| | | | | | | | | This field must be converted from a string value to an integer manually. The conversion cannot be automated via go ini reflection mechanism. Fixes: 2af81a743048 ("pgp: add configurable error level for opportunistic encryption") Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Jose Lombera <jose@lombera.dev>
* config: fix accounts backend specific parameters detectionRobin Jarry2023-01-111-16/+2
| | | | | | | | | | | | | | | | Since commit c56027b2e69e ("config: cleanup accounts.conf section parsing"), no backend-specific settings are parsed and stored into account.Params. The logic is completely broken. Any key is specific unless one field of the AccountConfig struct has a matching ini:"key" tag. The fields that are tagged ini:"-" are already handled in the parent switch block. Reported-by: Jose Lombera <jose@lombera.dev> Reported-by: Ben Cohen <ben@bencohen.net> Fixes: c56027b2e69e ("config: cleanup accounts.conf section parsing") Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Jose Lombera <jose@lombera.dev>
* msglist: log template errorsRobin Jarry2023-01-111-0/+1
| | | | | | | | | | The template exec errors can be rather verbose and may be truncated when the table is rendered. Log them as errors so that they can be inspected by users. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Bence Ferdinandy <bence@ferdinandy.com>
* filters: install them in $PREFIX/libexec/aerc/filtersRobin Jarry2023-01-105-14/+35
| | | | | | | | | | | | | | | | | | | | | The filesystem hierarchy standard describes /usr/share as "Architecture-independent data". This folder is not intended for executable scripts and especially not for arch specific binary files (such as the wrap filter). Lintian reports an error with aerc 0.14.0: arch-dependent-file-in-usr-share [usr/share/aerc/filters/wrap] Which I had to fix by moving the filter into /usr/libexec. Install all filters into $PREFIX/libexec/aerc/filters and update the default SearchDirs to look them up in here as well. Link: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s11.html Link: https://salsa.debian.org/go-team/packages/aerc/-/commit/a0ca00260ffd Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Moritz Poldrack <moritz@poldrack.dev>
* config: make styleset error message clearerBence Ferdinandy2023-01-101-1/+2
| | | | | | | | | When aerc fails to load a styleset it reports being unable to load the default styleset. Change error message to print the name of the styleset instead of default. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
* style: add msglist_answered config optionMartin Lucina2023-01-104-7/+16
| | | | | | | | Add a style for messages that have been marked as answered, and a "msglist_answered" config option for it. Signed-off-by: Martin Lucina <martin@lucina.net> Acked-by: Robin Jarry <robin@jarry.cc>
* changelog: move index-columns in the unreleased sectionRobin Jarry2023-01-081-2/+8
| | | | | | | | This was applied after 0.14.0 was released. I forgot to update my patch before pushing it... Fixes: 535300cfdbfc ("config: add columns based index format") Signed-off-by: Robin Jarry <robin@jarry.cc>
* pgp: add configurable error level for opportunistic encryptionTim Culverhouse2023-01-063-2/+44
| | | | | | | | | | | | | | Add a user-configurable error level for when opportunistic encryption is enabled but the message cannot be encrypted. Set the default level to this as "warn". This config option *only* applies when opportunistic encryption is enabled. If a user tries to manually encrypt a message, an error will still be shown. Don't show encryption status until at least one recipient is added. Fixes: https://todo.sr.ht/~rjarry/aerc/95 Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
* lint: add missing panic handlers in goroutinesRobin Jarry2023-01-0616-0/+26
| | | | | | | | These issues were all reported by the new custom analyzer introduced in previous commit. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* contrib: add linter to check for panic handler in goroutinesRobin Jarry2023-01-065-1/+140
| | | | | | | | | | | | | | | | | If log.PanicHandler() is not installed in a goroutine and a panic occurs, the terminal state is not restored. This causes the panic trace to be unreadable since the terminal is broken. Add a custom analyzer that parses our code and ensures that: defer log.PanicHandler() is the first statement of all functions that are executed in goroutines. Include that linter in golangci config. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* doc: fix English typosRobin Jarry2023-01-064-5/+5
| | | | | | | Reported by Lintian (Debian). Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Moritz Poldrack <moritz@poldrack.dev>
* config: add columns based index formatRobin Jarry2023-01-069-149/+474
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | The index-format option comes from mutt and is neither user friendly, nor intuitive. Introduce a new way of configuring the message list contents. Replace index-format with multiple settings to make everything more intuitive. Reuse the table widget added in the previous commit. index-columns Comma-separated list of column names followed by optional alignment and width specifiers. column-separator String separator between columns. column-$name One setting for every name defined in index-columns. This supports golang text/template syntax and allows access to the same message information than before and much more. When index-format is still defined in aerc.conf (which will most likely happen when users will update after this patch), convert it to the new index-columns + column-$name and column-separator system and a warning is displayed on startup so that users are aware that they need to update their config. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
* ui: add reusable table widgetRobin Jarry2023-01-042-0/+330
| | | | | | | | | | | | | This will be used by the message list index and by the status line. A table is constructed from rows/width dimensions, a list of column definitions and a column separator. Provide functions to parse column definitions from ini config files. This will be used in the next commit. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* selector: implement dynamic heightRobin Jarry2023-01-041-3/+17
| | | | | | | | Depending on the number of lines in the prompt text, allow the selector dialog to grow in height. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* format: add utf-8 aware truncate head functionRobin Jarry2023-01-042-0/+27
| | | | | | | | | The runewidth module only allows truncating at the end of strings. Add a function to truncate at the beginning. It will be used for the table widget in the next commit. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* templates: add more fields and functionsRobin Jarry2023-01-046-40/+556
| | | | | | | | | | | Add functions and fields in preparation for more than only message templates. The idea is to reuse the same symbols for the message list format and other parts of the UI. Update the man page accordingly. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* templates: change fields as lazy functionsRobin Jarry2023-01-0410-50/+79
| | | | | | | | | No need to pre-render fields that are not necessarily accessed in templates. Change fields to functions that are evaluated only when required. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* templates: split code in multiple filesRobin Jarry2023-01-043-182/+193
| | | | | | | This makes room in preparation for more code in here. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* config: parse account from and aliases onceRobin Jarry2023-01-049-74/+38
| | | | | | | | | Instead of accepting any garbage for these configuration fields, parse them when parsing accounts.conf and store mail.Address objects. Reuse these objects everywhere. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* config: cleanup accounts.conf section parsingRobin Jarry2023-01-041-52/+48
| | | | | | | | | | Use go ini reflection capabilities where possible. Mark fields that can be trivially parsed and those who need manual parsing. Restrict backend-specific parameters to ini keys that are not listed as ini struct field tags. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* model: change flags array to bitmaskRobin Jarry2023-01-0428-207/+120
| | | | | | | Using a list of integers is not optimal. Use a bit mask instead. Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Tim Culverhouse <tim@timculverhouse.com>
* send: add option to send&archiveBence Ferdinandy2023-01-045-14/+46
| | | | | | | | | Add `:send -a flat|month|year` to send, which archives the message being replied to. Extract most of archive logic into a separate function to make sure it behaves as manual archiving. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Acked-by: Robin Jarry <robin@jarry.cc>
* Release version 0.14.00.14.0Robin Jarry2023-01-042-1/+3
| | | | Signed-off-by: Robin Jarry <robin@jarry.cc>