summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfosskers <>2018-09-08 23:32:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2018-09-08 23:32:00 (GMT)
commitbc060cb24689f1c3cf22775c14168a9514d6ccc2 (patch)
tree61ffc4b4e286321f959e826f4a3bf46414091852
version 2.0.0HEAD2.0.0master
-rw-r--r--CHANGELOG.md485
-rw-r--r--LICENSE675
-rw-r--r--README.md229
-rw-r--r--aura.cabal167
-rw-r--r--doc/aura.8417
-rw-r--r--doc/completions/_aura591
-rw-r--r--doc/completions/bashcompletion.sh333
-rw-r--r--exec/Flags.hs515
-rw-r--r--exec/Settings.hs72
-rw-r--r--exec/aura.hs146
-rw-r--r--lib/Aura/Build.hs144
-rw-r--r--lib/Aura/Cache.hs66
-rw-r--r--lib/Aura/Colour.hs49
-rw-r--r--lib/Aura/Commands/A.hs202
-rw-r--r--lib/Aura/Commands/B.hs46
-rw-r--r--lib/Aura/Commands/C.hs155
-rw-r--r--lib/Aura/Commands/L.hs86
-rw-r--r--lib/Aura/Commands/O.hs36
-rw-r--r--lib/Aura/Core.hs190
-rw-r--r--lib/Aura/Dependencies.hs168
-rw-r--r--lib/Aura/Diff.hs24
-rw-r--r--lib/Aura/Install.hs231
-rw-r--r--lib/Aura/Languages.hs1385
-rw-r--r--lib/Aura/Languages/Fields.hs290
-rw-r--r--lib/Aura/Logo.hs109
-rw-r--r--lib/Aura/MakePkg.hs81
-rw-r--r--lib/Aura/Packages/AUR.hs135
-rw-r--r--lib/Aura/Packages/Repository.hs91
-rw-r--r--lib/Aura/Pacman.hs145
-rw-r--r--lib/Aura/Pkgbuild/Base.hs27
-rw-r--r--lib/Aura/Pkgbuild/Editing.hs53
-rw-r--r--lib/Aura/Pkgbuild/Fetch.hs47
-rw-r--r--lib/Aura/Pkgbuild/Records.hs40
-rw-r--r--lib/Aura/Pkgbuild/Security.hs102
-rw-r--r--lib/Aura/Settings.hs121
-rw-r--r--lib/Aura/State.hs164
-rw-r--r--lib/Aura/Types.hs258
-rw-r--r--lib/Aura/Utils.hs224
-rw-r--r--test/Test.hs82
39 files changed, 8381 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0595a39
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,485 @@
+# Aura Changelog
+
+## 2.0.0
+
+This is a large update representing about a month of full-time effort. Aura is now
+*much* faster, solves dependencies more reliably, has a few new features, and
+many fewer bugs. This is all while modernizing the code and seeing a ~15% decrease
+in overall code size.
+
+### Improvements
+
+#### Dependency Handling
+
+- Dependency resolution is now much faster and **handles split packages correctly**.
+ As such, the following troublesome packages now build correctly:
+ - `android-sdk`
+ - `backintime`
+ - `clion`
+ - `libc++`
+ - `mysql-connector-c++`
+ - `telegram-desktop-dev`
+ - `zoom`
+- Dependency provider selection for AUR packages.
+ - Example: `cron` is a legal dependency to specify, but there exists no package
+ with that name. `cronie` and `fcron` both "provide" `cron`, and now the user
+ can manually make a selection.
+ - Including `--noconfirm` will have Aura make its best guess.
+- If the exact version of an AUR package is available in the package cache, it
+ will be used automatically instead of being rebuilt. You can instead force a
+ rebuild with `--force`.
+
+#### PKGBUILD Analysis
+
+- In light of the recent [compromise of the Acroread package](https://lists.archlinux.org/pipermail/aur-general/2018-July/034151.html),
+ Aura nows performs static PKGBUILD analysis before building, and warns the user if
+ potentially malicious terms like `curl` are found.
+ - This feature can be disabled with `--noanalysis`. Caveat emptor!
+ - **This feature is a supplement in checking PKGBUILD safety, not a fool-proof replacement.**
+ It is always your responsiblity to understand what build scripts are running
+ on your machine.
+
+#### Saved Package State
+
+- `-Su` and `-Au` automatically save a package state before updating (unless you're doing `--dryrun`).
+ This lets you more easily roll back from problematic updates.
+- Saved package states can now be "pinned", which will protect them from removal via `-Bc`.
+ To pin a certain state, open its JSON file (see below in *Breaking Changes*) and edit the
+ `pinned` field from `false` to `true`.
+
+#### CLI Flags
+
+- Various CLI flag improvements:
+ - `-A --json <packages>`. Query the AUR directly for a package's raw JSON data. Great for debugging.
+ - `-Br` has been restored as short-hand for `-B --restore`.
+ - Added `-Bl` to list all saved package state filenames.
+ - `-Cb` added as a short-hand for `-C --backup`.
+ - The Pacman flags `--ignoregroup`, `--cachedir`, `--config`, and `--logfile` also now affect Aura.
+ - `--dryrun` no longer requires sudo.
+ - `--color never` turns off all text colouring. Further, by default, Aura will
+ only automatically colour text when it detects that the output device is a terminal
+ (and not a Unix pipe, say). These behaviours match Pacman.
+
+#### Translations
+
+- Improved Japanese translations thanks to **Onoue Takuro**.
+- Improved Portuguese translations thanks to **Wagner Amaral**.
+- Improved Russian translations thanks to **Alexey Kotlyarov**.
+
+#### Misc.
+
+- Packages that aren't interdependent will be built in succession without prompting
+ the user, only calling down to `pacman` once per group.
+- Modernized the Haskell code:
+ - Removed custom CLI flag handling in favour of `optparse-applicative`.
+ - Removed custom package version number parsing in favour of `versions`.
+ - Removed custom text colouring code in favour of `prettyprinter`.
+ - Removed the `Aura` Monad in favour of Extensible Effects via `freer-simple`.
+ - Removed custom shell interaction code in favour of `typed-process`.
+ - Used `async` to make AUR and `pacman` calls concurrent.
+ - `megaparsec` parsers used in place of hacky Regexes.
+- `aura` is now a library as well, and can be pulled into other Haskell projects.
+
+### Breaking Changes
+
+- `-B` now saves package states as JSON. This makes them readable by other tools,
+ and also improves internal code quality. **All old package state files are no longer readable by Aura.**
+ - The `"time"` field in these files is now a Haskell `ZonedTime`.
+- Various CLI flag changes:
+ - `--auradebug` is now just `--debug`, matching Pacman.
+ - `--aurignore` is now just `--ignore`, matching Pacman.
+ - `-Aw` has been removed.
+ - `-y` no longer works with `-A`. Perform an `-Sy` ahead of time instead.
+ - `-O` no longer accepts arguments to adopt packages, it only displays current
+ orphans. Use `-O --adopt` instead for the old behaviour.
+ - `-Ccc` is now `-C --notsaved`.
+- Help messages (`-h`) are no longer localised.
+- Support for `powerpill` removed.
+- Support for `customizepkg` removed.
+
+### Bug Fixes
+
+- Aura no longer returns an exit code of 1 if no packages are available to upgrade.
+- `-Aq` no longer fails at the package installation step.
+- Ctrl+C at certain moments no longer preserves the Pacman lock file.
+- `makepkg` output is no longer coloured green.
+
+## 1.4.0
+- *Dependency resolution vastly improved.* We removed the Bash parser that used
+ to poorly handle the bulk of this.
+- Chinese translations thanks to Kai Zhang.
+- `-M` operator and associated code fully removed.
+
+## 1.3.9
+- Updated Swedish translations
+- Disabled `-M` operator due to the `abs` tool being deprecated by Arch Linux
+
+## 1.3.8
+- Fixed behaviour of `-B` flags. For restoring of saved states, use the long
+ form: `aura -B --restore`. Cache backups also need to take their long form: `aura -C --backup`.
+- Fixed handling of language flags. Thanks to Doug Patti!
+
+## 1.3.5
+- Aura now uses version 5 of the `aur` package, to fix a critial bug
+- Updated Spanish and Polish
+
+## 1.3.4
+- Bash parser bug fix. Fixes some packages.
+
+## 1.3.3
+- Bash parser extended to be able to handle bash array expansions.
+ This enables packages with more (Bash-wise) complex PKGBUILDs to build
+ properly.
+
+## 1.3.2.1
+- `-Ai` and `-As` show popularity values.
+- `aur4` is no longer referenced.
+- `Yes/No` prompts are now localized.
+- Aura can be built with `stack`.
+- Updated German translation.
+
+## 1.3.1.0
+- Aura builds against GHC 7.10.
+- Updated German and Russian translations.
+
+## 1.3.0.4
+- Must use `--builduser` when building as root.
+- Bug fix regarding `--needed`.
+- Updated Portuguese translation.
+
+## 1.3.0.3
+- Pacman flags `--ignore` and `--ignoregroup` now work.
+- Bug fixes.
+
+## 1.3.0.2
+- (Bug fix) If a user tries to install a package in `IgnorePkg`, they
+ will now be prompted.
+- Man page updated.
+- Dependencies updated.
+
+## 1.3.0.1
+- (Bug fix) Tarballs are now downloaded from a URL provided by the RPC.
+
+## 1.3.0.0
+- Last major version of Aura 1! We have entered the design phase for Aura 2,
+ the implementation of which will transform Aura into a multi-distro
+ package management platform.
+- Aura 1 itself has entered "legacy" mode. The only releases to be made
+ on Aura 1 after this will be of `1.3.0.x`. You'll likely never see
+ `1.3.1.x`.
+- Befitting a major release, we have:
+
+ - New AUR interaction layer via the `aur` package. This fixes nasty
+ "AUR lookup failed" errors.
+ - `http-conduit` dropped for `wreq`, which is much easier to use.
+ - Better version number parsing/comparison on installation/upgrading.
+ - Package state backups have had their format changed. This BREAKS _all_
+ previously saved states. Please delete your old ones!
+ - Implemented extended `--needed` functionality for the AUR side of Aura.
+ AUR packages won't build if they're already installed.
+ - Indonesian translations!
+ - Other updated translations.
+
+## 1.2.3.4
+- zsh completions completely redone (thanks to Sauyon Lee!)
+ Having `aur-git` installed will let you auto-complete on AUR packages.
+
+## 1.2.3.3
+- `-As --{head,tail}` can now be passed numbers to truncate the results
+ to any number you want. The default is 10.
+- Updated Russian translation.
+
+## 1.2.3.2
+- Expanded Bash completions:
+ Aura Only
+ * Expanded completion for all options and search sub-options
+ * Package completion for -M/--abssync
+ * Completion for orphans using self-generated list
+ Pacman
+ * Include completion for all pacman options
+ * Directory or file completion for some common options
+- Use `--dryrun` with `-A` and `-M` install options to test everything
+ up until actual building would occur (dependency checks, etc.)
+
+## 1.2.3.1
+- Network.HTTP.Conduit errors are now caught properly
+ and don't crash aura.
+- `customizepkg` usage corrected.
+- zsh completions slightly expanded.
+
+## 1.2.3.0
+- Moved to `Network.HTTP.Conduit` from `Network.Curl`
+ This fixes the AUR connection issues.
+ Binary size has increased by quite a bit.
+
+## 1.2.2.1
+- `-Ai` now shows dependencies.
+
+## 1.2.2.0
+- Happy New Year!
+- makepkg's `--ignorearch` flag is now visible to Aura.
+ This allows users to build AUR packages on ARM devices
+ without worrying about architecture restrictions in PKGBUILDs.
+- Use `--head` and `--tail` to truncate `-As` results.
+- `-B` now uses local time.
+- Bug fixes and translation updates
+
+## 1.2.1.3
+- `-As` results now sort by vote. Use `--abc` to sort alphabetically.
+- "[installed]" will now be shown in `-As` results if you have it.
+- Fixed Bash parsing bug involving `\\` in arrays
+- Fixed broken `-C`
+- Updated Italian translation
+- Updated French translation
+
+## 1.2.1.2
+- Happy Canadian Thanksgiving
+- Bug fixes
+
+## 1.2.1.1
+- Norwegian translation added!
+- Dependency checks slightly faster
+- `--hotedit` and `--custom` can now be used together
+- Bug fixes
+
+## 1.2.1.0
+- New `builduser` option
+- `Prelude.head` bug fixed
+- Dependency checking is faster
+- New `-k` output
+- `--absdeps` works properly now
+- Other bug fixes
+
+## 1.2.0.2
+- Bug fixes and spelling corrections.
+
+## 1.2.0.1
+- Fixes dependency build order bug.
+
+## 1.2.0.0
+- New operator `-M` for building ABS packages. Has its own family of options.
+- Pre-built binary package available (x86_64 only)
+- Updates to Aura are now prioritized like pacman updates.
+- Dependency checking is now faster.
+- Use `-Ccc` to clean the cache of only packages not saved in any package
+ record.
+- `-Ai` now shows Maintainer name.
+- Extensive API changes.
+
+## 1.1.6.2
+- New option `--no-pp`. Disables use of powerpill, even if you have it.
+- This is a light release, as major work is being done on version 1.2 on
+ another development branch.
+
+## 1.1.6.1
+- Compatable with pacman 4.1
+- All pacman-color support removed
+- `-As` output slightly altered to match pacman.
+- Bug fixes.
+
+## 1.1.6.0
+- New option `--build` for specifying AUR package build path.
+- Vote number now shown in `-As` output.
+- Fixed Repo/AUR name collision bug.
+- API Change: Argument order for functions in `Aura/Languages` changed.
+
+## 1.1.5.0
+- `customizepkg` now usable with Aura.
+ Activate with the `--custom` option.
+- API Change: Aura/Pkgbuilds now a set of libraries as Aura/Pkgbuild/*
+
+## 1.1.4.3
+- Fixed flaw in `-Br`.
+- Fixed repititious `-Ad` output.
+- API Change: Aura/AurConnection renamed to Aura/AUR
+- API Change: function names in Aura/Languages now have better names.
+
+## 1.1.4.2
+- Haskell deps have been moved back to `makedepends`.
+- haskell-http removed as dependency.
+- API Change: function naming conventions in `Aura/Languages.hs` has been
+ changed. The localisation guide was also updated to reflect this.
+
+## 1.1.4.1
+- Support for the $LANG environment variable.
+- Aura will now pause before post-build installation if the package database
+ lock exists. This means you can run multiple instances of Aura and avoid
+ crashes.
+
+## 1.1.4.0
+- Serbian translation added. Thank you, Filip Brcic!
+- Fixed bug that was breaking `aura -Ss`.
+
+## 1.1.3.0
+- Changed `--save` and `--restore` to `-B` and `-Br`.
+ `--save` is now just an alias for `-B`, but `--restore`
+ must be used with `-B`.
+- New option `-Bc` for removing old unneeded package states.
+- `-Br` output is now sorted better and makes more sense.
+- Bash Parser can now properly parse `if` blocks, meaning packages
+ that have conditional dependencies based on architecutre will now
+ build properly.
+- API Change: `Aura.General` is now `Aura.Core`
+- Dep Change: `haskell-url` no longer needed.
+
+## 1.1.2.1
+- Added message to `--save`.
+
+## 1.1.2.0
+- Bash parser completely rewritten.
+- Bug fixes (thanks to the new parser)
+
+## 1.1.1.0
+- New option `--devel`. Rebuilds all devel packages installed.
+- Italian translation added! Thank you Bob Valantin!
+- Support for `powerpill` added. It will be used if installed, unless
+ the PACMAN variable is specifically set to something different.
+- Aura can now handle PKGBUILDs that produce multiple .pkg.tar files.
+- Bug fixes
+
+## 1.1.0.0
+- New `--save` and `--restore` options.
+- New option `-Ak` for showing PKGBUILD diffs when upgrading.
+- New option `--aurignore` for ignoring AUR packages.
+- Aura now reads `color.conf`.
+- Massive breaking API changes everywhere.
+- Aura now runs on the Aura Monad.
+- Code is quite cleaner now.
+
+## 1.0.8.1
+- Bash completions added.
+- zsh completions added.
+- Changed `--conf` to `--viewconf`
+- Fixed bug involving "symlink" Haskell error.
+
+## 1.0.8.0
+- Moved certain general functions to `Aura.Utils`
+- Moved `-L`, `-O`, `-A` functions out of `aura.hs`.
+- `--hotedit` functionality altered (fix).
+- The license message is now more badass.
+
+## 1.0.7.0
+- New libraries: Aura.Time, Aura.State
+- Moved `-C` functionality to `Aura.C`
+- New secret option you don't get to find out about until 1.1
+- Fixed manually alignment stupidity with `-Li`.
+- Bug fixes
+
+## 1.0.6.0
+- New libraries: ColourDiff, Data.Algorithm.Diff, Aura.Pkgbuilds
+- Aura.AuraLib split into Aura.General, Aura.Build, Aura.Dependencies
+- New secret option you don't get to find out about until 1.1
+
+## 1.0.5.0
+- Fixed bug where packages with `+` in their name couldn't be
+ searched or built.
+- `-As` now allows multi-word searches, as it always should have.
+- `pacman-color` integration is more complete.
+ Still does not read the color.conf directly.
+
+## 1.0.4.0
+- Added French translation. Thanks to Ma Jiehong!
+- Added Russian translation. Thanks to Kyrylo Silin!
+- Fixed bug where packages with dots in their name wouldn't build.
+
+## 1.0.3.2
+- Moved haskell dependencies out of `makedepends` field and into
+ `depends` field in PKGBUILD. Makedepends can usually be ignored
+ after building, but haskell packages are a pain to rebuild
+ and reregister at every build. It's more realistic to just keep
+ them installed. This is what other haskell packages in the AUR
+ do as well.
+- Fixed pacman-color issues.
+
+## 1.0.3.1
+- Added `--auradebug` option.
+
+## 1.0.3.0
+- Compatibility with AUR 2.0 added.
+- Portuguese translation added. Thanks to Henry "Ingvij" Kupty!
+- Support for `pacman-color` added. Run sudo with `-E` a la:
+ sudo -E aura -Ayu
+- Fixed backslash parsing bug in `Bash`.
+
+## 1.0.2.2
+- Fixed parsing bug in `Bash`.
+ If one package fell victim, a whole `-Au` session would fail.
+
+## 1.0.2.1
+- Added License info to source files.
+- Fixed virtual package recognition bug.
+- Altered version conflict error message.
+- Fixed bug in Bash parser that would occasionally break parsing.
+
+## 1.0.2.0
+- Bug fixes.
+- Extended the Bash parser. PKGBUILDs that had bash variables in their
+ dependency arrays will now be parsed correctly.
+
+## 1.0.1.0
+- German translation (use with --german).
+ Thanks to Lukas Niederbremer!
+- Spanish translation (use with --spanish)
+ Thanks to Alejandro Gómez!
+- Replaced regex-posix with regex-pcre.
+- `-As` now highlights properly.
+- Moved a number of modules to `Aura/`
+
+## 1.0.0.0
+- Fixed `-V` message in terminals other than urxvt.
+- New `haskell-ansi-terminal` library to do this.
+
+## 0.10.0.0
+- Internet access moved to Network.Curl library.
+- `Bash.hs` library created to help with PKGBUILD parsing.
+ Can currently handle string expansions a la::
+
+ "this-is-{awesome,neat}" => ["this-is-awesome","this-is-neat"]
+
+## 0.9.2.3
+- Dependency determining speed up.
+- Added AUR URL to `-Ai`.
+
+## 0.9.3.2
+- Swedish translation.
+ Thanks to Fredrik Haikarainen!
+
+## 0.9.2.0
+- `-Ai` and `-As`!
+
+## 0.9.1.0
+- `-Au` is about 20 times faster.
+
+## 0.9.?.?
+- Polish translation.
+ Thanks to Chris "Kwpolska" Warrick!
+- Croatian translation.
+ Thanks to Denis Kasak!
+
+## 0.9.0.0
+- New `-O` operation for dealing with orphan packages.
+- A man page!
+
+## 0.8.0.0
+- Help message now supports multiple languages.
+- Broke "no overlapping options" convention.
+- `-Cz` is now `-Cb`.
+- New option `-Ad`. Lists _all_ dependencies of an AUR package.
+ This is to aid pre-building research.
+ This option shows information you can't get from looking at PKGBUILDS!
+
+## 0.7.3.0
+- New option `--conf`. Lets you quickly view your pacman.conf.
+
+## 0.7.2.3
+- `--log` is now `-L`.
+- New option `-Ls`. Search the log file via a regex.
+- New option `-Li`. Reports information on a given package that has had
+ any appearance in the log file.
+
+## 0.7.0.0
+- `--hotedit` option added.
+- `Shell` library added.
+
+## 0.6.0.0
+- Aura passes proper exit codes to the shell upon completion.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..12fc7e0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,675 @@
+ 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>.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..57acbed
--- /dev/null
+++ b/README.md
@@ -0,0 +1,229 @@
+[![Build Status](https://travis-ci.org/aurapm/aura.svg?branch=master)](https://travis-ci.org/aurapm/aura)
+[![Join the chat at https://gitter.im/aurapm/aura](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/aurapm/aura?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+# What is Aura?
+Aura is a package manager for Arch Linux. It's main purpose is as an
+"AUR helper", in that it automates the process of installating packages
+from the Arch User Repositories. It is, however, capable of much more.
+
+# The Aura Philosophy
+
+### Aura is Pacman
+ Aura doesn't just mimic pacman... it _is_ pacman.
+ All pacman operations and their sub-options are allowed.
+ Some even hold special meaning in Aura as well.
+
+### Arch is Arch. AUR is AUR.
+ `-S` yields pacman packages and _only_ pacman packages. This agrees with
+ the above. Thus in Aura, the `-A` operation is introduced for obtaining
+ AUR packages. `-A` comes with sub-options you're used to (`-u`, `-s`,
+ `-i`, etc.).
+
+### All together
+Dependencies and packages are not built and installed one at a time.
+Install order is as follows:
+1. All pacman dependencies (all at once).
+2. All AUR dependencies (one at a time).
+3. All AUR packages (all at once).
+
+### Quiet Building
+ By default `makepkg` output is suppressed. If you want the people
+ behind you to think you're a badass hacker, then this suppression
+ can be disabled by using `-x` alongside `-A`.
+
+### Run as Root, Build as a User
+ `makepkg` gets very upset if you try to build a package as root.
+ That said, a built package can't be handed off to pacman and installed
+ if you _don't_ run as root. Other AUR helpers ignore this problem,
+ but Aura does not. Even when run with `sudo`, packages are built
+ with normal user privileges, then handed to pacman and installed as root.
+
+### Know your System
+ Editing PKGBUILDs mid-build is not default behaviour.
+ An Arch user should know _exactly_ what they're putting into their system,
+ thus research into prospective packages should be done beforehand.
+ However, for functionality's sake, the option `--hotedit` used with `-A`
+ will prompt the user for PKGBUILD editing. Regardless, as a responsible
+ user you must know what you are building.
+
+### Downgradibility
+ Built AUR package files are moved to the package cache.
+ This allows for them to be easily downgraded when problems arise.
+ Other top AUR helper programs do not do this.
+ The option `-B` will save a package state, and `-Br` will restore
+ a state you select. `-Au` also automatically invokes a save,
+ to help you roll back from problematic updates.
+
+### No Orphans
+ Sometimes dependencies lose their *required* status, but remain
+ installed on your system. Sometimes AUR package "makedepends"
+ aren't required at all after install. Packages like this just
+ sit there, receiving upgrades for no reason.
+ Aura helps keep track of and remove packages like this.
+ Learn more [here](#working-with-orphan-packages).
+
+### Arch Linux for Everyone
+ English is well established as the world's Lingua Franca, and is also
+ the dominant language of computing and the internet. That said, it's
+ natural that some people are going to be more comfortable working
+ in their native language. From the beginning Aura has been built with
+ multiple-language support in mind, making it very easy to add new ones.
+
+### Haskell
+ Aura is written in Haskell, which means easy development and pretty code.
+ Please feel free to use it as a simple Haskell reference.
+ Aura code demonstrates:
+ * Parser Combinators (`megaparsec`)
+ * CLI Flag Handling (`optparse-applicative`)
+ * Extensible Effects (`freer-simple`)
+ * Concurrency (`async`)
+ * Shell Interaction (`shelly`)
+
+# Installation
+
+## Arch Linux
+
+It is recommended to install the prebuilt binary of Aura:
+
+```bash
+git clone https://aur.archlinux.org/aura-bin.git
+cd aura-bin
+makepkg
+sudo pacman -U <the-package-file-that-makepkg-produces>
+```
+
+## Manual
+
+You will need the [stack tool](https://docs.haskellstack.org/en/stable/README/) for Haskell
+to compile Aura yourself. Then:
+
+```bash
+git clone https://github.com/aurapm/aura.git
+cd aura
+stack install -- aura
+```
+
+This may take a while to initially build all of Aura's dependencies. Once complete,
+your `aura` binary will be available in `~/.local/bin/`.
+
+Sample Usage
+============
+
+#### Installing Packages
+Install an AUR package:
+
+ aura -A (package)
+
+Author's favourite (upgrades, removes make deps, shows PKGBUILD diffs):
+
+ aura -Akuax
+
+Just upgrade all installed AUR packages:
+
+ aura -Au
+
+Look up information on an AUR package:
+
+ aura -Ai (package)
+
+Search the AUR via a regex:
+
+ aura -As (regex)
+
+Display an AUR package's PKGBUILD:
+
+ aura -Ap (package)
+
+Display an AUR package's dependencies (and those deps' deps too):
+
+ aura -Ad (package)
+
+Install with makepkg output unsuppressed:
+
+ aura -Ax (package)
+
+Install and remove make dependencies afterwards:
+
+ aura -Aa (package)
+
+Install and show PKGBUILD differences:
+
+ aura -Ak (package)
+
+#### Working with Package Records
+Store a record of all installed packages:
+
+ aura -B
+
+Restore a saved record. Rolls back, uninstalls, and reinstalls packages as necessary:
+
+ aura -Br
+
+#### Working with the Package Cache
+Downgrade a package (this is interactive):
+
+ aura -C (package)
+
+Search the package cache for package files via a regex:
+
+ aura -Cs (regex)
+
+Backup the package cache:
+
+ aura -Cb (/path/to/backup/location/)
+
+Reduce the package cache to contain only 'x' of each package file:
+
+ aura -Cc x
+
+#### Working with the Pacman Log
+View the Pacman Log:
+
+ aura -L
+
+Display install / upgrade history for a package:
+
+ aura -Li (package)
+
+Search the pacman logfile via a regex:
+
+ aura -Ls (regex)
+
+#### Working with Orphan Packages
+Display orphan packages:
+
+ aura -O
+
+Adopt an orphan package:
+
+ aura -O --adopt (package)
+
+Uninstall all orphan packages:
+
+ aura -Oj
+
+More information is available in aura's manpage.
+
+# Localisation
+As mentioned in the Philosophy above, adding new languages to Aura is
+quite easy. If you speak a language other than those available and
+would like it added to Aura, please consult **LOCALISATION.md**.
+
+Aura is currently translated by these generous people:
+
+| Language | Translators |
+|------------|-------------------------------------------------|
+| Chinese | Kai Zhang |
+| Croatian | Denis Kasak and "stranac" |
+| French | Ma Jiehong and Fabien Dubosson |
+| German | Lukas Niederbremer and Jonas Platte |
+| Indonesian | "pak tua Greg" |
+| Italian | Bob Valantin |
+| Japanese | Colin Woodbury and Onoue Takuro |
+| Norwegian | "chinatsun" |
+| Polish | Chris Warrick |
+| Portuguese | Henry Kupty, Thiago Perrotta, and Wagner Amaral |
+| Russian | Kyrylo Silin, Alexey Kotlyarov |
+| Serbian | Filip Brcic |
+| Spanish | Alejandro Gómez and Sergio Conde |
+| Swedish | Fredrik Haikarainen and Daniel Beecham |
diff --git a/aura.cabal b/aura.cabal
new file mode 100644
index 0000000..1d428f8
--- /dev/null
+++ b/aura.cabal
@@ -0,0 +1,167 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.30.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 30037d712654f7ee468436f48905a4a11e991740c708a85dc56a37bf656e0f38
+
+name: aura
+version: 2.0.0
+synopsis: A secure package manager for Arch Linux and the AUR, written in Haskell.
+description: aura is a package manager for Arch Linux written in Haskell. It connects to both the official Arch repostitories and to the AUR, allowing easy control of all packages on an Arch system. It allows /all/ pacman operations and provides /new/ custom ones for dealing with AUR packages. This differs from some other AUR package managers.
+category: System
+homepage: https://github.com/aurapm/aura
+author: Colin Woodbury
+maintainer: colin@fosskers.ca
+license: GPL-3
+license-file: LICENSE
+build-type: Simple
+extra-source-files:
+ README.md
+ CHANGELOG.md
+ doc/aura.8
+ doc/completions/bashcompletion.sh
+ doc/completions/_aura
+
+library
+ exposed-modules:
+ Aura.Build
+ Aura.Cache
+ Aura.Colour
+ Aura.Commands.A
+ Aura.Commands.B
+ Aura.Commands.C
+ Aura.Commands.L
+ Aura.Commands.O
+ Aura.Core
+ Aura.Dependencies
+ Aura.Diff
+ Aura.Install
+ Aura.Languages
+ Aura.Languages.Fields
+ Aura.Logo
+ Aura.MakePkg
+ Aura.Packages.AUR
+ Aura.Packages.Repository
+ Aura.Pacman
+ Aura.Pkgbuild.Base
+ Aura.Pkgbuild.Editing
+ Aura.Pkgbuild.Fetch
+ Aura.Pkgbuild.Records
+ Aura.Pkgbuild.Security
+ Aura.Settings
+ Aura.State
+ Aura.Types
+ Aura.Utils
+ hs-source-dirs:
+ lib
+ default-extensions: NoImplicitPrelude
+ ghc-options: -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-binds -Wunused-imports -Wunused-matches
+ build-depends:
+ aeson >=1.2 && <1.5
+ , aeson-pretty >=0.8 && <0.9
+ , algebraic-graphs >=0.1 && <0.2
+ , array >=0.5 && <0.6
+ , async >=2.1 && <2.3
+ , aur >=6.0.1 && <7
+ , base >=4.8 && <4.12
+ , base-prelude >=1.2 && <1.4
+ , bytestring
+ , compactable >=0.1 && <0.2
+ , containers
+ , directory
+ , errors >=2.3 && <2.4
+ , filepath
+ , freer-simple >=1.1 && <1.2
+ , generic-lens >=1.0.0.1 && <1.1
+ , http-client >=0.5 && <0.6
+ , http-types >=0.9 && <0.13
+ , language-bash >=0.8 && <0.9
+ , megaparsec >=6.4 && <6.6
+ , microlens >=0.4 && <0.5
+ , microlens-ghc >=0.4 && <0.5
+ , mtl >=2.2 && <2.3
+ , mwc-random >=0.14 && <0.15
+ , network-uri >=2.6 && <2.7
+ , non-empty-containers >=0.1 && <0.2
+ , paths >=0.2 && <0.3
+ , pretty-simple >=2.1 && <2.2
+ , prettyprinter >=1.2 && <1.3
+ , prettyprinter-ansi-terminal >=1.1 && <1.2
+ , semigroupoids >=5.2 && <5.4
+ , stm
+ , text >=1.2 && <1.3
+ , throttled >=1.1 && <1.2
+ , time
+ , transformers
+ , typed-process >=0.2 && <0.3
+ , versions >=3.4 && <3.5
+ , witherable >=0.2 && <0.3
+ default-language: Haskell2010
+
+executable aura
+ main-is: aura.hs
+ other-modules:
+ Flags
+ Settings
+ Paths_aura
+ hs-source-dirs:
+ exec
+ default-extensions: NoImplicitPrelude
+ ghc-options: -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-binds -Wunused-imports -Wunused-matches -threaded -O2 -with-rtsopts=-N
+ build-depends:
+ aura
+ , base >=4.8 && <4.12
+ , base-prelude >=1.2 && <1.4
+ , bytestring
+ , containers
+ , errors >=2.3 && <2.4
+ , freer-simple >=1.1 && <1.2
+ , http-client >=0.5 && <0.6
+ , http-client-tls >=0.3 && <0.4
+ , language-bash >=0.8 && <0.9
+ , microlens >=0.4 && <0.5
+ , non-empty-containers >=0.1 && <0.2
+ , optparse-applicative >=0.14 && <0.15
+ , paths >=0.2 && <0.3
+ , pretty-simple >=2.1 && <2.2
+ , prettyprinter >=1.2 && <1.3
+ , prettyprinter-ansi-terminal >=1.1 && <1.2
+ , text >=1.2 && <1.3
+ , transformers
+ , typed-process >=0.2 && <0.3
+ , versions >=3.4 && <3.5
+ default-language: Haskell2010
+
+test-suite aura-test
+ type: exitcode-stdio-1.0
+ main-is: Test.hs
+ hs-source-dirs:
+ test
+ default-extensions: NoImplicitPrelude
+ ghc-options: -Widentities -Wincomplete-patterns -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wname-shadowing -Wpartial-fields -Wredundant-constraints -Wunused-binds -Wunused-imports -Wunused-matches -threaded -with-rtsopts=-N
+ build-depends:
+ aura
+ , base >=4.8 && <4.12
+ , base-prelude >=1.2 && <1.4
+ , bytestring
+ , containers
+ , errors >=2.3 && <2.4
+ , freer-simple >=1.1 && <1.2
+ , http-client >=0.5 && <0.6
+ , language-bash >=0.8 && <0.9
+ , megaparsec >=6.4 && <6.6
+ , microlens >=0.4 && <0.5
+ , non-empty-containers >=0.1 && <0.2
+ , paths >=0.2 && <0.3
+ , pretty-simple >=2.1 && <2.2
+ , prettyprinter >=1.2 && <1.3
+ , prettyprinter-ansi-terminal >=1.1 && <1.2
+ , tasty >=0.11 && <2.0
+ , tasty-hunit >=0.9 && <0.11
+ , text >=1.2 && <1.3
+ , transformers
+ , typed-process >=0.2 && <0.3
+ , versions >=3.4 && <3.5
+ default-language: Haskell2010
diff --git a/doc/aura.8 b/doc/aura.8
new file mode 100644
index 0000000..14589f8
--- /dev/null
+++ b/doc/aura.8
@@ -0,0 +1,417 @@
+.\" Man page for `aura`
+.\" Written by Colin Woodbury <colin@fosskers.ca>
+
+.TH aura 8 "June 2018" "Aura" "Aura Manual"
+
+.\" Disable hyphenation.
+.nh
+
+.SH NAME
+aura \- A package manager for Arch Linux and the AUR.
+
+.SH SYNOPSIS
+\fIaura\fR <operation> [options] [package(s)]
+
+.SH DESCRIPTION
+.P
+\fIaura\fR is a secure, multilingual package manager for Arch Linux written in
+Haskell.
+It connects to both the official Arch repositories and to the Arch User
+Repositories (AUR), allowing easy control of all packages on an Arch system.
+Aura can also be used to build Arch Build System (ABS) packages from source.
+Aura allows \fIall\fR pacman operations and provides \fInew\fR custom ones for
+dealing with AUR and ABS packages.
+
+.SH OPERATIONS
+.P
+\fB\-A\fR, \-\-aursync [package(s)]
+.RS 4
+Perform actions involving the [A]UR. Default action installs packages from the
+AUR. During building, makepkg output is \fInot\fR shown by default. After
+building, the built \fI.pkg.tar.xz\fR file is moved to the package cache and
+installed from there. This allows for easy AUR package downgrading.
+.RE
+.P
+\fB\-B\fR, \-\-save
+.RS 4
+Manage the saving and restoring of the global package state. Default action
+stores a record of all currently installed packages and their versions.
+.RE
+.P
+\fB\-C\fR, \-\-downgrade [package(s)]
+.RS 4
+Perform actions involving the package [C]ache. Default action downgrades
+specified packages. This process is interactive, allowing the user to choose
+from any previous version they have available in the package cache.
+.RE
+.P
+\fB\-L\fR, \-\-viewlog
+.RS 4
+Perform actions involving the pacman [L]ogfile.
+Default action opens the log for read-only viewing.
+.RE
+.P
+\fB\-O\fR, \-\-orphans [package(s)]
+.RS 4
+Perform actions involving [O]rphan packages. Orphan packages are packages
+installed as dependencies, but are no longer required by any other package.
+Default action is to list the current orphans.
+.RE
+
+.SH AUR SYNC OPTIONS (\fI\-A\fR)
+.P
+\fB\-a\fR, \-\-delmakedeps
+.RS 4
+Uninstalls build dependencies that are no longer required after installing the
+main package. This prevents the creation of orphan packages. Also note that
+while the package itself will be uninstalled, its package file will remain in
+the cache.
+.RE
+.P
+\fB\-d\fR, \-\-deps <package(s)>
+.RS 4
+View all the dependencies of a package. This process is recursive for AUR
+packages, so all dependencies of dependencies (and so on) will also be shown.
+This can aid the pre-install package research process.
+.RE
+.P
+\fB\-i\fR, \-\-info <package(s)>
+.RS 4
+View information about a package in the AUR.
+.RE
+.P
+\fB\-k\fR, \-\-diff
+.RS 4
+Shows PKGBUILD diffs. When upgrading, using this option will compare the most
+and second-most recent PKGBUILDs and output changes in colour.
+.RE
+.P
+\fB\-p\fR, \-\-pkgbuild <package(s)>
+.RS 4
+Outputs an AUR package's PKGBUILD. Use this before installing new packages to
+confirm that the build scripts aren't doing anything fishy.
+.RE
+.P
+\fB\-q\fR, \-\-quiet
+.RS 4
+Display less information about certain aursync operations (this is useful when
+processing Aura's output in a script). In particular, search and view
+dependencies will only show uncoloured package names.
+.RE
+.P
+\fB\-s\fR, \-\-search <word>
+.RS 4
+Search the AUR via a word only (no regex). Results are sorted by vote.
+Suboptions:
+.RS 4
+\fI\-\-abc\fR : Sorts results alphabetically.
+.P
+\fI\-\-head\=n\fR : Only show the first n results. Default n = 10
+.P
+\fI\-\-tail\=n\fR : Only show the last n results. Default n = 10
+.RE
+.RE
+.P
+\fB\-u\fR, \-\-sysupgrade
+.RS 4
+Upgrade all installed AUR packages. \fI\-Au\fR is \fI\-Su\fR for AUR packages.
+.RE
+.P
+\fB\-x\fR, \-\-unsuppress
+.RS 4
+Unsuppress \fImakepkg\fR output during building. By default this output is
+suppressed for a more silent install. Note that when this option isn't used,
+\fImakepkg\fR output is actually collected and printed if any errors occur.
+.RE
+.P
+\fB\-\-json <package(s)>
+.RS 4
+Query the AUR RPC for package info as raw JSON. Good for debugging.
+.RE
+.P
+\fB\-\-build\fR=/path/
+.RS 4
+Specify build path when building AUR packages. Only the \fIfull\fR path of a
+pre-existing directory can be used. Example:
+.RS 6
+"aura -A foo --build=/full/path/to/build/location/"
+.RE
+.RE
+.P
+\fB\-\-builduser\fR=username
+.RS 4
+Specify the user to build packages as. This can be useful when logged in as
+root and a build user is available.
+.RE
+.P
+\fB\-\-custom\fR
+.RS 4
+Run \fIcustomizepkg\fR on packages before building. Note that you need
+customizepkg installed and set up correctly for this to work.
+.RE
+.P
+\fB\-\-devel\fR
+.RS 4
+When ran with \fI\-Au\fR, adds all development packages to the queue of
+packages to upgrade. Devel packages are those pulled directly from online
+repositories, via git / mercurial / etc.
+.RE
+.P
+\fB\-\-dryrun\fR
+.RS 4
+When ran with \fI\-A\fR or \fI\-Au\fR, update checks, PKGBUILD diffs, and
+dependency checks will be performed, but nothing will be built. Also usable
+with \fI\-M\fR.
+.RE
+.P
+\fB\-\-hotedit\fR
+.RS 4
+Prompt the user immediately before dependency checks to ask if they wish to
+view/edit the PKGBUILD.
+This is not default behavior and thus does not have a single\-letter option.
+Research into packages (and by extension, their PKGBUILDs) should be done
+before any building occurs. Please use \fI\-Ap\fR and \fI\-Ad\fR for this, as
+they will be much faster at presenting information than searching the AUR
+website manually.
+Note that, since aura is run through sudo, your local value of $EDITOR may not
+be preserved. Run as "sudo \fI\-E\fR aura -A --hotedit ..." to preserve your
+environment. To always allow environment variables to be passed, have a look at
+the \fIenv_reset\fR and \fIenv_keep\fR options in \fBsudoers\fR(5).
+.RE
+
+.SH GLOBAL PACKAGE STATE OPTIONS (\fI\-B\fR)
+.P
+\fB\-c\fR, \-\-clean <states-to-retain>
+.RS 4
+Saves a given number of the most recently saved package states and removes the
+rest.
+.RE
+.P
+\fB\-r\fR, \-\-restore\fR
+.RS 4
+Restores a record kept with \fI\-B\fR. Attempts to downgrade any packages that
+were upgraded since the chosen save. Will remove any that weren't installed at
+the time.
+.RE
+
+.SH DOWNGRADE OPTIONS (\fI\-C\fR)
+.P
+\fB\-b\fR, \-\-backup\fR <path>
+.RS 4
+Backup the package cache to a given directory. The given directory must already
+exist. During copying, progress will be shown. If the copy takes too long, you
+may want to reduce the number of older versions of each package by using
+\fI\-Cc\fR.
+.RE
+.P
+\fB\-c\fR, \-\-clean <versions-to-retain>
+.RS 4
+Saves a given number of package versions for each package and deletes the rest
+from the package cache. Count is made from the most recent version, so using:
+.RS 4
+aura -Cc 3
+.RE
+would save the three most recent versions of each package file.
+Giving the number 0 as an argument is identical to \fI\-Scc\fR.
+.RE
+.P
+\fB\-\-notsaved
+.RS 4
+Remove only those package files which are not saved in a package record (a la \fI\-B\fR).
+.RE
+.P
+\fB\-s\fR, \-\-search <regex>
+.RS 4
+Search the package cache via a regex. Any package name that matches the regex
+will be output as\-is.
+.RE
+
+.SH LOGFILE OPTIONS (\fI\-L\fR)
+.P
+\fB\-i\fR, \-\-info <package(s)>
+.RS 4
+Displays install / upgrade history for a given package. Under the `Recent
+Actions` section, only the last five entries will be displayed. If there are
+less than five actions ever performed with the package, what is available will
+be printed.
+.RE
+.P
+\fB\-s\fR, \-\-search <regex>
+.RS 4
+Search the pacman log file via a regex. Useful for singling out any and all
+actions performed on a package.
+.RE
+
+.SH ORPHAN PACKAGE OPTIONS (\fI\-O\fR)
+.P
+\fB\-\-adopt <package(s)>
+.RS 4
+Mark a package as being explicitly installed (i.e. it's not a dependency).
+.RE
+.P
+\fB\-j\fR, \-\-abandon
+.RS 4
+Uninstall all orphan packages.
+.RE
+
+.SH PACMAN / AURA DUAL FUNCTIONALITY OPTIONS
+.P
+\-\-noconfirm
+.RS 4
+Never ask for any Aura or Pacman confirmation. Any time a prompt would
+appear, say before building or installation, it is assumed the user
+answered in whatever way would progress the program.
+.RE
+.P
+\-\-needed
+.RS 4
+Don't rebuild/reinstall packages that are already up to date.
+.RE
+.P
+\-\-debug
+.RS 4
+View some handy debugging information.
+.RE
+
+.SH EXPOSED MAKEPKG OPTIONS
+.P
+\-\-ignorearch
+.RS 4
+Ignores processor architecture when building packages.
+.RE
+.P
+\-\-allsource
+.RS 4
+Creates a \fI.src\fR file containing all the downloaded sources (code, etc)
+and stores it at \fI/var/cache/aura/src/\fR.
+.RE
+
+.SH LANGUAGE OPTIONS
+.P
+Aura is available in multiple languages. As options, they can be used with
+either their English names or their real names written in their native
+characters. The available languages are, in option form:
+.P
+\-\-english (default)
+.P
+\-\-japanese, \-\-日本語
+.P
+\-\-polish, \-\-polski
+.P
+\-\-croatian, \-\-hrvatski
+.P
+\-\-swedish, \-\-svenska
+.P
+\-\-german, \-\-deutsch
+.P
+\-\-spanish, \-\-español
+.P
+\-\-portuguese, \-\-português
+.P
+\-\-french, \-\-français
+.P
+\-\-russian, \-\-русский
+.P
+\-\-italian, \-\-italiano
+.P
+\-\-serbian, \-\-српски
+.P
+\-\-norwegian, \-\-norsk
+
+.SH PRO TIPS
+.P
+1. If you build a package and then choose not to install it, the built package
+file will still be moved to the cache. You can then install it whenever you
+want with \fI\-C\fR.
+.P
+2. Research packages using \fI\-Ad\fR, \fI\-Ai\fR, and \fI\-Ap\fR!
+.P
+3. When upgrading, use \fI\-Akua\fR instead of just \fI\-Au\fR. This will
+remove make deps, as well as show PKGBUILD diffs before building.
+.P
+4. If you want to search both the Repos and the AUR at the same time, you can
+use the following shell functions:
+.RS 4
+Bash => function search() {
+ aura -Ss $1 && aura -As $1
+ }
+
+Fish => function search
+ aura -Ss $argv
+ aura -As $argv
+ end
+.RE
+
+.SH SEE ALSO
+.P
+\fBpacman\fR(8), \fBpacman.conf\fR(5), \fBmakepkg\fR(8)
+
+.SH BUGS
+.P
+It is not recommended to install non-ABS, non-AUR packages with pacman or aura.
+Aura will assume they are AUR packages during a `-Au` and attempt to upgrade
+them. If a name collision occurs (that is, if there is a legitimate AUR package
+with the same name as the one you installed) previous installations could be
+overwritten.
+
+.SH AUTHOR
+.P
+Colin Woodbury <colin@fosskers.ca>
+
+.SH CONTRIBUTORS
+.P
+Chris Warrick
+.P
+Brayden Banks
+.P
+Denis Kasak
+.P
+Edwin Marshall
+.P
+Jimmy Brisson
+.P
+Kyle Raftogianis
+.P
+Nicholas Clarke
+
+.SH TRANSLATORS
+.P
+( Polish ) Chris "Kwpolska" Warrick
+.P
+( Croatian ) Denis Kasak
+.P
+( Croatian ) "stranac"
+.P
+( Swedish ) Fredrik Haikarainen
+.P
+( Swedish ) Daniel Beecham
+.P
+( German ) Lukas Niederbremer
+.P
+( Spanish ) Alejandro Gómez
+.P
+( Portuguese ) Henry "Ingvij" Kupty
+.P
+( Portuguese ) Thiago "thiagowfx" Perrotta
+.P
+( Portuguese ) Wagner Amaral
+.P
+( French ) Ma Jiehong
+.P
+( French ) Fabien Dubosson
+.P
+( Russian ) Kyrylo Silin
+.P
+( Russian ) Alexey Kotlyarov
+.P
+( Italian ) Bob Valantin
+.P
+( Serbian ) Filip Brcic
+.P
+( Norwegian ) "chinatsun"
+.P
+( Indonesian ) "pak tua Greg"
+.P
+( Chinese ) Kai Zhang
+.P
+( Japanese ) Onoue Takuro
diff --git a/doc/completions/_aura b/doc/completions/_aura
new file mode 100644
index 0000000..27082d9
--- /dev/null
+++ b/doc/completions/_aura
@@ -0,0 +1,591 @@
+#compdef aura
+
+# Zsh completion file for Aura, based on pacman's
+# copy this file to /usr/share/site-functions/_aura
+
+typeset -A opt_args
+setopt extendedglob
+
+# options for passing to _arguments: main aura commands
+_aura_opts_commands=(
+ {-A,--aursync}'[Install and manage AUR packages]'
+ {-B,--save}'[Manage the global package state]'
+ {-C,--downgrade}'[Manage the package cache]'
+ {-L,--viewlog}'[Analyse the pacman log file]'
+ {-M,--abssync}'[Build and install from the ABS tree]'
+ {-O,--orphans}'[Manage orphan packages]'
+ {-Q,--query}'[Query the package database]'
+ {-R,--remove}'[Remove a packags]'
+ {-S,--sync}'[Synchronize repo packages]'
+ {-U,--upgrade}'[Upgrade packages]'
+ {-V,--version}'[Display version information]'
+ '--viewconf[View the pacman config file read-only]'
+ '--languages[Display available output languages]'
+ '(-h --help)'{-h,--help}'[Help message]'
+)
+
+# options for passing to _arguments: options common to all commands
+_aura_opts_common=(
+ {-b,--dbpath}'[Alternate database location]:database_location:_files -/'
+ '--color[colorize the output]:color options:(always never auto)'
+ {-h,--help}'[Display syntax for the given operation]'
+ {-r,--root}'[Set alternate installation root]:installation root:_files -/'
+ {-v,--verbose}'[Be more verbose]'
+ '--cachedir[Alternate package cache location]:cache_location:_files -/'
+ '--config[An alternate configuration file]:config file:_files'
+ '--debug[Display debug messages]'
+ '--gpgdir[Set an alternate home directory for GnuPG]:_files -/'
+ '--logfile[An alternate log file]:config file:_files'
+ '--noconfirm[Do not ask for confirmation]'
+ '--no-pp[Do not use powerpill]'
+ '--noprogressbar[Do not show a progress bar when downloading files]'
+ '--noscriptlet[Do not execute the install scriptlet if one exists]'
+ '--print[Only print the targets instead of performing the operation]'
+)
+
+# options for passing to _arguments: options for --upgrade commands
+_aura_opts_pkgfile=(
+ '*-d[Skip dependency checks]'
+ '*--nodeps[Skip dependency checks]'
+ '--dbonly[Only remove database entry, do not remove files]'
+ '--force[Overwrite conflicting files]'
+ '--needed[Do not reinstall up to date packages]'
+ '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
+)
+
+# options for passing to _arguments: subactions for --query command
+_aura_opts_query_actions=(
+ '(-Q --query)'{-Q,--query}
+ {-g,--groups}'[View all members of a package group]:*:package groups:->query_group'
+ {-o,--owns}'[Query the package that owns a file]:file:_files'
+ {-p,--file}'[Package file to query]:*:package file:->query_file'
+ {-s,--search}'[Search package names and descriptions]:*:search text:->query_search'
+)
+
+# options for passing to _arguments: options for --query and subcommands
+_aura_opts_query_modifiers=(
+ {-c,--changelog}'[List package changelog]'
+ {-d,--deps}'[List packages installed as dependencies]'
+ {-e,--explicit}'[List packages explicitly installed]'
+ {\*-i,\*--info}'-i[View package information]'
+ #'-ii[View package information including backup files]'
+ {\*-k,\*--check}'[Check package files]'
+ {-l,--list}'-l[List package contents]'
+ {-m,--foreign}'-m[List installed packages not found in sync db(s)]'
+ {-n,--native}'[List installed packages found in sync db(s)]'
+ {-q,--quiet}'[Display less information for certain operations.]'
+ {-t,--unrequired}'[List packages not required by any package]'
+ {-u,--upgrades}'[List packages that can be upgraded]'
+)
+
+# options for passing to _arguments: options for --remove command
+_aura_opts_remove=(
+ {-c,--cascade}'[Remove all dependent packages]'
+ {*-d,*--nodeps}'[Skip dependency checks]'
+ {-n,--nosave}'[Remove protected configuration files]'
+ {\*-s,\*--recursive}'[Remove dependencies not required by other packages]'
+ '--dbonly[Only remove database entry, do not remove files]'
+ '*:installed package:_aura_completions_installed_packages'
+)
+
+# options for passing to _arguments: options for commands dealing with the database
+_aura_opts_database=(
+ '--asdeps[mark packages as non-explicitly installed]'
+ '--asexplicit[mark packages as explicitly installed]'
+ '*:installed package:_aura_completions_installed_packages'
+)
+
+# options for passing to _arguments: options for --sync command
+_aura_opts_sync_actions=(
+ '(-S --sync)'{-S,--sync}
+ {\*-c,\*--clean}'[Remove old packages from cache]:\*:clean:->sync_clean'
+ #'*-cc[Remove all packages from cache]:*:clean:->sync_clean'
+ {-g,--groups}'[View all members of a package group]:*:package groups:->sync_group'
+ {-s,--search}'[Search package names and descriptions]:*:search text:->sync_search'
+ '--dbonly[Only remove database entry, do not remove files]'
+ '--needed[Do not reinstall up to date packages]'
+ '--recursive[Reinstall all dependencies of target packages]'
+)
+
+# options for passing to _arguments: options for --sync command
+_aura_opts_sync_modifiers=(
+ {\*-d,\*--nodeps}'[Skip dependency checks]'
+ {\*-i,\*--info}'[View package information]'
+ {-l,--list}'[List all packages in a repository]'
+ {-p,--print}'[Print download URIs for each package to be installed]'
+ {\*-u,\*--sysupgrade}'[Upgrade all out-of-date packages]'
+ {-w,--downloadonly}'[Download packages only]'
+ {\*-y,\*--refresh}'[Download fresh package databases]'
+ '*--ignore[Ignore a package upgrade]:package: _aura_completions_all_packages'
+ '*--ignoregroup[Ignore a group upgrade]:package group:_aura_completions_all_groups'
+ '--asdeps[Install packages as non-explicitly installed]'
+ '--asexplicit[Install packages as explicitly installed]'
+ '--force[Overwrite conflicting files]'
+
+ #'-q[Display less information for certain operations.]'
+)
+
+# options for passing to _arguments: options for --aursync command
+_aura_opts_aursync_actions=(
+ '(-A --aursync)'{-A,--aursync}
+ {-p,--pkgbuild}'[Display an AUR package PKGBUILD]'
+ {-s,--search}'[Search AUR package names and descriptions]'
+ {-w,--downloadonly}'[Download an AUR package source tarball]'
+)
+
+# options for passing to _arguments: options for --aursync command
+_aura_opts_aursync_modifiers=(
+ {-a,--delmakedeps}'[Uninstall unneeded build deps after installation]'
+ {-d,--deps}'[Display package dependencies. Recursive for AUR packages]'
+ {\*-i,\*--info}'[Display AUR package information]'
+ {-k,--diff}'[Show PKGBUILD diffs when upgrading]'
+ {-u,--sysupgrade}'[Upgrade all installed AUR packages]'
+ {-x,--unsuppress}'[Unsuppress makepkg output during building]'
+ '*--aurignore[Ignore AUR packages when installing]:package: _aura_completions_aur_packages'
+ '--build[Build packages in specified path]'
+ '--builduser[Use specified user to build packages]'
+ '--custom[Run customizepkg]'
+ {--dev,--devel}'[With -Au, also upgrades all development packages]'
+ "--dryrun[Perform checks, but don't build]"
+ '--hotedit[Prompt user to edit PKGBUILD before dep checks]'
+ '--ignorearch[makepkg will ignore processor architecture]'
+)
+
+# options for passing to _arguments: options for --save commands
+_aura_opts_save=(
+ {-c,--clean}'[Given n, save n recent package states and remove the rest]'
+ {-r,--restore}'[Restore a previously saved package state]'
+)
+
+# options for passing to _arguments: options for --downgrade commands
+_aura_opts_downgrade=(
+ {-b,--backup}'[Backup the cache to a given directory]'
+ {-c,--clean}'[Given n, save n versions of each package file]'
+ {-s,--search}'[Search the cache via a regex]'
+ '*:installed package:_aura_completions_installed_packages'
+)
+
+# options for passing to _arguments: options for --viewlog commands
+typeset -a _aura_opts_viewlog
+_aura_opts_viewlog=(
+ '-i[View package installation history.]'
+ '-s[Search log file via a regex.]'
+)
+
+# options for passing to _arguments: options for --orphans commands
+typeset -a _aura_opts_orphans
+_aura_opts_orphans=( '-j[Uninstall all orphan packages.]' )
+
+# handles --help subcommand
+_aura_action_help() {
+ _arguments -s : \
+ "$_aura_opts_commands[@]"
+}
+
+# handles cases where no subcommand has yet been given
+_aura_action_none() {
+ _arguments -s : \
+ "$_aura_opts_commands[@]"
+}
+
+# handles --query subcommand
+_aura_action_query() {
+ local context state line
+ typeset -A opt_args
+
+ case $state in
+ query_file)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
+ ;;
+ query_group)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:groups:_aura_completions_installed_groups'
+ ;;
+ query_owner)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:file:_files'
+ ;;
+ query_search)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:search text: '
+ ;;
+ *)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_actions[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:package:_aura_completions_installed_packages'
+ ;;
+ esac
+}
+
+# handles --remove subcommand
+_aura_action_remove() {
+ _arguments -s : \
+ '(--remove -R)'{-R,--remove} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_remove[@]"
+}
+
+# handles --database subcommand
+_aura_actions_database() {
+ _arguments -s : \
+ '(--database -D)'{-D,--database} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_database[@]"
+}
+
+# handles --deptest subcommand
+_aura_action_deptest() {
+ _arguments -s : \
+ '(--deptest)-T' \
+ "$_aura_opts_aura_common[@]" \
+ ":packages:_aura_all_packages"
+}
+
+# handles --sync subcommand
+_aura_action_sync() {
+ local context state line
+ typeset -A opt_args
+
+ case $state in
+ sync_clean)
+ _arguments -s : \
+ {\*-c,\*--clean}'[Remove old packages from cache]'
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ ;;
+ sync_group)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '(-g --group)'{-g,--groups} \
+ '*:package group:_aura_completions_all_groups'
+ ;;
+ sync_search)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '*:search text: '
+ ;;
+ *)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_actions[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '*:package:_aura_completions_repo_packages'
+ ;;
+ esac
+}
+
+# handles --upgrade subcommand
+_aura_action_upgrade() {
+ _arguments -s : \
+ '(-U --upgrade)'{-U,--upgrade} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_pkgfile[@]"
+}
+
+# handles --version subcommand
+_aura_action_version() {
+ # no further arguments
+ return 0
+}
+
+# provides completions for package groups
+_aura_completions_all_groups() {
+ local -a cmd groups
+ _aura_get_command
+ groups=( $(_call_program groups $cmd[@] -Sg) )
+ typeset -U groups
+ compadd "$@" -a groups
+}
+
+# provides completions for packages available from repositories
+# these can be specified as either 'package' or 'repository/package'
+_aura_completions_repo_packages() {
+ local -a cmd packages repositories packages_long
+ _aura_get_command
+
+ if compset -P '*/'; then
+ packages=( $(_call_program packages $cmd[@] -Sql ${words[CURRENT]%/*}) )
+ typeset -U packages
+ _wanted repo_packages expl "repository/package" compadd ${(@)packages}
+ else
+ packages=( $(_call_program packages $cmd[@] -Sql) )
+ typeset -U packages
+ _wanted packages expl "packages" compadd - "${(@)packages}"
+
+ repositories=( aur ${(o)${${${(M)${(f)"$(</etc/pacman.conf)"}:#\[*}/\[/}/\]/}:#options} )
+ typeset -U repositories
+ _wanted repo_packages expl "repository/package" compadd -S "/" $repositories
+ fi
+}
+
+# provides completions for packages in the AUR
+_aura_completions_aur_packages() {
+ local -a cmd packages repositories packages_long
+ _aura_get_command
+
+ packages=()
+ if [[ -d /var/aur ]]; then
+ packages=( $(ls /var/aur) )
+ fi
+ typeset -U packages
+ _wanted aur_packages expl "packages" compadd - "${(@)packages}"
+}
+
+# provides completions for packages available from repositories and the AUR
+# these can be specied as either 'package' or 'repository/package'
+_aura_completions_all_packages() {
+ _alternative : \
+ 'aurpkgs:aur packages:_aura_completions_aur_packages' \
+ 'repopkgs:repository packages:_aura_completions_repo_packages'
+}
+
+_aura_all_packages() {
+ _alternative : \
+ 'localpkgs:local packages:_aura_completions_installed_packages' \
+ 'aurpkgs:aur packages:_aura_completions_aur_packages' \
+ 'repopkgs:repository packages:_aura_completions_repo_packages'
+}
+
+# provides completions for package groups
+_aura_completions_installed_groups() {
+ local -a cmd groups
+ _aura_get_command
+ groups=(${(o)${(f)"$(_call_program groups $cmd[@] -Qg)"}% *})
+ typeset -U groups
+ compadd "$@" -a groups
+}
+
+# provides completions for installed packages
+_aura_completions_installed_packages() {
+ local -a cmd packages packages_long
+ packages_long=(/var/lib/pacman/local/*(/))
+ packages=( ${${packages_long#/var/lib/pacman/local/}%-*-*} )
+ compadd "$@" -a packages
+}
+
+# provides completions for repository names
+_aura_completions_repositories() {
+ local -a cmd repositories
+ repositories=(${(o)${${${(M)${(f)"$(</etc/pacman.conf)"}:#\[*}/\[/}/\]/}:#options})
+ # Uniq the array
+ typeset -U repositories
+ compadd "$@" -a repositories
+}
+
+# builds command for invoking pacman in a _call_program command - extracts
+# relevant options already specified (config file, etc)
+# $cmd must be declared by calling function
+_aura_get_command() {
+ # this is mostly nicked from _perforce
+ cmd=( "aura" "2>/dev/null" )
+ integer i
+ for (( i = 2; i < CURRENT - 1; i++ )); do
+ if [[ ${words[i]} = "--config" || ${words[i]} = "--root" ]]; then
+ cmd+=( ${words[i,i+1]} )
+ fi
+ done
+}
+
+_aura_comp() {
+ local -a args cmds
+ local tmp
+ args=( ${${${(M)words:#-*}#-}:#-*} )
+ _message 'now what'
+ for tmp in $words; do
+ cmds+=("${${_aura_opts_commands[(r)*$tmp\[*]%%\[*}#*\)}")
+ done
+ case $args in #$words[2] in
+ h*)
+ if (( ${(c)#args} <= 1 && ${(w)#cmds} <= 1 )); then
+ _aura_action_help
+ else
+ _message "no more arguments"
+ fi
+ ;;
+ *h*)
+ _message "no more arguments"
+ ;;
+ D*)
+ _aura_action_database
+ ;;
+ Q*g*) # ipkg groups
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:groups:_aura_completions_installed_groups'
+ ;;
+ Q*o*) # file
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:package file:_files'
+ ;;
+ Q*p*) # file *.pkg.tar.*
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_query_modifiers[@]" \
+ '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"'
+ ;;
+ T*)
+ _aura_action_deptest
+ ;;
+ Q*)
+ _aura_action_query
+ ;;
+ R*)
+ _aura_action_remove
+ ;;
+ S*c*)
+ _arguments -s : \
+ '(-S --sync)'{-S,--sync} \
+ '(-c --clean)'{\*-c,\*--clean}'[Remove all files from the cache]' \
+ "$_aura_opts_common[@]"
+ ;;
+ S*l*) # repos
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '*:package repo:_aura_completions_repositories' \
+ ;;
+ S*g*) # pkg groups
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '*:package group:_aura_completions_all_groups'
+ ;;
+ S*s)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_sync_modifiers[@]" \
+ '*:search text: '
+ ;;
+ S*)
+ _aura_action_sync
+ ;;
+ U*)
+ _aura_action_upgrade
+ ;;
+ V*)
+ _aura_action_version
+ ;;
+ A*)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_aursync_modifiers[@]" \
+ "$_aura_opts_aursync_actions[@]" \
+ '*:package:_aura_completions_aur_packages'
+ ;;
+ B*)
+ _arguments -s : \
+ '(-B --save)'{-B,--save} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_save[@]"
+ ;;
+ C*)
+ _arguments -s : \
+ '(-C --downgrade)'{-C,--downgrade} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_downgrade[@]"
+ ;;
+ L*)
+ _arguments -s : \
+ '(-L --viewlog)'{-L,--viewlog} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_viewlog[@]"
+ ;;
+ M*)
+ _arguments -s : \
+ '(-M --abssync)'{-M,--abssync} \
+ "$_aura_opts_common[@]"
+ ;;
+ O*)
+ _arguments -s : \
+ '(-O --orphans)'{-O,--orphans} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_orphans[@]"
+ ;;
+ *)
+ case ${(M)words:#--*} in
+ *--help*)
+ if (( ${(W)#cmds} == 1 )); then
+ _aura_action_help
+ else
+ return 0
+ fi
+ ;;
+ *--sync*)
+ _aura_action_sync
+ ;;
+ *--query*)
+ _aura_action_query
+ ;;
+ *--remove*)
+ _aura_action_remove
+ ;;
+ *--deptest*)
+ _aura_action_deptest
+ ;;
+ *--database*)
+ _aura_action_database
+ ;;
+ *--version*)
+ _aura_action_version
+ ;;
+ *--upgrade*)
+ _aura_action_upgrade
+ ;;
+
+ *--aursync*)
+ _arguments -s : \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_aursync_modifiers[@]" \
+ "$_aura_opts_aursync_actions[@]" \
+ '*:package:_aura_completions_aur_packages'
+ ;;
+ *--save*)
+ _arguments -s : \
+ '(-B --save)'{-B,--save} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_save[@]"
+ ;;
+ *--downgrade*)
+ _arguments -s : \
+ '(-C --downgrade)'{-C,--downgrade} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_downgrade[@]"
+ ;;
+ *--viewlog*)
+ _arguments -s : \
+ '(-L --viewlog)'{-L,--viewlog} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_viewlog[@]"
+ ;;
+ *--orphans*)
+ _arguments -s : \
+ '(-O --orphans)'{-O,--orphans} \
+ "$_aura_opts_common[@]" \
+ "$_aura_opts_orphans[@]"
+ ;;
+ *)
+ _aura_action_none
+ ;;
+ esac
+ ;;
+ esac
+}
+
+# run the main dispatcher
+_aura_comp "$@"
diff --git a/doc/completions/bashcompletion.sh b/doc/completions/bashcompletion.sh
new file mode 100644
index 0000000..158ba9a
--- /dev/null
+++ b/doc/completions/bashcompletion.sh
@@ -0,0 +1,333 @@
+# Bash completion for Aura
+
+# generates completion line
+_aura_compgen() {
+ local i r
+ COMPREPLY=($( compgen -W "$1" -- "$cur" ))
+ for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do
+ for r in ${!COMPREPLY[@]}; do
+ if [[ ${COMP_WORDS[i]} = ${COMPREPLY[r]} ]]; then
+ unset 'COMPREPLY[r]'; break
+ fi
+ done
+ done
+ return 0
+}
+
+# check if COMP_LINE contains a given string
+_aura_incomp() {
+ if [[ $COMP_LINE =~ $1 ]]; then
+ return 0
+ fi
+ return 1
+}
+
+# deal with options that have an equal sign at the end
+# NOTE: regex for username might need tweaking to match
+# Arch Linux username requirements
+_aura_has_equal() {
+ # to make sure these options haven't been used already
+ local b_reg=--build=[^\0]+[[:blank:]]
+ local bu_reg=--builduser=[a-z_][-a-z0-9_]*[[:blank:]]
+ if [[ $cur == *=* ]]; then
+ prev=${cur%%=*}
+ cur=${cur#*=}
+ fi
+ if ! _aura_incomp "$b_reg"; then
+ if _aura_incomp --build=; then
+ _filedir -d
+ return 0
+ fi
+ fi
+ if ! _aura_incomp "$bu_reg"; then
+ if _aura_incomp --builduser=; then
+ _usergroup
+ return 0
+ fi
+ fi
+ return 1
+}
+
+# these common pacman options require directory or file completion
+_aura_pac_common_opts() {
+ local c status regex dir_comm file_comm
+ status="1"
+ # to make sure these options haven't been used already
+ regex=($c[[:blank:]][^\0]+[[:blank:]])
+ dir_comm="--cachedir --dbpath --gpgdir --root -b -r"
+ file_comm="--config --logfile"
+ for c in $dir_comm $file_comm; do
+ if [[ $dir_comm == *$c* ]]; then
+ if ! _aura_incomp "$regex" && _aura_incomp $c ; then
+ _filedir -d
+ status="0"
+ break
+ fi
+ elif [[ $file_comm == *$c* ]]; then
+ if ! _aura_incomp "$regex" && _aura_incomp $c ; then
+ _filedir
+ status="0"
+ break
+ fi
+ fi
+ done
+ return "$status"
+}
+
+# sends list of ABS packages to compgen
+_aura_pac_pkg() {
+ _aura_compgen "$(
+ if [[ $2 ]]; then
+ \aura -"$1" 2>/dev/null | \cut -d' ' -f1 | \sort -u
+ else
+ \aura -"$1" 2>/dev/null
+ fi
+ )"
+ return 0
+}
+
+# complete pacman package files
+_aura_pac_file() {
+ compopt -o filenames
+ _filedir 'pkg.tar*'
+ return 0
+}
+
+_aura()
+{
+ local cur prev core pcommon o showopts opts
+ COMPREPLY=()
+ _init_completion || return
+ _get_comp_words_by_ref cur prev
+ core="-A --aursync -B --save -C --downgrade -L --viewlog -M --abssync \
+ -O --orphans -D --database -Q --query -R --remove -S --sync \
+ -T --deptest -U --upgrade --auradebug --languages --noconfirm --no-pp \
+ -V --version -h --help"
+ # options common to all pacman functions
+ pcommon="--arch --cachedir --color --config --dbpath --debug --gpgdir \
+ --help --logfile --noconfirm --root --verbose -b -h -r -v"
+
+ # check if base option has been selected
+ for o in $core; do
+ _aura_incomp $o && break
+ done
+
+ # if no core option entered
+ if [[ $? != 0 ]]; then
+ _aura_compgen "$core"
+ else
+ # show options for core selection when dash typed
+ if [[ "$cur" = -* ]]; then
+ showopts=true
+ else
+ showopts=false
+ fi
+ # main option handling
+ case ${o} in
+ ##### Aura Options #####
+ -A|--aursync)
+ if $showopts; then
+ opts="-a --delmakedeps -d --deps -i --info -k --diff -p \
+ --pkgbuild -q --quiet -s --search -u --sysupgrade -w \
+ --downloadonly -x --unsuppress -y --refresh \
+ --aurignore= --build= --builduser= -custom --devel \
+ --hotedit --ignorearch --absdeps --dryrun"
+ # include suboptions when searching
+ local a search_opts="--abc --head --tail"
+ for a in '-As' '-s' '--search'; do
+ if _aura_incomp $a; then
+ _aura_compgen "$opts $search_opts"
+ return 0
+ fi
+ done
+ _aura_compgen "$opts"
+ return 0
+ else
+ _aura_has_equal
+ return 0
+ fi
+ ;;
+ -B|--save)
+ opts="-c --clean -r --restore"
+ _aura_compgen "$opts"
+ return 0
+ ;;
+ -C|--downgrade)
+ if $showopts; then
+ opts="-b --backup -c --clean -s --search"
+ _aura_compgen "$opts"
+ return 0
+ else
+ local c
+ # complete for dir to backup cache
+ for c in '-Cb' 'b' '--backup'; do
+ if _aura_incomp $c; then
+ _filedir
+ return 0
+ fi
+ done
+ fi
+ ;;
+ -L|--viewlog)
+ opts="-i --info -s --search"
+ _aura_compgen "$opts"
+ return 0
+ ;;
+ -M|--abssync)
+ if $showopts; then
+ opts="-a --delmakedeps -c --clean -d --deps -i --info \
+ -k --diff -p --pkgbuild -s --search -t --treesync \
+ -x --unsuppress -y --refresh --absdeps"
+ _aura_compgen "$opts"
+ return 0
+ else
+ # offer ABS pkg selection
+ _aura_pac_pkg Slq
+ return 0
+ fi
+ ;;
+ -O|--orphans)
+ if $showopts; then
+ opts="-j --abandon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ # use self generated orphan list
+ local o=$( aura -O )
+ if [[ $o != "" ]]; then
+ _aura_compgen "$o"
+ return 0
+ fi
+ fi
+ ;;
+ ##### Pacman Options #####
+ -D|--database)
+ if $showopts; then
+ opts="--asdeps --asexplicit $pcommon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_pkg Qq
+ return 0
+ fi
+ ;;
+ -Q|--query)
+ if $showopts; then
+ opts="--changelog --check --deps --explicit --file \
+ --foreign --groups --info --list --native --owns \
+ --quiet --search --unrequired --upgrades -c -d -e -g \
+ -i -k -l -m -n -o -p -q -s -t -u $pcommon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ local q
+ local ary=('-Qg' '-g' '--groups' '-Qp' '-p' '--file'
+ '-Qo' '-o' '--owns')
+ for q in ${ary[@]}; do
+ if _aura_incomp $q; then
+ case $q in
+ -Qg|-g|--groups)
+ _aura_pac_pkg Qg sort
+ ;;
+ -Qp|-p|--file)
+ _aura_pac_file
+ ;;
+ -Qo|-o|--owns)
+ _filedir
+ ;;
+ esac
+ return 0
+ fi
+ done
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_pkg Qq
+ return 0
+ fi
+ ;;
+ -R|--remove)
+ if $showopts; then
+ opts="--cascade --dbonly --nodeps --noprogressbar --nosave \
+ --noscriptlet --print --print-format --recursive \
+ --unneeded -c -d -n -p -s -u $pcommon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_pkg Qq
+ return 0
+ fi
+ ;;
+ -S|--sync)
+ if $showopts; then
+ opts="--asdeps --asexplicit --clean --dbonly --downloadonly \
+ --force --groups --ignore --ignoregroup --info --list \
+ --needed --nodeps --noprogressbar --noscriptlet --print \
+ --print-format --quiet --refresh --recursive --search \
+ --sysupgrade -c -d -g -i -l -p -q -s -u -w -y $pcommon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ local s ary
+ local ary=('-Sg' '-g' '--groups' '-Sl' '-l' '--list')
+ for s in ${ary[@]}; do
+ if _aura_incomp $s; then
+ case $s in
+ -Sg|-g|--groups)
+ _aura_pac_pkg Sg sort
+ ;;
+ -Sl|-l|--list)
+ _aura_pac_pkg Sl sort
+ ;;
+ esac
+ return 0
+ fi
+ done
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_pkg Slq
+ return 0
+ fi
+ ;;
+ -T|--deptest)
+ if $showopts; then
+ _aura_compgen "$pcommon"
+ return 0
+ else
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_pkg Slq
+ return 0
+ fi
+ ;;
+ -U|--upgrade)
+ if $showopts; then
+ opts="--asdeps --asexplicit --dbonly --force --ignore \
+ --ignoregroup --needed --nodeps --noprogressbar \
+ --noscriptlet --print --print-format --recursive \
+ -d -p $pcommon"
+ _aura_compgen "$opts"
+ return 0
+ else
+ if _aura_pac_common_opts; then
+ return 0
+ fi
+ _aura_pac_file
+ return 0
+ fi
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ fi
+}
+complete -F _aura -o nospace aura
diff --git a/exec/Flags.hs b/exec/Flags.hs
new file mode 100644
index 0000000..9013861
--- /dev/null
+++ b/exec/Flags.hs
@@ -0,0 +1,515 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Flags
+ ( Program(..), opts
+ , PacmanOp(..), SyncOp(..), MiscOp(..)
+ , AuraOp(..), _AurSync, _AurIgnore, _AurIgnoreGroup
+ , AurSwitch(..), AurOp(..), BackupOp(..), CacheOp(..), LogOp(..), OrphanOp(..)
+ ) where
+
+import Aura.Cache (defaultPackageCache)
+import Aura.Pacman (pacmanConfFile, defaultLogFile)
+import Aura.Settings
+import Aura.Types
+import BasePrelude hiding (Version, option, log, exp)
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Lens.Micro
+import Options.Applicative
+import System.Path (Path, Absolute, toFilePath, fromAbsoluteFilePath)
+
+---
+
+-- | A description of a run of Aura to attempt.
+data Program = Program {
+ -- ^ Whether Aura handles everything, or the ops and input are just passed down to Pacman.
+ _operation :: Either (PacmanOp, S.Set MiscOp) AuraOp
+ -- ^ Settings common to both Aura and Pacman.
+ , _commons :: CommonConfig
+ -- ^ Settings specific to building packages.
+ , _buildConf :: BuildConfig
+ -- ^ The human language of text output.
+ , _language :: Maybe Language } deriving (Show)
+
+-- | Inherited operations that are fed down to Pacman.
+data PacmanOp = Database (Either DatabaseOp (NonEmptySet PkgName))
+ | Files (S.Set FilesOp)
+ | Query (Either QueryOp (S.Set QueryFilter, S.Set PkgName))
+ | Remove (S.Set RemoveOp) (NonEmptySet PkgName)
+ | Sync (Either SyncOp (S.Set PkgName)) (S.Set SyncSwitch)
+ | TestDeps (NonEmptySet T.Text)
+ | Upgrade (S.Set UpgradeSwitch) (NonEmptySet PkgName)
+ deriving (Show)
+
+instance Flagable PacmanOp where
+ asFlag (Database (Left o)) = "-D" : asFlag o
+ asFlag (Database (Right fs)) = "-D" : asFlag fs
+ asFlag (Files os) = "-F" : asFlag os
+ asFlag (Query (Left o)) = "-Q" : asFlag o
+ asFlag (Query (Right (fs, ps))) = "-Q" : asFlag ps ++ asFlag fs
+ asFlag (Remove os ps) = "-R" : asFlag os ++ asFlag ps
+ asFlag (Sync (Left o) ss) = "-S" : asFlag ss ++ asFlag o
+ asFlag (Sync (Right ps) ss) = "-S" : asFlag ss ++ asFlag ps
+ asFlag (TestDeps ps) = "-T" : asFlag ps
+ asFlag (Upgrade s ps) = "-U" : asFlag s ++ asFlag ps
+
+data DatabaseOp = DBCheck
+ | DBAsDeps (NonEmptySet T.Text)
+ | DBAsExplicit (NonEmptySet T.Text)
+ deriving (Show)
+
+instance Flagable DatabaseOp where
+ asFlag DBCheck = ["--check"]
+ asFlag (DBAsDeps ps) = "--asdeps" : asFlag ps
+ asFlag (DBAsExplicit ps) = "--asexplicit" : asFlag ps
+
+data FilesOp = FilesList (NonEmptySet T.Text)
+ | FilesOwns T.Text
+ | FilesSearch T.Text
+ | FilesRegex
+ | FilesRefresh
+ | FilesMachineReadable
+ deriving (Eq, Ord, Show)
+
+instance Flagable FilesOp where
+ asFlag (FilesList fs) = "--list" : asFlag fs
+ asFlag (FilesOwns f) = ["--owns", T.unpack f]
+ asFlag (FilesSearch f) = ["--search", T.unpack f]
+ asFlag FilesRegex = ["--regex"]
+ asFlag FilesRefresh = ["--refresh"]
+ asFlag FilesMachineReadable = ["--machinereadable"]
+
+data QueryOp = QueryChangelog (NonEmptySet T.Text)
+ | QueryGroups (NonEmptySet T.Text)
+ | QueryInfo (NonEmptySet T.Text)
+ | QueryCheck (NonEmptySet T.Text)
+ | QueryList (NonEmptySet T.Text)
+ | QueryOwns (NonEmptySet T.Text)
+ | QueryFile (NonEmptySet T.Text)
+ | QuerySearch T.Text
+ deriving (Show)
+
+instance Flagable QueryOp where
+ asFlag (QueryChangelog ps) = "--changelog" : asFlag ps
+ asFlag (QueryGroups ps) = "--groups" : asFlag ps
+ asFlag (QueryInfo ps) = "--info" : asFlag ps
+ asFlag (QueryCheck ps) = "--check" : asFlag ps
+ asFlag (QueryList ps) = "--list" : asFlag ps
+ asFlag (QueryOwns ps) = "--owns" : asFlag ps
+ asFlag (QueryFile ps) = "--file" : asFlag ps
+ asFlag (QuerySearch t) = ["--search", T.unpack t]
+
+data QueryFilter = QueryDeps
+ | QueryExplicit
+ | QueryForeign
+ | QueryNative
+ | QueryUnrequired
+ | QueryUpgrades
+ deriving (Eq, Ord, Show)
+
+instance Flagable QueryFilter where
+ asFlag QueryDeps = ["--deps"]
+ asFlag QueryExplicit = ["--explicit"]
+ asFlag QueryForeign = ["--foreign"]
+ asFlag QueryNative = ["--native"]
+ asFlag QueryUnrequired = ["--unrequired"]
+ asFlag QueryUpgrades = ["--upgrades"]
+
+data RemoveOp = RemoveCascade
+ | RemoveNoSave
+ | RemoveRecursive
+ | RemoveUnneeded
+ deriving (Eq, Ord, Show)
+
+instance Flagable RemoveOp where
+ asFlag RemoveCascade = ["--cascade"]
+ asFlag RemoveNoSave = ["--nosave"]
+ asFlag RemoveRecursive = ["--recursive"]
+ asFlag RemoveUnneeded = ["--unneeded"]
+
+data SyncOp = SyncClean
+ | SyncGroups (NonEmptySet T.Text)
+ | SyncInfo (NonEmptySet T.Text)
+ | SyncList T.Text
+ | SyncSearch T.Text
+ | SyncUpgrade (S.Set T.Text)
+ | SyncDownload (NonEmptySet T.Text)
+ deriving (Show)
+
+instance Flagable SyncOp where
+ asFlag SyncClean = ["--clean"]
+ asFlag (SyncGroups gs) = "--groups" : asFlag gs
+ asFlag (SyncInfo ps) = "--info" : asFlag ps
+ asFlag (SyncList r) = ["--list", T.unpack r]
+ asFlag (SyncSearch s) = ["--search", T.unpack s]
+ asFlag (SyncUpgrade ps) = "--sysupgrade" : asFlag ps
+ asFlag (SyncDownload ps) = "--downloadonly" : asFlag ps
+
+data SyncSwitch = SyncRefresh
+ | SyncIgnore (S.Set PkgName)
+ | SyncIgnoreGroup (S.Set PkgGroup)
+ deriving (Eq, Ord, Show)
+
+instance Flagable SyncSwitch where
+ asFlag SyncRefresh = ["--refresh"]
+ asFlag (SyncIgnore ps) = ["--ignore", intercalate "," $ asFlag ps ]
+ asFlag (SyncIgnoreGroup gs) = ["--ignoregroup" , intercalate "," $ asFlag gs ]
+
+data UpgradeSwitch = UpgradeAsDeps
+ | UpgradeAsExplicit
+ | UpgradeIgnore (S.Set PkgName)
+ | UpgradeIgnoreGroup (S.Set PkgGroup)
+ deriving (Eq, Ord, Show)
+
+instance Flagable UpgradeSwitch where
+ asFlag UpgradeAsDeps = ["--asdeps"]
+ asFlag UpgradeAsExplicit = ["--asexplicit"]
+ asFlag (UpgradeIgnore ps) = ["--ignore", intercalate "," $ asFlag ps ]
+ asFlag (UpgradeIgnoreGroup gs) = ["--ignoregroup" , intercalate "," $ asFlag gs ]
+
+-- | Flags common to several Pacman operations.
+data MiscOp = MiscArch (Path Absolute)
+ | MiscAssumeInstalled T.Text
+ | MiscColor T.Text
+ | MiscConfirm
+ | MiscDBOnly
+ | MiscDBPath (Path Absolute)
+ | MiscGpgDir (Path Absolute)
+ | MiscHookDir (Path Absolute)
+ | MiscNoDeps
+ | MiscNoProgress
+ | MiscNoScriptlet
+ | MiscPrint
+ | MiscPrintFormat T.Text
+ | MiscRoot (Path Absolute)
+ | MiscVerbose
+ deriving (Eq, Ord, Show)
+
+instance Flagable MiscOp where
+ asFlag (MiscArch p) = ["--arch", toFilePath p]
+ asFlag (MiscAssumeInstalled p) = ["--assume-installed", T.unpack p]
+ asFlag (MiscColor c) = ["--color", T.unpack c]
+ asFlag (MiscDBPath p) = ["--dbpath", toFilePath p]
+ asFlag (MiscGpgDir p) = ["--gpgdir", toFilePath p]
+ asFlag (MiscHookDir p) = ["--hookdir", toFilePath p]
+ asFlag (MiscPrintFormat s) = ["--print-format", T.unpack s]
+ asFlag (MiscRoot p) = ["--root", toFilePath p]
+ asFlag MiscConfirm = ["--confirm"]
+ asFlag MiscDBOnly = ["--dbonly"]
+ asFlag MiscNoDeps = ["--nodeps"]
+ asFlag MiscNoProgress = ["--noprogressbar"]
+ asFlag MiscNoScriptlet = ["--noscriptlet"]
+ asFlag MiscPrint = ["--print"]
+ asFlag MiscVerbose = ["--verbose"]
+
+-- | Operations unique to Aura.
+data AuraOp = AurSync (Either AurOp (NonEmptySet PkgName)) (S.Set AurSwitch)
+ | Backup (Maybe BackupOp)
+ | Cache (Either CacheOp (NonEmptySet PkgName))
+ | Log (Maybe LogOp)
+ | Orphans (Maybe OrphanOp)
+ | Version
+ | Languages
+ | ViewConf
+ deriving (Show)
+
+_AurSync :: Traversal' AuraOp (S.Set AurSwitch)
+_AurSync f (AurSync o s) = AurSync o <$> f s
+_AurSync _ x = pure x
+
+data AurOp = AurDeps (NonEmptySet PkgName)
+ | AurInfo (NonEmptySet PkgName)
+ | AurPkgbuild (NonEmptySet PkgName)
+ | AurSearch T.Text
+ | AurUpgrade (S.Set PkgName)
+ | AurJson (NonEmptySet PkgName)
+ deriving (Show)
+
+data AurSwitch = AurIgnore (S.Set PkgName)
+ | AurIgnoreGroup (S.Set PkgGroup)
+ deriving (Eq, Ord, Show)
+
+_AurIgnore :: Traversal' AurSwitch (S.Set PkgName)
+_AurIgnore f (AurIgnore s) = AurIgnore <$> f s
+_AurIgnore _ x = pure x
+
+_AurIgnoreGroup :: Traversal' AurSwitch (S.Set PkgGroup)
+_AurIgnoreGroup f (AurIgnoreGroup s) = AurIgnoreGroup <$> f s
+_AurIgnoreGroup _ x = pure x
+
+data BackupOp = BackupClean Word | BackupRestore | BackupList deriving (Show)
+
+data CacheOp = CacheBackup (Path Absolute) | CacheClean Word | CacheCleanNotSaved | CacheSearch T.Text deriving (Show)
+
+data LogOp = LogInfo (NonEmptySet PkgName) | LogSearch T.Text deriving (Show)
+
+data OrphanOp = OrphanAbandon | OrphanAdopt (NonEmptySet PkgName) deriving (Show)
+
+opts :: ParserInfo Program
+opts = info (program <**> helper) (fullDesc <> header "Aura - Package manager for Arch Linux and the AUR.")
+
+program :: Parser Program
+program = Program
+ <$> (fmap Right aurOps <|> (curry Left <$> pacOps <*> misc))
+ <*> commonConfig
+ <*> buildConfig
+ <*> optional language
+ where aurOps = aursync <|> backups <|> cache <|> log <|> orphans <|> version' <|> languages <|> viewconf
+ pacOps = database <|> files <|> queries <|> remove <|> sync <|> testdeps <|> upgrades
+
+aursync :: Parser AuraOp
+aursync = bigA *>
+ (AurSync
+ <$> (fmap (Right . NES.fromNonEmpty . fmap (PkgName . T.toLower) . NES.toNonEmpty) someArgs <|> fmap Left mods)
+ <*> (S.fromList <$> many switches)
+ )
+ where bigA = flag' () (long "aursync" <> short 'A' <> help "Install packages from the AUR.")
+ mods = ds <|> ainfo <|> pkgb <|> search <|> upgrade <|> aur
+ ds = AurDeps <$> (flag' () (long "deps" <> short 'd' <> hidden <> help "View dependencies of an AUR package.") *> somePkgs')
+ ainfo = AurInfo <$> (flag' () (long "info" <> short 'i' <> hidden <> help "View AUR package information.") *> somePkgs')
+ pkgb = AurPkgbuild <$> (flag' () (long "pkgbuild" <> short 'p' <> hidden <> help "View an AUR package's PKGBUILD file.") *> somePkgs')
+ search = AurSearch <$> strOption (long "search" <> short 's' <> metavar "STRING" <> hidden <> help "Search the AUR via a search string.")
+ upgrade = AurUpgrade <$> (flag' () (long "sysupgrade" <> short 'u' <> hidden <> help "Upgrade all installed AUR packages.") *> fmap (S.map PkgName) manyArgs')
+ aur = AurJson <$> (flag' () (long "json" <> hidden <> help "Retrieve package JSON straight from the AUR.") *> somePkgs')
+ switches = ign <|> igg
+ ign = AurIgnore . S.fromList . map PkgName . T.split (== ',') <$>
+ strOption (long "ignore" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore given packages.")
+ igg = AurIgnoreGroup . S.fromList . map PkgGroup . T.split (== ',') <$>
+ strOption (long "ignoregroup" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore packages from the given groups.")
+
+backups :: Parser AuraOp
+backups = bigB *> (Backup <$> optional mods)
+ where bigB = flag' () (long "save" <> short 'B' <> help "Save a package state.")
+ mods = clean <|> restore <|> lst
+ clean = BackupClean <$> option auto (long "clean" <> short 'c' <> metavar "N" <> hidden <> help "Keep the most recent N states, delete the rest.")
+ restore = flag' BackupRestore (long "restore" <> short 'r' <> hidden <> help "Restore a previous package state.")
+ lst = flag' BackupList (long "list" <> short 'l' <> hidden <> help "Show all saved package state filenames.")
+
+cache :: Parser AuraOp
+cache = bigC *> (Cache <$> (fmap Left mods <|> fmap Right somePkgs))
+ where bigC = flag' () (long "downgrade" <> short 'C' <> help "Interact with the package cache.")
+ mods = backup <|> clean <|> clean' <|> search
+ backup = CacheBackup . fromAbsoluteFilePath <$> strOption (long "backup" <> short 'b' <> metavar "PATH" <> help "Backup the package cache to a given directory." <> hidden)
+ clean = CacheClean <$> option auto (long "clean" <> short 'c' <> metavar "N" <> help "Save the most recent N versions of a package in the cache, deleting the rest." <> hidden)
+ clean' = flag' CacheCleanNotSaved (long "notsaved" <> help "Clean out any cached package files which doesn't appear in any saved state." <> hidden)
+ search = CacheSearch <$> strOption (long "search" <> short 's' <> metavar "STRING" <> help "Search the package cache via a search string." <> hidden)
+
+log :: Parser AuraOp
+log = bigL *> (Log <$> optional mods)
+ where bigL = flag' () (long "viewlog" <> short 'L' <> help "View the Pacman log.")
+ mods = inf <|> sch
+ inf = LogInfo <$> (flag' () (long "info" <> short 'i' <> help "Display the installation history for given packages." <> hidden) *> somePkgs')
+ sch = LogSearch <$> strOption (long "search" <> short 's' <> metavar "STRING" <> help "Search the Pacman log via a search string." <> hidden)
+
+orphans :: Parser AuraOp
+orphans = bigO *> (Orphans <$> optional mods)
+ where bigO = flag' () (long "orphans" <> short 'O' <> help "Display all orphan packages.")
+ mods = abandon <|> adopt
+ abandon = flag' OrphanAbandon (long "abandon" <> short 'j' <> hidden <> help "Uninstall all orphan packages.")
+ adopt = OrphanAdopt <$> (flag' () (long "adopt" <> hidden <> help "Mark some packages' install reason as 'Explicit'.") *> somePkgs')
+
+version' :: Parser AuraOp
+version' = flag' Version (long "version" <> short 'V' <> help "Display Aura's version.")
+
+languages :: Parser AuraOp
+languages = flag' Languages (long "languages" <> help "Show all human languages available for output.")
+
+viewconf :: Parser AuraOp
+viewconf = flag' ViewConf (long "viewconf" <> help "View the Pacman config file.")
+
+buildConfig :: Parser BuildConfig
+buildConfig = BuildConfig <$> makepkg <*> bp <*> optional bu <*> trunc <*> buildSwitches
+ where makepkg = S.fromList <$> many (ia <|> as <|> si)
+ ia = flag' IgnoreArch (long "ignorearch" <> hidden <> help "Exposed makepkg flag.")
+ as = flag' AllSource (long "allsource" <> hidden <> help "Exposed makepkg flag.")
+ si = flag' SkipInteg (long "skipinteg" <> hidden <> help "Skip all makepkg integrity checks.")
+ bp = fmap fromAbsoluteFilePath (strOption (long "build" <> metavar "PATH" <> hidden <> help "Directory in which to build packages."))
+ <|> pure defaultBuildDir
+ bu = User <$> strOption (long "builduser" <> metavar "USER" <> hidden <> help "User account to build as.")
+ trunc = fmap Head (option auto (long "head" <> metavar "N" <> hidden <> help "Only show top N search results."))
+ <|> fmap Tail (option auto (long "tail" <> metavar "N" <> hidden <> help "Only show last N search results."))
+ <|> pure None
+
+buildSwitches :: Parser (S.Set BuildSwitch)
+buildSwitches = S.fromList <$> many (lv <|> dmd <|> dsm <|> dpb <|> rbd <|> he <|> ucp <|> dr <|> sa <|> fo <|> npc)
+ where dmd = flag' DeleteMakeDeps (long "delmakedeps" <> short 'a' <> hidden <> help "Uninstall makedeps after building.")
+ dsm = flag' DontSuppressMakepkg (long "unsuppress" <> short 'x' <> hidden <> help "Unsuppress makepkg output.")
+ dpb = flag' DiffPkgbuilds (long "diff" <> short 'k' <> hidden <> help "Show PKGBUILD diffs.")
+ rbd = flag' RebuildDevel (long "devel" <> hidden <> help "Rebuild all git/hg/svn/darcs-based packages.")
+ he = flag' HotEdit (long "hotedit" <> hidden <> help "Edit a PKGBUILD before building.")
+ ucp = flag' UseCustomizepkg (long "custom" <> hidden <> help "Run customizepkg before building.")
+ dr = flag' DryRun (long "dryrun" <> hidden <> help "Run dependency checks and PKGBUILD diffs, but don't build.")
+ sa = flag' SortAlphabetically (long "abc" <> hidden <> help "Sort search results alphabetically.")
+ lv = flag' LowVerbosity (long "quiet" <> short 'q' <> hidden <> help "Display less information.")
+ fo = flag' ForceBuilding (long "force" <> hidden <> help "Always (re)build specified packages.")
+ npc = flag' NoPkgbuildCheck (long "noanalysis" <> hidden <> help "Do not analyse PKGBUILDs for security flaws.")
+
+commonConfig :: Parser CommonConfig
+commonConfig = CommonConfig <$> cap <*> cop <*> lfp <*> commonSwitches
+ where cap = fmap (Right . fromAbsoluteFilePath)
+ (strOption (long "cachedir" <> hidden <> help "Use an alternate package cache location."))
+ <|> pure (Left defaultPackageCache)
+ cop = fmap (Right . fromAbsoluteFilePath)
+ (strOption (long "config" <> hidden <> help "Use an alternate Pacman config file."))
+ <|> pure (Left pacmanConfFile)
+ lfp = fmap (Right . fromAbsoluteFilePath)
+ (strOption (long "logfile" <> hidden <> help "Use an alternate Pacman log."))
+ <|> pure (Left defaultLogFile)
+
+commonSwitches :: Parser (S.Set CommonSwitch)
+commonSwitches = S.fromList <$> many (nc <|> no <|> dbg <|> clr)
+ where nc = flag' NoConfirm (long "noconfirm" <> hidden <> help "Never ask for Aura or Pacman confirmation.")
+ no = flag' NeededOnly (long "needed" <> hidden <> help "Don't rebuild/reinstall up-to-date packages.")
+ dbg = flag' Debug (long "debug" <> hidden <> help "Print useful debugging info.")
+ clr = Colour . f <$> strOption (long "color" <> metavar "WHEN" <> hidden <> help "Colourize the output.")
+ f "never" = Never
+ f "always" = Always
+ f _ = Auto
+
+database :: Parser PacmanOp
+database = bigD *> (Database <$> (fmap Right somePkgs <|> fmap Left mods))
+ where bigD = flag' () (long "database" <> short 'D' <> help "Interact with the package database.")
+ mods = check <|> asdeps <|> asexp
+ check = flag' DBCheck (long "check" <> short 'k' <> hidden <> help "Test local database validity.")
+ asdeps = DBAsDeps <$> (flag' () (long "asdeps" <> hidden <> help "Mark packages as being dependencies.") *> someArgs')
+ asexp = DBAsExplicit <$> (flag' () (long "asexplicit" <> hidden <> help "Mark packages as being explicitely installed.") *> someArgs')
+
+files :: Parser PacmanOp
+files = bigF *> (Files <$> fmap S.fromList (many mods))
+ where bigF = flag' () (long "files" <> short 'F' <> help "Interact with the file database.")
+ mods = lst <|> own <|> sch <|> rgx <|> rfr <|> mch
+ lst = FilesList <$> (flag' () (long "list" <> short 'l' <> hidden <> help "List the files owned by given packages.") *> someArgs')
+ own = FilesOwns <$> strOption (long "owns" <> short 'o' <> metavar "FILE" <> hidden <> help "Query the package that owns FILE.")
+ sch = FilesSearch <$> strOption (long "search" <> short 's' <> metavar "FILE" <> hidden <> help "Find package files that match the given FILEname.")
+ rgx = flag' FilesRegex (long "regex" <> short 'x' <> hidden <> help "Interpret the input of -Fs as a regex.")
+ rfr = flag' FilesRefresh (long "refresh" <> short 'y' <> hidden <> help "Download fresh package databases.")
+ mch = flag' FilesMachineReadable (long "machinereadable" <> hidden <> help "Produce machine-readable output.")
+
+queries :: Parser PacmanOp
+queries = bigQ *> (Query <$> (fmap Right query <|> fmap Left mods))
+ where bigQ = flag' () (long "query" <> short 'Q' <> help "Interact with the local package database.")
+ query = curry (second (S.map PkgName)) <$> queryFilters <*> manyArgs
+ mods = chl <|> gps <|> inf <|> lst <|> own <|> fls <|> sch <|> chk
+ chl = QueryChangelog <$> (flag' () (long "changelog" <> short 'c' <> hidden <> help "View a package's changelog.") *> someArgs')
+ gps = QueryGroups <$> (flag' () (long "groups" <> short 'g' <> hidden <> help "View all members of a package group.") *> someArgs')
+ inf = QueryInfo <$> (flag' () (long "info" <> short 'i' <> hidden <> help "View package information.") *> someArgs')
+ lst = QueryList <$> (flag' () (long "list" <> short 'l' <> hidden <> help "List files owned by a package.") *> someArgs')
+ chk = QueryCheck <$> (flag' () (long "check" <> short 'k' <> hidden <> help "Check that package files exist.") *> someArgs')
+ own = QueryOwns <$> (flag' () (long "owns" <> short 'o' <> hidden <> help "Find the package some file belongs to.") *> someArgs')
+ fls = QueryFile <$> (flag' () (long "file" <> short 'p' <> hidden <> help "Query a package file.") *> someArgs')
+ sch = QuerySearch <$> strOption (long "search" <> short 's' <> metavar "REGEX" <> hidden <> help "Search the local database.")
+
+queryFilters :: Parser (S.Set QueryFilter)
+queryFilters = S.fromList <$> many (dps <|> exp <|> frg <|> ntv <|> urq <|> upg)
+ where dps = flag' QueryDeps (long "deps" <> short 'd' <> hidden <> help "[filter] Only list packages installed as deps.")
+ exp = flag' QueryExplicit (long "explicit" <> short 'e' <> hidden <> help "[filter] Only list explicitly installed packages.")
+ frg = flag' QueryForeign (long "foreign" <> short 'm' <> hidden <> help "[filter] Only list AUR packages.")
+ ntv = flag' QueryNative (long "native" <> short 'n' <> hidden <> help "[filter] Only list official packages.")
+ urq = flag' QueryUnrequired (long "unrequired" <> short 't' <> hidden <> help "[filter] Only list packages not required as a dependency to any other.")
+ upg = flag' QueryUpgrades (long "upgrades" <> short 'u' <> hidden <> help "[filter] Only list outdated packages.")
+
+remove :: Parser PacmanOp
+remove = bigR *> (Remove <$> mods <*> somePkgs)
+ where bigR = flag' () (long "remove" <> short 'R' <> help "Uninstall packages.")
+ mods = S.fromList <$> many (cascade <|> nosave <|> recurse <|> unneeded)
+ cascade = flag' RemoveCascade (long "cascade" <> short 'c' <> hidden <> help "Remove packages and all others that depend on them.")
+ nosave = flag' RemoveNoSave (long "nosave" <> short 'n' <> hidden <> help "Remove configuration files as well.")
+ recurse = flag' RemoveRecursive (long "recursive" <> short 's' <> hidden <> help "Remove unneeded dependencies.")
+ unneeded = flag' RemoveUnneeded (long "unneeded" <> short 'u' <> hidden <> help "Remove unneeded packages.")
+
+sync :: Parser PacmanOp
+sync = bigS *> (Sync <$> (fmap (Right . S.map PkgName) manyArgs <|> fmap Left mods) <*> (S.fromList <$> many (ref <|> ign <|> igg)))
+ where bigS = flag' () (long "sync" <> short 'S' <> help "Install official packages.")
+ ref = flag' SyncRefresh (long "refresh" <> short 'y' <> hidden <> help "Update the package database.")
+ mods = cln <|> gps <|> inf <|> lst <|> sch <|> upg <|> dnl
+ cln = flag' SyncClean (long "clean" <> short 'c' <> hidden <> help "Remove old packages from the cache.")
+ gps = SyncGroups <$> (flag' () (long "groups" <> short 'g' <> hidden <> help "View members of a package group.") *> someArgs')
+ inf = SyncInfo <$> (flag' () (long "info" <> short 'i' <> hidden <> help "View package information.") *> someArgs')
+ lst = SyncList <$> strOption (long "list" <> short 'l' <> metavar "REPO" <> hidden <> help "List the packages in a REPO.")
+ sch = SyncSearch <$> strOption (long "search" <> short 's' <> metavar "REGEX" <> hidden <> help "Search the official package repos.")
+ upg = SyncUpgrade <$> (flag' () (long "sysupgrade" <> short 'u' <> hidden <> help "Upgrade installed packages.") *> manyArgs')
+ dnl = SyncDownload <$> (flag' () (long "downloadonly" <> short 'w' <> hidden <> help "Download package tarballs.") *> someArgs')
+ ign = SyncIgnore . S.fromList . map PkgName . T.split (== ',') <$>
+ strOption (long "ignore" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore given packages.")
+ igg = SyncIgnoreGroup . S.fromList . map PkgGroup . T.split (== ',') <$>
+ strOption (long "ignoregroup" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore packages from the given groups.")
+
+misc :: Parser (S.Set MiscOp)
+misc = S.fromList <$> many (ar <|> dbp <|> roo <|> ver <|> gpg <|> hd <|> con <|> dbo <|> nop <|> nos <|> pf <|> nod <|> prt <|> asi)
+ where ar = MiscArch . fromAbsoluteFilePath
+ <$> strOption (long "arch" <> metavar "ARCH" <> hidden <> help "Use an alternate architecture.")
+ dbp = MiscDBPath . fromAbsoluteFilePath
+ <$> strOption (long "dbpath" <> short 'b' <> metavar "PATH" <> hidden <> help "Use an alternate database location.")
+ roo = MiscRoot . fromAbsoluteFilePath
+ <$> strOption (long "root" <> short 'r' <> metavar "PATH" <> hidden <> help "Use an alternate installation root.")
+ ver = flag' MiscVerbose (long "verbose" <> short 'v' <> hidden <> help "Be more verbose.")
+ gpg = MiscGpgDir . fromAbsoluteFilePath
+ <$> strOption (long "gpgdir" <> metavar "PATH" <> hidden <> help "Use an alternate GnuGPG directory.")
+ hd = MiscHookDir . fromAbsoluteFilePath
+ <$> strOption (long "hookdir" <> metavar "PATH" <> hidden <> help "Use an alternate hook directory.")
+ con = flag' MiscConfirm (long "confirm" <> hidden <> help "Always ask for confirmation.")
+ dbo = flag' MiscDBOnly (long "dbonly" <> hidden <> help "Only modify database entries, not package files.")
+ nop = flag' MiscNoProgress (long "noprogressbar" <> hidden <> help "Don't show a progress bar when downloading.")
+ nos = flag' MiscNoScriptlet (long "noscriptlet" <> hidden <> help "Don't run available install scriptlets.")
+ pf = MiscPrintFormat <$> strOption (long "print-format" <> metavar "STRING" <> hidden <> help "Specify how targets should be printed.")
+ nod = flag' MiscNoDeps (long "nodeps" <> short 'd' <> hidden <> help "Skip dependency version checks.")
+ prt = flag' MiscPrint (long "print" <> short 'p' <> hidden <> help "Print the targets instead of performing the operation.")
+ asi = MiscAssumeInstalled <$> strOption (long "assume-installed" <> metavar "<package=version>" <> hidden <> help "Add a virtual package to satisfy dependencies.")
+
+testdeps :: Parser PacmanOp
+testdeps = bigT *> (TestDeps <$> someArgs)
+ where bigT = flag' () (long "deptest" <> short 'T' <> help "Test dependencies - useful for scripts.")
+
+upgrades :: Parser PacmanOp
+upgrades = bigU *> (Upgrade <$> (S.fromList <$> many mods) <*> somePkgs)
+ where bigU = flag' () (long "upgrade" <> short 'U' <> help "Install given package files.")
+ mods = asd <|> ase <|> ign <|> igg
+ asd = flag' UpgradeAsDeps (long "asdeps" <> hidden)
+ ase = flag' UpgradeAsExplicit (long "asexplicit" <> hidden)
+ ign = UpgradeIgnore . S.fromList . map PkgName . T.split (== ',') <$>
+ strOption (long "ignore" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore given packages.")
+ igg = UpgradeIgnoreGroup . S.fromList . map PkgGroup . T.split (== ',') <$>
+ strOption (long "ignoregroup" <> metavar "PKG(,PKG,...)" <> hidden <> help "Ignore packages from the given groups.")
+
+somePkgs :: Parser (NonEmptySet PkgName)
+somePkgs = NES.fromNonEmpty . fromJust . NEL.nonEmpty . map PkgName <$> some (argument str (metavar "PACKAGES"))
+
+-- | Same as `someArgs`, but the help message "brief display" won't show PACKAGES.
+somePkgs' :: Parser (NonEmptySet PkgName)
+somePkgs' = NES.fromNonEmpty . fromJust . NEL.nonEmpty . map PkgName <$> some (argument str (metavar "PACKAGES" <> hidden))
+
+-- | One or more arguments.
+someArgs :: Parser (NonEmptySet T.Text)
+someArgs = NES.fromNonEmpty . fromJust . NEL.nonEmpty <$> some (argument str (metavar "PACKAGES"))
+
+-- | Same as `someArgs`, but the help message "brief display" won't show PACKAGES.
+someArgs' :: Parser (NonEmptySet T.Text)
+someArgs' = NES.fromNonEmpty . fromJust . NEL.nonEmpty <$> some (argument str (metavar "PACKAGES" <> hidden))
+
+-- | Zero or more arguments.
+manyArgs :: Parser (S.Set T.Text)
+manyArgs = S.fromList <$> many (argument str (metavar "PACKAGES"))
+
+-- | Zero or more arguments.
+manyArgs' :: Parser (S.Set T.Text)
+manyArgs' = S.fromList <$> many (argument str (metavar "PACKAGES" <> hidden))
+
+language :: Parser Language
+language = foldr1 (<|>) $ map (\(f, v) -> flag' v (long f <> hidden)) langs
+ where langs = [ ( "japanese", Japanese ), ( "日本語", Japanese )
+ , ( "polish", Polish ), ( "polski", Polish )
+ , ( "croatian", Croatian ), ( "hrvatski", Croatian )
+ , ( "swedish", Swedish ), ( "svenska", Swedish )
+ , ( "german", German ), ( "deutsch", German )
+ , ( "spanish", Spanish ), ( "español", Spanish )
+ , ( "portuguese", Portuguese ), ( "português", Portuguese )
+ , ( "french", French), ( "français", French )
+ , ( "russian", Russian ), ( "русский", Russian )
+ , ( "italian", Italian ), ( "italiano", Italian )
+ , ( "serbian", Serbian ), ( "српски", Serbian )
+ , ( "norwegian", Norwegian ), ( "norsk", Norwegian )
+ , ( "indonesian", Indonesia )
+ , ( "chinese", Chinese ), ( "中文", Chinese ) ]
diff --git a/exec/Settings.hs b/exec/Settings.hs
new file mode 100644
index 0000000..f3bb3d5
--- /dev/null
+++ b/exec/Settings.hs
@@ -0,0 +1,72 @@
+{-
+
+Copyright 2012 - 2018 Colin Woodbury <colin@fosskers.ca>
+
+This file is part of Aura.
+
+Aura 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.
+
+Aura 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 Aura. If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+module Settings ( getSettings ) where
+
+import Aura.Languages
+import Aura.Pacman
+import Aura.Settings
+import Aura.Types
+import Aura.Utils
+import BasePrelude hiding (FilePath)
+import Control.Error.Util (failWith)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Except
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Flags
+import Lens.Micro
+import Network.HTTP.Client (newManager)
+import Network.HTTP.Client.TLS (tlsManagerSettings)
+import System.Environment (getEnvironment)
+import System.IO (hIsTerminalDevice, stdout)
+
+---
+
+getSettings :: Program -> IO (Either Failure Settings)
+getSettings (Program op co bc lng) = runExceptT $ do
+ let ign = S.fromList $ op ^.. _Right . _AurSync . folded . _AurIgnore . folded
+ igg = S.fromList $ op ^.. _Right . _AurSync . folded . _AurIgnoreGroup . folded
+ confFile <- ExceptT (getPacmanConf . either id id $ configPathOf co)
+ environment <- lift (M.fromList . map (bimap T.pack T.pack) <$> getEnvironment)
+ manager <- lift $ newManager tlsManagerSettings
+ isTerm <- lift $ hIsTerminalDevice stdout
+ fromGroups <- lift . maybe (pure S.empty) groupPackages . NES.fromSet $ getIgnoredGroups confFile <> igg
+ let language = checkLang lng environment
+ bu <- failWith (Failure whoIsBuildUser_1) $ buildUserOf bc <|> getTrueUser environment
+ pure Settings { managerOf = manager
+ , envOf = environment
+ , langOf = language
+ , editorOf = getEditor environment
+ , isTerminal = isTerm
+ , ignoresOf = getIgnoredPkgs confFile <> fromGroups <> ign
+ , commonConfigOf =
+ -- | These maintain the precedence order: flags, config file entry, default
+ co { cachePathOf = first (\x -> fromMaybe x $ getCachePath confFile) $ cachePathOf co
+ , logPathOf = first (\x -> fromMaybe x $ getLogFilePath confFile) $ logPathOf co }
+ , buildConfigOf = bc { buildUserOf = Just bu}
+ }
+
+checkLang :: Maybe Language -> Environment -> Language
+checkLang Nothing env = langFromLocale $ getLocale env
+checkLang (Just lang) _ = lang
diff --git a/exec/aura.hs b/exec/aura.hs
new file mode 100644
index 0000000..bc4e79b
--- /dev/null
+++ b/exec/aura.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds, FlexibleContexts, MonoLocalBinds, TypeApplications #-}
+
+{-
+
+Copyright 2012 - 2018 Colin Woodbury <colin@fosskers.ca>
+
+This file is part of Aura.
+
+Aura is free s
+
+ oftwar
+ e:youcanredist
+ ributeitand/ormodify
+ itunderthetermsoftheGN
+ UGeneralPublicLicenseasp
+ ublishedbytheFreeSoftw
+ areFoundation,either ver sio n3o fth
+ eLicense,or(atyou ropti on)an ylate rvers
+ion.Auraisdistr ibutedi nthehop ethatit willbeu
+ seful,butWITHOUTA NYWAR RANTY ;with outev
+ entheimpliedwarranty ofM ERC HAN TAB
+ ILITYorFITNESSFORAPART
+ ICULARPURPOSE.SeetheGNUG
+ eneralPublicLicensefor
+ moredetails.Youshoul
+ dhavereceiveda
+ copyof
+
+the GNU General Public License
+along with Aura. If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+module Main ( main ) where
+
+import Aura.Colour (dtot)
+import Aura.Commands.A as A
+import Aura.Commands.B as B
+import Aura.Commands.C as C
+import Aura.Commands.L as L
+import Aura.Commands.O as O
+import Aura.Core
+import Aura.Languages
+import Aura.Logo
+import Aura.Pacman
+import Aura.Settings
+import Aura.Types
+import BasePrelude hiding (Version)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import qualified Data.Set as S
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Flags
+import Options.Applicative (execParser)
+import Settings
+import System.Path (toFilePath)
+import System.Process.Typed (proc, runProcess)
+import Text.Pretty.Simple (pPrintNoColor)
+
+---
+
+auraVersion :: T.Text
+auraVersion = "2.0.0"
+
+main :: IO ()
+main = do
+ options <- execParser opts
+ esettings <- getSettings options
+ case esettings of
+ Left err -> T.putStrLn . dtot . ($ English) $ failure err
+ Right ss -> execute ss options >>= exit ss
+
+execute :: Settings -> Program -> IO (Either (Doc AnsiStyle) ())
+execute ss p = first (($ langOf ss) . failure) <$> (runM . runReader ss . runError . executeOpts $ _operation p)
+
+exit :: Settings -> Either (Doc AnsiStyle) () -> IO a
+exit ss (Left e) = scold ss e *> exitFailure
+exit _ (Right _) = exitSuccess
+
+executeOpts :: Either (PacmanOp, S.Set MiscOp) AuraOp -> Eff '[Error Failure, Reader Settings, IO] ()
+executeOpts ops = do
+ ss <- ask
+ when (shared ss Debug) $ do
+ pPrintNoColor ops
+ pPrintNoColor (buildConfigOf ss)
+ pPrintNoColor (commonConfigOf ss)
+ let p (ps, ms) = liftEitherM . pacman $
+ asFlag ps
+ ++ foldMap asFlag ms
+ ++ asFlag (commonConfigOf ss)
+ ++ bool [] ["--quiet"] (switch ss LowVerbosity)
+ case ops of
+ Left o@(Sync (Left (SyncUpgrade _)) _, _) -> sudo (send $ B.saveState ss) *> p o
+ Left o -> p o
+ Right (AurSync o _) ->
+ case o of
+ Right ps -> bool (trueRoot . sudo) id (switch ss DryRun) $ A.install ps
+ Left (AurDeps ps) -> A.displayPkgDeps ps
+ Left (AurInfo ps) -> A.aurPkgInfo ps
+ Left (AurPkgbuild ps) -> A.displayPkgbuild ps
+ Left (AurSearch s) -> A.aurPkgSearch s
+ Left (AurUpgrade ps) -> bool (trueRoot . sudo) id (switch ss DryRun) $ A.upgradeAURPkgs ps
+ Left (AurJson ps) -> A.aurJson ps
+ Right (Backup o) ->
+ case o of
+ Nothing -> sudo . send $ B.saveState ss
+ Just (BackupClean n) -> sudo . send $ B.cleanStates ss n
+ Just BackupRestore -> sudo B.restoreState
+ Just BackupList -> send B.listStates
+ Right (Cache o) ->
+ case o of
+ Right ps -> sudo $ C.downgradePackages ps
+ Left (CacheSearch s) -> C.searchCache s
+ Left (CacheClean n) -> sudo $ C.cleanCache n
+ Left CacheCleanNotSaved -> sudo C.cleanNotSaved
+ Left (CacheBackup pth) -> sudo $ C.backupCache pth
+ Right (Log o) ->
+ case o of
+ Nothing -> L.viewLogFile
+ Just (LogInfo ps) -> L.logInfoOnPkg ps
+ Just (LogSearch s) -> ask >>= send . flip L.searchLogFile s
+ Right (Orphans o) ->
+ case o of
+ Nothing -> send O.displayOrphans
+ Just OrphanAbandon -> sudo $ send orphans >>= traverse_ removePkgs . NES.fromSet
+ Just (OrphanAdopt ps) -> O.adoptPkg ps
+ Right Version -> send $ getVersionInfo >>= animateVersionMsg ss auraVersion
+ Right Languages -> displayOutputLanguages
+ Right ViewConf -> viewConfFile
+
+displayOutputLanguages :: (Member (Reader Settings) r, Member IO r) => Eff r ()
+displayOutputLanguages = do
+ ss <- ask
+ send . notify ss . displayOutputLanguages_1 $ langOf ss
+ send $ traverse_ print [English ..]
+
+viewConfFile :: (Member (Reader Settings) r, Member IO r) => Eff r ()
+viewConfFile = do
+ pth <- asks (either id id . configPathOf . commonConfigOf)
+ send . void . runProcess @IO $ proc "less" [toFilePath pth]
diff --git a/lib/Aura/Build.hs b/lib/Aura/Build.hs
new file mode 100644
index 0000000..77b22b9
--- /dev/null
+++ b/lib/Aura/Build.hs
@@ -0,0 +1,144 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts, TypeApplications, MonoLocalBinds, DataKinds #-}
+
+-- |
+-- Module : Aura.Build
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Agnostically builds packages, regardless of original source.
+
+module Aura.Build
+ ( installPkgFiles
+ , buildPackages
+ ) where
+
+import Aura.Core
+import Aura.Languages
+import Aura.MakePkg
+import Aura.Packages.AUR (clone)
+import Aura.Pacman (pacman)
+import Aura.Settings
+import Aura.Types
+import Aura.Utils
+import BasePrelude
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Except
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import qualified Data.List.NonEmpty as NEL
+import Data.Semigroup.Foldable (fold1)
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Data.Witherable (wither)
+import Lens.Micro ((^.))
+import System.Directory (setCurrentDirectory)
+import System.IO (hFlush, stdout)
+import System.Path
+import System.Path.IO
+import System.Process.Typed
+import System.Random.MWC (GenIO, uniform, createSystemRandom)
+
+---
+
+srcPkgStore :: Path Absolute
+srcPkgStore = fromAbsoluteFilePath "/var/cache/aura/src"
+
+-- | Expects files like: \/var\/cache\/pacman\/pkg\/*.pkg.tar.xz
+installPkgFiles :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet PackagePath -> Eff r ()
+installPkgFiles files = do
+ ss <- ask
+ send $ checkDBLock ss
+ liftEitherM . pacman $ ["-U"] <> map (toFilePath . path) (toList files) <> asFlag (commonConfigOf ss)
+
+-- | All building occurs within temp directories,
+-- or in a location specified by the user with flags.
+buildPackages :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet Buildable -> Eff r (NonEmptySet PackagePath)
+buildPackages bs = do
+ g <- send createSystemRandom
+ wither (build g) (toList bs) >>= maybe bad (pure . fold1) . NEL.nonEmpty
+ where bad = throwError $ Failure buildFail_10
+
+-- | Handles the building of Packages. Fails nicely.
+-- Assumed: All dependencies are already installed.
+build :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ GenIO -> Buildable -> Eff r (Maybe (NonEmptySet PackagePath))
+build g p = do
+ ss <- ask
+ send $ notify ss (buildPackages_1 (p ^. field @"name") (langOf ss)) *> hFlush stdout
+ result <- send $ build' ss g p
+ either buildFail (pure . Just) result
+
+-- | Should never throw an IO Exception. In theory all errors
+-- will come back via the @Language -> String@ function.
+build' :: Settings -> GenIO -> Buildable -> IO (Either Failure (NonEmptySet PackagePath))
+build' ss g b = do
+ let pth = buildPathOf $ buildConfigOf ss
+ createDirectoryIfMissing True pth
+ setCurrentDirectory $ toFilePath pth
+ buildDir <- randomDirName g b
+ createDirectoryIfMissing True buildDir
+ setCurrentDirectory $ toFilePath buildDir
+ runExceptT $ do
+ bs <- ExceptT $ cloneRepo b usr
+ lift . setCurrentDirectory $ toFilePath bs
+ lift $ overwritePkgbuild ss b
+ pNames <- ExceptT $ makepkg ss usr
+ paths <- lift . fmap NES.fromNonEmpty . traverse (moveToCachePath ss) $ NES.toNonEmpty pNames
+ lift . when (S.member AllSource . makepkgFlagsOf $ buildConfigOf ss) $
+ makepkgSource usr >>= traverse_ moveToSourcePath
+ pure paths
+ where usr = fromMaybe (User "桜木花道") . buildUserOf $ buildConfigOf ss
+
+-- | Create a temporary directory with a semi-random name based on
+-- the `Buildable` we're working with.
+randomDirName :: GenIO -> Buildable -> IO (Path Absolute)
+randomDirName g b = do
+ pwd <- getCurrentDirectory
+ v <- uniform g :: IO Word
+ let dir = T.unpack (b ^. field @"name" . field @"name") <> "-" <> show v
+ pure $ pwd </> fromUnrootedFilePath dir
+
+cloneRepo :: Buildable -> User -> IO (Either Failure (Path Absolute))
+cloneRepo pkg usr = do
+ currDir <- getCurrentDirectory
+ scriptsDir <- chown usr currDir [] *> clone pkg
+ case scriptsDir of
+ Nothing -> pure . Left . Failure . buildFail_7 $ pkg ^. field @"name"
+ Just sd -> chown usr sd ["-R"] $> Right sd
+
+-- | The user may have edited the original PKGBUILD. If they have, we need to
+-- overwrite what's been downloaded before calling `makepkg`.
+overwritePkgbuild :: Settings -> Buildable -> IO ()
+overwritePkgbuild ss p = when (switch ss HotEdit || switch ss UseCustomizepkg) $
+ BL.writeFile "PKGBUILD" $ p ^. field @"pkgbuild" . field @"pkgbuild"
+
+-- | Inform the user that building failed. Ask them if they want to
+-- continue installing previous packages that built successfully.
+buildFail :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Failure -> Eff r (Maybe a)
+buildFail (Failure err) = do
+ ss <- ask
+ send . scold ss . err $ langOf ss
+ response <- send $ optionalPrompt ss buildFail_6
+ bool (throwError $ Failure buildFail_5) (pure Nothing) response
+
+-- | Moves a file to the pacman package cache and returns its location.
+moveToCachePath :: Settings -> Path Absolute -> IO PackagePath
+moveToCachePath ss p = copy $> PackagePath newName
+ where newName = pth </> takeFileName p
+ pth = either id id . cachePathOf $ commonConfigOf ss
+ copy = runProcess . setStderr closed . setStdout closed
+ $ proc "cp" ["--reflink=auto", toFilePath p, toFilePath newName ]
+
+-- | Moves a file to the aura src package cache and returns its location.
+moveToSourcePath :: Path Absolute -> IO (Path Absolute)
+moveToSourcePath p = renameFile p newName $> newName
+ where newName = srcPkgStore </> takeFileName p
diff --git a/lib/Aura/Cache.hs b/lib/Aura/Cache.hs
new file mode 100644
index 0000000..8112d0d
--- /dev/null
+++ b/lib/Aura/Cache.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections, TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Cache
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Reading and searching the package cache.
+
+module Aura.Cache
+ ( -- * Types
+ Cache(..)
+ , cacheContents
+ -- * Misc.
+ , defaultPackageCache
+ , cacheMatches
+ , pkgsInCache
+ ) where
+
+import Aura.Settings
+import Aura.Types
+import BasePrelude
+import Data.Generics.Product (field)
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Lens.Micro ((^.))
+import System.Path (Path, Absolute, (</>), toFilePath, fromAbsoluteFilePath)
+import System.Path.IO (getDirectoryContents)
+
+---
+
+-- | Every package in the current cache, paired with its original filename.
+newtype Cache = Cache { _cache :: M.Map SimplePkg PackagePath }
+
+-- | The default location of the package cache: \/var\/cache\/pacman\/pkg\/
+defaultPackageCache :: Path Absolute
+defaultPackageCache = fromAbsoluteFilePath "/var/cache/pacman/pkg/"
+
+-- SILENT DROPS PATHS THAT DON'T PARSE
+-- Maybe that's okay, since we don't know what non-package garbage files
+-- could be sitting in the cache.
+-- | Given every filepath contained in the package cache, form
+-- the `Cache` type.
+cache :: [PackagePath] -> Cache
+cache = Cache . M.fromList . mapMaybe (\p -> (,p) <$> simplepkg p)
+
+-- | Given a path to the package cache, yields its contents in a usable form.
+cacheContents :: Path Absolute -> IO Cache
+cacheContents pth = cache . map (PackagePath . (pth </>)) <$> getDirectoryContents pth
+
+-- | All packages from a given `S.Set` who have a copy in the cache.
+pkgsInCache :: Settings -> NonEmptySet PkgName -> IO (S.Set PkgName)
+pkgsInCache ss ps = do
+ c <- cacheContents . either id id . cachePathOf $ commonConfigOf ss
+ pure . S.filter (`NES.member` ps) . S.map (^. field @"name") . M.keysSet $ _cache c
+
+-- | Any entries (filepaths) in the cache that match a given `T.Text`.
+cacheMatches :: Settings -> T.Text -> IO [PackagePath]
+cacheMatches ss input = do
+ c <- cacheContents . either id id . cachePathOf $ commonConfigOf ss
+ pure . filter (T.isInfixOf input . T.pack . toFilePath . path) . M.elems $ _cache c
diff --git a/lib/Aura/Colour.hs b/lib/Aura/Colour.hs
new file mode 100644
index 0000000..fee7943
--- /dev/null
+++ b/lib/Aura/Colour.hs
@@ -0,0 +1,49 @@
+-- |
+-- Module : Aura.Colour
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Annotate `Doc` text with various colours.
+
+module Aura.Colour
+ ( -- * Render to Text
+ dtot
+ -- * Colours
+ , cyan, bCyan, green, yellow, red, magenta
+ ) where
+
+import BasePrelude
+import Data.Text (Text)
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+
+---
+
+-- | Render an assembled `Doc` into strict `Text`.
+dtot :: Doc AnsiStyle -> Text
+dtot = renderStrict . layoutPretty defaultLayoutOptions
+
+-- | Colour a `Doc` cyan.
+cyan :: Doc AnsiStyle -> Doc AnsiStyle
+cyan = annotate (color Cyan)
+
+-- | Colour a `Doc` cyan and bold.
+bCyan :: Doc AnsiStyle -> Doc AnsiStyle
+bCyan = annotate (color Cyan <> bold)
+
+-- | Colour a `Doc` green.
+green :: Doc AnsiStyle -> Doc AnsiStyle
+green = annotate (color Green)
+
+-- | Colour a `Doc` yellow.
+yellow :: Doc AnsiStyle -> Doc AnsiStyle
+yellow = annotate (color Yellow)
+
+-- | Colour a `Doc` red.
+red :: Doc AnsiStyle -> Doc AnsiStyle
+red = annotate (color Red)
+
+-- | Colour a `Doc` magenta.
+magenta :: Doc AnsiStyle -> Doc AnsiStyle
+magenta = annotate (color Magenta)
diff --git a/lib/Aura/Commands/A.hs b/lib/Aura/Commands/A.hs
new file mode 100644
index 0000000..8f66008
--- /dev/null
+++ b/lib/Aura/Commands/A.hs
@@ -0,0 +1,202 @@
+{-# LANGUAGE FlexibleContexts, TypeApplications, MonoLocalBinds, DataKinds #-}
+{-# LANGUAGE ViewPatterns, MultiWayIf, OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Commands.A
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle all @-A@ flags - those which involve viewing and building packages
+-- from the AUR.
+
+module Aura.Commands.A
+ ( I.install
+ , upgradeAURPkgs
+ , aurPkgInfo
+ , aurPkgSearch
+ , I.displayPkgDeps
+ , displayPkgbuild
+ , aurJson
+ ) where
+
+import Aura.Colour
+import Aura.Core
+import qualified Aura.Install as I
+import Aura.Languages
+import Aura.Packages.AUR
+import Aura.Pkgbuild.Fetch
+import Aura.Settings
+import Aura.State (saveState)
+import Aura.Types
+import Aura.Utils
+import BasePrelude hiding ((<+>))
+import Control.Error.Util (hush)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder)
+import Data.Generics.Product (field)
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Text.Lazy (toStrict)
+import Data.Text.Lazy.Builder (toLazyText)
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Data.Versions (Versioning, versioning, prettyV)
+import Lens.Micro ((^.), (^..), each)
+import Lens.Micro.Extras (view)
+import Linux.Arch.Aur
+
+---
+
+-- | The result of @-Au@.
+upgradeAURPkgs :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => S.Set PkgName -> Eff r ()
+upgradeAURPkgs pkgs = do
+ ss <- ask
+ send . notify ss . upgradeAURPkgs_1 $ langOf ss
+ send (foreigns ss) >>= traverse_ (upgrade pkgs) . NES.fromSet
+
+-- | Foreign packages to consider for upgrading, after "ignored packages" have
+-- been taken into consideration.
+foreigns :: Settings -> IO (S.Set SimplePkg)
+foreigns ss = S.filter (notIgnored . view (field @"name")) <$> foreignPackages
+ where notIgnored p = not . S.member p $ ignoresOf ss
+
+upgrade :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ S.Set PkgName -> NonEmptySet SimplePkg -> Eff r ()
+upgrade pkgs fs = do
+ ss <- ask
+ toUpgrade <- possibleUpdates fs
+ let !names = map (PkgName . aurNameOf . fst) toUpgrade
+ auraFirst <- auraCheck names
+ case auraFirst of
+ Just a -> auraUpgrade a
+ Nothing -> do
+ devel <- develPkgCheck
+ send . notify ss . upgradeAURPkgs_2 $ langOf ss
+ if | null toUpgrade && null devel -> send . warn ss . upgradeAURPkgs_3 $ langOf ss
+ | otherwise -> do
+ reportPkgsToUpgrade toUpgrade (toList devel)
+ send . unless (switch ss DryRun) $ saveState ss
+ traverse_ I.install . NES.fromSet $ S.fromList names <> pkgs <> devel
+
+possibleUpdates :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet SimplePkg -> Eff r [(AurInfo, Versioning)]
+possibleUpdates (NES.toNonEmpty -> pkgs) = do
+ aurInfos <- aurInfo $ fmap (^. field @"name") pkgs
+ let !names = map aurNameOf aurInfos
+ aurPkgs = NEL.filter (\(SimplePkg (PkgName n) _) -> n `elem` names) pkgs
+ pure . filter isntMostRecent . zip aurInfos $ aurPkgs ^.. each . field @"version"
+
+-- | Is there an update for Aura that we could apply first?
+auraCheck :: (Member (Reader Settings) r, Member IO r) => [PkgName] -> Eff r (Maybe PkgName)
+auraCheck ps = join <$> traverse f auraPkg
+ where f a = do
+ ss <- ask
+ bool Nothing (Just a) <$> send (optionalPrompt ss auraCheck_1)
+ auraPkg | "aura" `elem` ps = Just "aura"
+ | "aura-bin" `elem` ps = Just "aura-bin"
+ | otherwise = Nothing
+
+auraUpgrade :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => PkgName -> Eff r ()
+auraUpgrade = I.install . NES.singleton
+
+develPkgCheck :: (Member (Reader Settings) r, Member IO r) => Eff r (S.Set PkgName)
+develPkgCheck = ask >>= \ss ->
+ if switch ss RebuildDevel then send develPkgs else pure S.empty
+
+-- | The result of @-Ai@.
+aurPkgInfo :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+aurPkgInfo = aurInfo . NES.toNonEmpty >=> traverse_ displayAurPkgInfo
+
+displayAurPkgInfo :: (Member (Reader Settings) r, Member IO r) => AurInfo -> Eff r ()
+displayAurPkgInfo ai = ask >>= \ss -> send . T.putStrLn $ renderAurPkgInfo ss ai <> "\n"
+
+renderAurPkgInfo :: Settings -> AurInfo -> T.Text
+renderAurPkgInfo ss ai = dtot . colourCheck ss $ entrify ss fields entries
+ where fields = infoFields . langOf $ ss
+ entries = [ magenta "aur"
+ , annotate bold . pretty $ aurNameOf ai
+ , pretty $ aurVersionOf ai
+ , outOfDateMsg (dateObsoleteOf ai) $ langOf ss
+ , orphanedMsg (aurMaintainerOf ai) $ langOf ss
+ , cyan . maybe "(null)" pretty $ urlOf ai
+ , pretty . pkgUrl . PkgName $ aurNameOf ai
+ , pretty . T.unwords $ licenseOf ai
+ , pretty . T.unwords $ dependsOf ai
+ , pretty . T.unwords $ makeDepsOf ai
+ , yellow . pretty $ aurVotesOf ai
+ , yellow . pretty . T.pack . printf "%0.2f" $ popularityOf ai
+ , maybe "(null)" pretty $ aurDescriptionOf ai ]
+
+-- | The result of @-As@.
+aurPkgSearch :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => T.Text -> Eff r ()
+aurPkgSearch regex = do
+ ss <- ask
+ db <- S.map (^. field @"name" . field @"name") <$> send foreignPackages
+ let t = case truncationOf $ buildConfigOf ss of -- Can't this go anywhere else?
+ None -> id
+ Head n -> take $ fromIntegral n
+ Tail n -> reverse . take (fromIntegral n) . reverse
+ results <- fmap (\x -> (x, aurNameOf x `S.member` db)) . t
+ <$> aurSearch regex
+ send $ traverse_ (T.putStrLn . renderSearch ss regex) results
+
+renderSearch :: Settings -> T.Text -> (AurInfo, Bool) -> T.Text
+renderSearch ss r (i, e) = searchResult
+ where searchResult = if switch ss LowVerbosity then sparseInfo else dtot $ colourCheck ss verboseInfo
+ sparseInfo = aurNameOf i
+ verboseInfo = repo <> n <+> v <+> "(" <> l <+> "|" <+> p <>
+ ")" <> (if e then annotate bold " [installed]" else "") <> "\n " <> d
+ repo = magenta "aur/"
+ n = fold . intersperse (bCyan $ pretty r) . map (annotate bold . pretty) . T.splitOn r $ aurNameOf i
+ d = maybe "(null)" pretty $ aurDescriptionOf i
+ l = yellow . pretty $ aurVotesOf i -- `l` for likes?
+ p = yellow . pretty . T.pack . printf "%0.2f" $ popularityOf i
+ v = case dateObsoleteOf i of
+ Just _ -> red . pretty $ aurVersionOf i
+ Nothing -> green . pretty $ aurVersionOf i
+
+-- | The result of @-Ap@.
+displayPkgbuild :: (Member (Reader Settings) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+displayPkgbuild ps = do
+ man <- asks managerOf
+ pbs <- catMaybes <$> traverse (send . getPkgbuild @IO man) (toList ps)
+ send . traverse_ (T.putStrLn . strictText) $ pbs ^.. each . field @"pkgbuild"
+
+isntMostRecent :: (AurInfo, Versioning) -> Bool
+isntMostRecent (ai, v) = trueVer > Just v
+ where trueVer = hush . versioning $ aurVersionOf ai
+
+-- | Similar to @-Ai@, but yields the raw data as JSON instead.
+aurJson :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+aurJson ps = do
+ m <- asks managerOf
+ infos <- liftMaybeM (Failure connectionFailure_1) . info m . (^.. each . field @"name") $ toList ps
+ let json = map (toStrict . toLazyText . encodePrettyToTextBuilder) infos
+ send $ traverse_ T.putStrLn json
+
+------------
+-- REPORTING
+------------
+reportPkgsToUpgrade :: (Member (Reader Settings) r, Member IO r) =>
+ [(AurInfo, Versioning)] -> [PkgName] -> Eff r ()
+reportPkgsToUpgrade ups pns = do
+ ss <- ask
+ send . notify ss . reportPkgsToUpgrade_1 $ langOf ss
+ send $ putDoc (colourCheck ss . vcat $ map f ups' <> map g devels) >> T.putStrLn "\n"
+ where devels = pns ^.. each . field @"name"
+ ups' = map (second prettyV) ups
+ nLen = maximum $ map (T.length . aurNameOf . fst) ups <> map T.length devels
+ vLen = maximum $ map (T.length . snd) ups'
+ g = annotate (color Cyan) . pretty
+ f (p, v) = hsep [ cyan . fill nLen . pretty $ aurNameOf p
+ , "::"
+ , yellow . fill vLen $ pretty v
+ , "->"
+ , green . pretty $ aurVersionOf p ]
diff --git a/lib/Aura/Commands/B.hs b/lib/Aura/Commands/B.hs
new file mode 100644
index 0000000..e62d5c0
--- /dev/null
+++ b/lib/Aura/Commands/B.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE MultiWayIf, ViewPatterns, TupleSections #-}
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds #-}
+
+-- |
+-- Module : Aura.Commands.B
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle all @-B@ flags - those which involve saved package states.
+
+module Aura.Commands.B
+ ( saveState
+ , restoreState
+ , cleanStates
+ , listStates
+ ) where
+
+import Aura.Core (warn)
+import Aura.Languages
+import Aura.Settings
+import Aura.State
+import Aura.Utils (optionalPrompt)
+import BasePrelude
+import qualified Data.Text as T
+import System.Path (toFilePath, takeFileName, toUnrootedFilePath)
+import System.Path.IO (removeFile)
+
+---
+
+-- | Remove all but the newest @n@ package states. Any "pinned" states will also remain.
+cleanStates :: Settings -> Word -> IO ()
+cleanStates ss (fromIntegral -> n) = do
+ stfs <- reverse <$> getStateFiles
+ (pinned, others) <- partition p <$> traverse (\sf -> (sf,) <$> readState sf) stfs
+ warn ss . cleanStates_4 (length stfs) $ langOf ss
+ unless (null pinned) . warn ss . cleanStates_6 (length pinned) $ langOf ss
+ unless (null stfs) . warn ss . cleanStates_5 (T.pack . toUnrootedFilePath . takeFileName $ head stfs) $ langOf ss
+ okay <- optionalPrompt ss $ cleanStates_2 n
+ if | not okay -> warn ss . cleanStates_3 $ langOf ss
+ | otherwise -> traverse_ (removeFile . fst) . drop n $ others
+ where p = maybe False pinnedOf . snd
+
+-- | The result of @-Bl@.
+listStates :: IO ()
+listStates = getStateFiles >>= traverse_ (putStrLn . toFilePath)
diff --git a/lib/Aura/Commands/C.hs b/lib/Aura/Commands/C.hs
new file mode 100644
index 0000000..0d809ab
--- /dev/null
+++ b/lib/Aura/Commands/C.hs
@@ -0,0 +1,155 @@
+{-# LANGUAGE FlexibleContexts, TypeApplications, MonoLocalBinds, DataKinds #-}
+{-# LANGUAGE OverloadedStrings, MultiWayIf #-}
+
+-- |
+-- Module : Aura.Commands.C
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle all @-C@ flags - those which involve the package cache.
+
+module Aura.Commands.C
+ ( downgradePackages
+ , searchCache
+ , backupCache
+ , cleanCache
+ , cleanNotSaved
+ ) where
+
+import Aura.Cache
+import Aura.Colour (red)
+import Aura.Core
+import Aura.Languages
+import Aura.Pacman (pacman)
+import Aura.Settings
+import Aura.State
+import Aura.Types
+import Aura.Utils
+import BasePrelude
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Data.Generics.Product (field)
+import Data.List.NonEmpty (nonEmpty)
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Lens.Micro ((^?), _Just)
+import System.Path
+import System.Path.IO (doesDirectoryExist, removeFile, copyFile)
+
+---
+
+-- | Interactive. Gives the user a choice as to exactly what versions
+-- they want to downgrade to.
+downgradePackages :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet PkgName -> Eff r ()
+downgradePackages pkgs = do
+ ss <- ask
+ let cachePath = either id id . cachePathOf $ commonConfigOf ss
+ reals <- send $ pkgsInCache ss pkgs
+ traverse_ (report red reportBadDowngradePkgs_1) . nonEmpty . toList $ NES.toSet pkgs S.\\ reals
+ unless (null reals) $ do
+ cache <- send $ cacheContents cachePath
+ choices <- traverse (getDowngradeChoice cache) $ toList reals
+ liftEitherM . pacman $ "-U" : asFlag (commonConfigOf ss) <> map (toFilePath . path) choices
+
+-- | For a given package, get a choice from the user about which version of it to
+-- downgrade to.
+getDowngradeChoice :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ Cache -> PkgName -> Eff r PackagePath
+getDowngradeChoice cache pkg =
+ case nonEmpty $ getChoicesFromCache cache pkg of
+ Nothing -> throwError . Failure $ reportBadDowngradePkgs_2 pkg
+ Just choices -> do
+ ss <- ask
+ send . notify ss . getDowngradeChoice_1 pkg $ langOf ss
+ send $ getSelection (T.pack . toFilePath . path) choices
+
+getChoicesFromCache :: Cache -> PkgName -> [PackagePath]
+getChoicesFromCache (Cache cache) p = sort . M.elems $ M.filterWithKey (\(SimplePkg pn _) _ -> p == pn) cache
+
+-- | Print all package filenames that match a given `T.Text`.
+searchCache :: (Member (Reader Settings) r, Member IO r) => T.Text -> Eff r ()
+searchCache ps = do
+ ss <- ask
+ matches <- send $ cacheMatches ss ps
+ send . traverse_ (putStrLn . toFilePath . path) $ sort matches
+
+-- | The destination folder must already exist for the back-up to begin.
+backupCache :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Path Absolute -> Eff r ()
+backupCache dir = do
+ exists <- send $ doesDirectoryExist dir
+ if | not exists -> throwError $ Failure backupCache_3
+ | otherwise -> confirmBackup dir >>= backup dir
+
+confirmBackup :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Path Absolute -> Eff r Cache
+confirmBackup dir = do
+ ss <- ask
+ cache <- send . cacheContents . either id id . cachePathOf $ commonConfigOf ss
+ send . notify ss $ backupCache_4 (toFilePath dir) (langOf ss)
+ send . notify ss $ backupCache_5 (M.size $ _cache cache) (langOf ss)
+ okay <- send $ optionalPrompt ss backupCache_6
+ bool (throwError $ Failure backupCache_7) (pure cache) okay
+
+backup :: (Member (Reader Settings) r, Member IO r) => Path Absolute -> Cache -> Eff r ()
+backup dir (Cache cache) = do
+ ss <- ask
+ send . notify ss . backupCache_8 $ langOf ss
+ send $ putStrLn "" -- So that the cursor can rise at first.
+ copyAndNotify dir (M.elems cache) 1
+
+-- | Manages the file copying and display of the real-time progress notifier.
+copyAndNotify :: (Member (Reader Settings) r, Member IO r) => Path Absolute -> [PackagePath] -> Int -> Eff r ()
+copyAndNotify _ [] _ = pure ()
+copyAndNotify dir (PackagePath p : ps) n = do
+ ss <- ask
+ send $ raiseCursorBy 1
+ send . warn ss . copyAndNotify_1 n $ langOf ss
+ send $ copyFile p dir
+ copyAndNotify dir ps $ n + 1
+
+-- | Keeps a certain number of package files in the cache according to
+-- a number provided by the user. The rest are deleted.
+cleanCache :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Word -> Eff r ()
+cleanCache toSave
+ | toSave == 0 = ask >>= \ss -> send (warn ss . cleanCache_2 $ langOf ss) >> liftEitherM (pacman ["-Scc"])
+ | otherwise = do
+ ss <- ask
+ send . warn ss . cleanCache_3 toSave $ langOf ss
+ okay <- send $ optionalPrompt ss cleanCache_4
+ bool (throwError $ Failure cleanCache_5) (clean (fromIntegral toSave)) okay
+
+clean :: (Member (Reader Settings) r, Member IO r) => Int -> Eff r ()
+clean toSave = do
+ ss <- ask
+ send . notify ss . cleanCache_6 $ langOf ss
+ let cachePath = either id id . cachePathOf $ commonConfigOf ss
+ (Cache cache) <- send $ cacheContents cachePath
+ let !files = M.elems cache
+ grouped = take toSave . reverse <$> groupByName files
+ toRemove = files \\ fold grouped
+ send $ traverse_ removeFile $ map path toRemove
+
+-- | Only package files with a version not in any PkgState will be
+-- removed.
+cleanNotSaved :: (Member (Reader Settings) r, Member IO r) => Eff r ()
+cleanNotSaved = do
+ ss <- ask
+ send . notify ss . cleanNotSaved_1 $ langOf ss
+ sfs <- send getStateFiles
+ states <- fmap catMaybes . send $ traverse readState sfs
+ let cachePath = either id id . cachePathOf $ commonConfigOf ss
+ (Cache cache) <- send $ cacheContents cachePath
+ let duds = M.filterWithKey (\p _ -> any (inState p) states) cache
+ prop <- send . optionalPrompt ss $ cleanNotSaved_2 $ M.size duds
+ when prop . send . traverse_ removeFile . map path $ M.elems duds
+
+-- | Typically takes the contents of the package cache as an argument.
+groupByName :: [PackagePath] -> [[PackagePath]]
+groupByName pkgs = groupBy sameBaseName $ sort pkgs
+ where sameBaseName a b = baseName a == baseName b
+ baseName p = simplepkg p ^? _Just . field @"name"
diff --git a/lib/Aura/Commands/L.hs b/lib/Aura/Commands/L.hs
new file mode 100644
index 0000000..ef9bfc0
--- /dev/null
+++ b/lib/Aura/Commands/L.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Commands.L
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle all @-L@ flags - those which involve the pacman log file.
+
+module Aura.Commands.L
+ ( viewLogFile
+ , searchLogFile
+ , logInfoOnPkg
+ ) where
+
+import Aura.Colour (red, dtot)
+import Aura.Core (report)
+import Aura.Languages
+import Aura.Settings
+import Aura.Types (PkgName(..))
+import Aura.Utils
+import BasePrelude hiding (FilePath)
+import Control.Compactable (fmapEither)
+import Control.Monad.Freer
+import Control.Monad.Freer.Reader
+import qualified Data.ByteString.Char8 as BS
+import Data.Generics.Product (field)
+import qualified Data.List.NonEmpty as NEL
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Text as T
+import Data.Text.Encoding as T
+import Data.Text.Encoding.Error (lenientDecode)
+import qualified Data.Text.IO as T
+import Data.Text.Prettyprint.Doc
+import Lens.Micro ((^.))
+import System.Path (toFilePath)
+import System.Process.Typed (proc, runProcess)
+
+---
+
+-- | The contents of the Pacman log file.
+newtype Log = Log [T.Text]
+
+data LogEntry = LogEntry { _pkgName :: PkgName, _firstInstall :: T.Text, _upgrades :: Word, _recent :: [T.Text] }
+
+-- | Pipes the pacman log file through a @less@ session.
+viewLogFile :: (Member (Reader Settings) r, Member IO r) => Eff r ()
+viewLogFile = do
+ pth <- asks (toFilePath . either id id . logPathOf . commonConfigOf)
+ send . void . runProcess @IO $ proc "less" [pth]
+
+-- | Print all lines in the log file which contain a given `T.Text`.
+searchLogFile :: Settings -> T.Text -> IO ()
+searchLogFile ss input = do
+ let pth = toFilePath . either id id . logPathOf $ commonConfigOf ss
+ logFile <- map (T.decodeUtf8With lenientDecode) . BS.lines <$> BS.readFile pth
+ traverse_ T.putStrLn $ searchLines input logFile
+
+-- | The result of @-Li@.
+logInfoOnPkg :: (Member (Reader Settings) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+logInfoOnPkg pkgs = do
+ ss <- ask
+ let pth = toFilePath . either id id . logPathOf $ commonConfigOf ss
+ logFile <- Log . map (T.decodeUtf8With lenientDecode) . BS.lines <$> send (BS.readFile pth)
+ let (bads, goods) = fmapEither (logLookup logFile) $ toList pkgs
+ traverse_ (report red reportNotInLog_1) $ NEL.nonEmpty bads
+ send . traverse_ T.putStrLn $ map (renderEntry ss) goods
+
+logLookup :: Log -> PkgName -> Either PkgName LogEntry
+logLookup (Log lns) p = case matches of
+ [] -> Left p
+ (h:t) -> Right $ LogEntry p
+ (T.take 16 $ T.tail h)
+ (fromIntegral . length $ filter (T.isInfixOf " upgraded ") t)
+ (reverse . take 5 $ reverse t)
+ where matches = filter (T.isInfixOf (" " <> (p ^. field @"name") <> " (")) lns
+
+renderEntry :: Settings -> LogEntry -> T.Text
+renderEntry ss (LogEntry (PkgName pn) fi us rs) =
+ dtot . colourCheck ss $ entrify ss fields entries <> hardline <> recent <> hardline
+ where fields = logLookUpFields $ langOf ss
+ entries = map pretty [ pn, fi, T.pack (show us), "" ]
+ recent = vsep $ map pretty rs
diff --git a/lib/Aura/Commands/O.hs b/lib/Aura/Commands/O.hs
new file mode 100644
index 0000000..d6b5efb
--- /dev/null
+++ b/lib/Aura/Commands/O.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Commands.O
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle all @-O@ flags - those which involve orphan packages.
+
+module Aura.Commands.O ( displayOrphans, adoptPkg ) where
+
+import Aura.Core (orphans, sudo, liftEitherM)
+import Aura.Pacman (pacman)
+import Aura.Settings (Settings)
+import Aura.Types
+import BasePrelude
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Data.Generics.Product (field)
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Text.IO as T
+import Lens.Micro.Extras (view)
+
+---
+
+-- | Print the result of @pacman -Qqdt@
+displayOrphans :: IO ()
+displayOrphans = orphans >>= traverse_ (T.putStrLn . view (field @"name"))
+
+-- | Identical to @-D --asexplicit@.
+adoptPkg :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+adoptPkg pkgs = sudo . liftEitherM . pacman $ ["-D", "--asexplicit"] <> asFlag pkgs
diff --git a/lib/Aura/Core.hs b/lib/Aura/Core.hs
new file mode 100644
index 0000000..f58d413
--- /dev/null
+++ b/lib/Aura/Core.hs
@@ -0,0 +1,190 @@
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds, TypeApplications, DataKinds #-}
+{-# LANGUAGE MultiWayIf, OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Core
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Core types and functions which belong nowhere else.
+
+module Aura.Core
+ ( -- * Types
+ Repository(..)
+ , liftEither, liftEitherM
+ , liftMaybe, liftMaybeM
+ -- * User Privileges
+ , sudo, trueRoot
+ -- * Querying the Package Database
+ , foreignPackages, orphans, develPkgs
+ , isSatisfied, isInstalled
+ , checkDBLock
+ -- * Misc. Package Handling
+ , removePkgs, partitionPkgs, packageBuildable
+ -- * IO
+ , notify, warn, scold, report
+ ) where
+
+import Aura.Colour
+import Aura.Languages
+import Aura.Pacman
+import Aura.Pkgbuild.Editing (hotEdit)
+import Aura.Settings
+import Aura.Types
+import Aura.Utils
+import BasePrelude hiding ((<>))
+import Control.Compactable (fmapEither)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Control.Monad.Trans.Maybe
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import qualified Data.List.NonEmpty as NEL
+import Data.Semigroup
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Data.Versions (prettyV)
+import Lens.Micro ((^.))
+import Lens.Micro.Extras (view)
+import System.Path.IO (doesFileExist)
+
+---
+
+--------
+-- TYPES
+--------
+
+-- | A `Repository` is a place where packages may be fetched from. Multiple
+-- repositories can be combined with the `Semigroup` instance.
+-- Checks packages in batches for efficiency.
+newtype Repository = Repository { repoLookup :: Settings -> NonEmptySet PkgName -> IO (Maybe (S.Set PkgName, S.Set Package)) }
+
+instance Semigroup Repository where
+ a <> b = Repository $ \ss ps -> runMaybeT $
+ MaybeT (repoLookup a ss ps) >>= \(bads, goods) -> case NES.fromSet bads of
+ Nothing -> pure (bads, goods)
+ Just bads' -> second (goods <>) <$> MaybeT (repoLookup b ss bads')
+
+---------------------------------
+-- Functions common to `Package`s
+---------------------------------
+-- | Partition a list of packages into pacman and buildable groups.
+-- Yes, this is the correct signature. As far as this function (in isolation)
+-- is concerned, there is no way to guarantee that the list of `NonEmptySet`s
+-- will itself be non-empty.
+partitionPkgs :: NonEmpty (NonEmptySet Package) -> ([Prebuilt], [NonEmptySet Buildable])
+partitionPkgs = bimap fold f . unzip . map g . toList
+ where g = fmapEither toEither . toList
+ f = mapMaybe (fmap NES.fromNonEmpty . NEL.nonEmpty)
+ toEither (FromAUR b) = Right b
+ toEither (FromRepo b) = Left b
+
+-- | Package a Buildable, running the customization handler first.
+packageBuildable :: Settings -> Buildable -> IO Package
+packageBuildable ss b = FromAUR <$> hotEdit ss b
+
+-----------
+-- THE WORK
+-----------
+-- | Lift a common return type into the `Eff` world. Usually used after a `pacman` call.
+liftEither :: Member (Error a) r => Either a b -> Eff r b
+liftEither = either throwError pure
+
+-- | Like `liftEither`, but the `Either` can be embedded in something else,
+-- usually a `Monad`.
+liftEitherM :: (Member (Error a) r, Member m r) => m (Either a b) -> Eff r b
+liftEitherM = send >=> liftEither
+
+-- | Like `liftEither`, but for `Maybe`.
+liftMaybe :: Member (Error a) r => a -> Maybe b -> Eff r b
+liftMaybe a = maybe (throwError a) pure
+
+-- | Like `liftEitherM`, but for `Maybe`.
+liftMaybeM :: (Member (Error a) r, Member m r) => a -> m (Maybe b) -> Eff r b
+liftMaybeM a m = send m >>= liftMaybe a
+
+-- | Action won't be allowed unless user is root, or using sudo.
+sudo :: (Member (Reader Settings) r, Member (Error Failure) r) => Eff r a -> Eff r a
+sudo action = asks (hasRootPriv . envOf) >>= bool (throwError $ Failure mustBeRoot_1) action
+
+-- | Stop the user if they are the true root. Building as root isn't allowed
+-- since makepkg v4.2.
+trueRoot :: (Member (Reader Settings) r, Member (Error Failure) r) => Eff r a -> Eff r a
+trueRoot action = ask >>= \ss ->
+ if not (isTrueRoot $ envOf ss) && buildUserOf (buildConfigOf ss) /= Just (User "root")
+ then action else throwError $ Failure trueRoot_3
+
+-- | A list of non-prebuilt packages installed on the system.
+-- `-Qm` yields a list of sorted values.
+foreignPackages :: IO (S.Set SimplePkg)
+foreignPackages = S.fromList . mapMaybe (simplepkg' . strictText) . BL.lines <$> pacmanOutput ["-Qm"]
+
+-- | Packages marked as a dependency, yet are required by no other package.
+orphans :: IO (S.Set PkgName)
+orphans = S.fromList . map (PkgName . strictText) . BL.lines <$> pacmanOutput ["-Qqdt"]
+
+-- | Any package whose name is suffixed by git, hg, svn, darcs, cvs, or bzr.
+develPkgs :: IO (S.Set PkgName)
+develPkgs = S.filter isDevelPkg . S.map (^. field @"name") <$> foreignPackages
+ where isDevelPkg (PkgName pkg) = any (`T.isSuffixOf` pkg) suffixes
+ suffixes = ["-git", "-hg", "-svn", "-darcs", "-cvs", "-bzr"]
+
+-- | Returns what it was given if the package is already installed.
+-- Reasoning: Using raw bools can be less expressive.
+isInstalled :: PkgName -> IO (Maybe PkgName)
+isInstalled pkg = bool Nothing (Just pkg) <$> pacmanSuccess ["-Qq", T.unpack (pkg ^. field @"name")]
+
+-- | An @-Rsu@ call.
+removePkgs :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
+removePkgs pkgs = do
+ pacOpts <- asks commonConfigOf
+ liftEitherM . pacman $ ["-Rsu"] <> asFlag pkgs <> asFlag pacOpts
+
+-- | True if a dependency is satisfied by an installed package.
+-- `asT` renders the `VersionDemand` into the specific form that `pacman -T`
+-- understands. See `man pacman` for more info.
+isSatisfied :: Dep -> IO Bool
+isSatisfied (Dep n ver) = pacmanSuccess $ map T.unpack ["-T", (n ^. field @"name") <> asT ver]
+ where asT (LessThan v) = "<" <> prettyV v
+ asT (AtLeast v) = ">=" <> prettyV v
+ asT (MoreThan v) = ">" <> prettyV v
+ asT (MustBe v) = "=" <> prettyV v
+ asT Anything = ""
+
+-- | Block further action until the database is free.
+checkDBLock :: Settings -> IO ()
+checkDBLock ss = do
+ locked <- doesFileExist lockFile
+ when locked $ (warn ss . checkDBLock_1 $ langOf ss) *> getLine *> checkDBLock ss
+
+-------
+-- MISC -- Too specific for `Utilities.hs` or `Aura.Utils`
+-------
+
+-- | Print some message in green with Aura flair.
+notify :: Settings -> Doc AnsiStyle -> IO ()
+notify ss = putStrLnA ss . green
+
+-- | Print some message in yellow with Aura flair.
+warn :: Settings -> Doc AnsiStyle -> IO ()
+warn ss = putStrLnA ss . yellow
+
+-- | Print some message in red with Aura flair.
+scold :: Settings -> Doc AnsiStyle -> IO ()
+scold ss = putStrLnA ss . red
+
+-- | Report a message with multiple associated items. Usually a list of
+-- naughty packages.
+report :: (Member (Reader Settings) r, Member IO r) =>
+ (Doc AnsiStyle -> Doc AnsiStyle) -> (Language -> Doc AnsiStyle) -> NonEmpty PkgName -> Eff r ()
+report c msg pkgs = do
+ ss <- ask
+ send . putStrLnA ss . c . msg $ langOf ss
+ send . T.putStrLn . dtot . colourCheck ss . vsep . map (cyan . pretty . view (field @"name")) $ toList pkgs
diff --git a/lib/Aura/Dependencies.hs b/lib/Aura/Dependencies.hs
new file mode 100644
index 0000000..c7517ae
--- /dev/null
+++ b/lib/Aura/Dependencies.hs
@@ -0,0 +1,168 @@
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds, TupleSections, MultiWayIf #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Dependencies
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Library for handling package dependencies and version conflicts.
+
+module Aura.Dependencies ( resolveDeps ) where
+
+import Algebra.Graph.AdjacencyMap
+import Aura.Core
+import Aura.Languages
+import Aura.Settings
+import Aura.Types
+import BasePrelude
+import Control.Concurrent.STM.TQueue
+import Control.Concurrent.STM.TVar
+import Control.Concurrent.Throttled (throttleMaybe_)
+import Control.Error.Util (note, hush)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Maybe
+import Data.Generics.Product (field)
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Data.Versions
+import Data.Witherable (wither)
+import Lens.Micro
+import System.IO (stdout, hFlush)
+
+---
+
+-- | To signal if one of the STM actions below wrote a new value to the
+-- shared Map.
+data Wrote = WroteNothing | WroteNew
+
+-- | Given some `Package`s, determine its full dependency graph.
+-- The graph is collapsed into layers of packages which are not
+-- interdependent, and thus can be built and installed as a group.
+--
+-- Deeper layers of the result list (generally) depend on the previous layers.
+resolveDeps :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ Repository -> NonEmptySet Package -> Eff r (NonEmpty (NonEmptySet Package))
+resolveDeps repo ps = do
+ ss <- ask
+ tv <- send $ newTVarIO M.empty
+ ts <- send $ newTVarIO S.empty
+ liftMaybeM (Failure connectionFailure_1) $ resolveDeps' ss repo tv ts ps
+ m <- send $ readTVarIO tv
+ s <- send $ readTVarIO ts
+ unless (length ps == length m) $ send (putStr "\n")
+ let de = conflicts ss m s
+ unless (null de) . throwError . Failure $ missingPkg_2 de
+ either throwError pure $ sortInstall m
+
+-- | An empty list signals success.
+resolveDeps' :: Settings -> Repository -> TVar (M.Map PkgName Package) -> TVar (S.Set PkgName) -> NonEmptySet Package -> IO (Maybe ())
+resolveDeps' ss repo tv ts ps = hush <$> throttleMaybe_ h ps
+ where
+ -- | Handles determining whether further work should be done. It won't
+ -- continue to `j` if this `Package` has already been analysed.
+ h :: TQueue Package -> Package -> IO (Maybe ())
+ h tq p = do
+ w <- atomically $ do
+ let pn = pname p
+ m <- readTVar tv
+ case M.lookup pn m of
+ Just _ -> pure WroteNothing
+ Nothing -> modifyTVar' tv (M.insert pn p) $> WroteNew
+ case w of
+ WroteNothing -> pure $ Just ()
+ WroteNew -> case p of
+ FromRepo _ -> pure $ Just ()
+ FromAUR b -> readTVarIO tv >>= j tq b
+
+ -- | Check for the existance of dependencies, as well as for any version conflicts
+ -- they might introduce.
+ j :: TQueue Package -> Buildable -> M.Map PkgName Package -> IO (Maybe ())
+ j tq b m = do
+ s <- readTVarIO ts
+ (ds, sd) <- fmap partitionEithers . wither (satisfied m s) $ b ^. field @"deps"
+ atomically $ modifyTVar' ts (<> S.fromList sd)
+ case NEL.nonEmpty $ ds ^.. each . field @"name" of
+ Nothing -> pure $ Just ()
+ Just deps' -> do
+ putStr "." *> hFlush stdout
+ runMaybeT $ MaybeT (repoLookup repo ss $ NES.fromNonEmpty deps') >>= \(_, goods) ->
+ unless (null goods) (lift . atomically $ traverse_ (writeTQueue tq) goods)
+
+ satisfied :: M.Map PkgName Package -> S.Set PkgName -> Dep -> IO (Maybe (Either Dep PkgName))
+ satisfied m s d | M.member dn m || S.member dn s = pure Nothing
+ | otherwise = Just . bool (Left d) (Right dn) <$> isSatisfied d
+ where dn = d ^. field @"name"
+
+conflicts :: Settings -> M.Map PkgName Package -> S.Set PkgName -> [DepError]
+conflicts ss m s = foldMap f m
+ where pm = M.fromList $ foldr (\p acc -> (pprov p ^. field @"provides" . to PkgName, p) : acc) [] m
+ f (FromRepo _) = []
+ f (FromAUR b) = flip mapMaybe (b ^. field @"deps") $ \d ->
+ let dn = d ^. field @"name"
+ in if | S.member dn s -> Nothing
+ | otherwise -> case M.lookup dn m <|> M.lookup dn pm of
+ Nothing -> Just $ NonExistant dn
+ Just p -> realPkgConflicts ss (b ^. field @"name") p d
+
+sortInstall :: M.Map PkgName Package -> Either Failure (NonEmpty (NonEmptySet Package))
+sortInstall m = case cycles depGraph of
+ [] -> note (Failure missingPkg_3) . NEL.nonEmpty . mapMaybe NES.fromSet $ batch depGraph
+ cs -> Left . Failure . missingPkg_4 $ map (map pname . vertexList) cs
+ where f (FromRepo _) = []
+ f p@(FromAUR b) = mapMaybe (\d -> fmap (p,) $ (d ^. field @"name") `M.lookup` m) $ b ^. field @"deps" -- TODO handle "provides"?
+ depGraph = overlay connected singles
+ elems = M.elems m
+ connected = edges $ foldMap f elems
+ singles = overlays $ map vertex elems
+
+cycles :: Ord a => AdjacencyMap a -> [AdjacencyMap a]
+cycles x = [ induce (`S.member` c) x | c <- cs ]
+ where cs = filter (\c -> S.size c > 1) $ vertexList (scc x)
+
+-- | Find the vertices that have no dependencies.
+-- O(n) complexity.
+leaves :: Ord a => AdjacencyMap a -> S.Set a
+leaves x = S.filter (null . flip postSet x) $ vertexSet x
+
+-- | Split a graph into batches of mutually independent vertices.
+-- Probably O(m * n * log(n)) complexity.
+batch :: Ord a => AdjacencyMap a -> [S.Set a]
+batch g | isEmpty g = []
+ | otherwise = ls : batch (induce (`S.notMember` ls) g)
+ where ls = leaves g
+
+-- | Questions to be answered in conflict checks:
+-- 1. Is the package ignored in `pacman.conf`?
+-- 2. Is the version requested different from the one provided by
+-- the most recent version?
+realPkgConflicts :: Settings -> PkgName -> Package -> Dep -> Maybe DepError
+realPkgConflicts ss parent pkg dep
+ | pn `elem` toIgnore = Just $ Ignored failMsg1
+ | isVersionConflict reqVer curVer = Just $ VerConflict failMsg2
+ | otherwise = Nothing
+ where pn = pname pkg
+ curVer = pver pkg & release .~ []
+ reqVer = (dep ^. field @"demand") & _VersionDemand . release .~ []
+ lang = langOf ss
+ toIgnore = ignoresOf ss
+ failMsg1 = getRealPkgConflicts_2 pn lang
+ failMsg2 = getRealPkgConflicts_1 parent pn (prettyV curVer) (T.pack $ show reqVer) lang
+
+-- | Compares a (r)equested version number with a (c)urrent up-to-date one.
+-- The `MustBe` case uses regexes. A dependency demanding version 7.4
+-- SHOULD match as `okay` against version 7.4, 7.4.0.1, or even 7.4.0.1-2.
+isVersionConflict :: VersionDemand -> Versioning -> Bool
+isVersionConflict Anything _ = False
+isVersionConflict (LessThan r) c = c >= r
+isVersionConflict (MoreThan r) c = c <= r
+isVersionConflict (MustBe r) c = c /= r
+isVersionConflict (AtLeast r) c = c < r
diff --git a/lib/Aura/Diff.hs b/lib/Aura/Diff.hs
new file mode 100644
index 0000000..96c9043
--- /dev/null
+++ b/lib/Aura/Diff.hs
@@ -0,0 +1,24 @@
+-- |
+-- Module : Aura.Diff
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Diffing files.
+
+module Aura.Diff ( diff ) where
+
+import Aura.Settings
+import BasePrelude hiding (diff)
+import System.Path (Path, Absolute, toFilePath)
+import System.Process.Typed (runProcess, proc)
+
+---
+
+-- | Given two filepaths, output the diff of the two files.
+-- Output will be coloured unless colour is deactivated by
+-- `--color never` or by detection of a non-terminal output
+-- target.
+diff :: MonadIO m => Settings -> Path Absolute -> Path Absolute -> m ()
+diff ss f1 f2 = void . runProcess . proc "diff" $ c <> ["-u", toFilePath f1, toFilePath f2]
+ where c = bool ["--color"] [] $ shared ss (Colour Never)
diff --git a/lib/Aura/Install.hs b/lib/Aura/Install.hs
new file mode 100644
index 0000000..ba5abb4
--- /dev/null
+++ b/lib/Aura/Install.hs
@@ -0,0 +1,231 @@
+{-# LANGUAGE OverloadedStrings, MultiWayIf, ViewPatterns #-}
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds, TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Install
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Layer for AUR package installation.
+-- Backend for `Aura.Commands.A`.
+
+module Aura.Install
+ ( install
+ , displayPkgDeps
+ ) where
+
+import Aura.Build (buildPackages, installPkgFiles)
+import Aura.Cache (Cache(..), cacheContents)
+import Aura.Colour
+import Aura.Core
+import Aura.Dependencies (resolveDeps)
+import Aura.Diff (diff)
+import Aura.Languages
+import Aura.Packages.AUR (aurLookup, aurRepo)
+import Aura.Packages.Repository (pacmanRepo)
+import Aura.Pacman (pacman, pacmanSuccess)
+import Aura.Pkgbuild.Base
+import Aura.Pkgbuild.Records
+import Aura.Pkgbuild.Security
+import Aura.Settings
+import Aura.Types
+import Aura.Utils (optionalPrompt)
+import BasePrelude hiding (FilePath, diff)
+import Control.Compactable (fmapEither)
+import Control.Concurrent.STM.TQueue
+import Control.Concurrent.Throttled (throttle)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field, super, HasField'(..))
+import qualified Data.List.NonEmpty as NEL
+import qualified Data.Map.Strict as M
+import Data.Semigroup.Foldable (fold1)
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text.IO as T
+import Language.Bash.Pretty (prettyText)
+import Language.Bash.Syntax (ShellCommand)
+import Lens.Micro ((^.), (^..), each)
+import Lens.Micro.Extras (view)
+import System.Directory (setCurrentDirectory)
+import System.IO (hFlush, stdout)
+import System.Path (fromAbsoluteFilePath)
+
+---
+
+repository :: Repository
+repository = pacmanRepo <> aurRepo
+
+-- | High level 'install' command. Handles installing
+-- dependencies.
+install :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet PkgName -> Eff r ()
+install pkgs = do
+ ss <- ask
+ if | not $ switch ss DeleteMakeDeps -> install' pkgs
+ | otherwise -> do -- `-a` was used.
+ orphansBefore <- send orphans
+ install' pkgs
+ orphansAfter <- send orphans
+ let makeDeps = NES.fromSet (orphansAfter S.\\ orphansBefore)
+ traverse_ (\mds -> send (notify ss . removeMakeDepsAfter_1 $ langOf ss) *> removePkgs mds) makeDeps
+
+install' :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet PkgName -> Eff r ()
+install' pkgs = do
+ ss <- ask
+ unneeded <- bool
+ (pure S.empty)
+ (S.fromList . catMaybes <$> send (throttle (const isInstalled) pkgs >>= atomically . flushTQueue))
+ $ shared ss NeededOnly
+ let !pkgs' = NES.toSet pkgs
+ if | shared ss NeededOnly && unneeded == pkgs' -> send . warn ss . install_2 $ langOf ss
+ | otherwise -> do
+ let (ignored, notIgnored) = S.partition (`S.member` ignoresOf ss) pkgs'
+ installAnyway <- confirmIgnored ignored
+ case NES.fromSet $ (notIgnored <> installAnyway) S.\\ unneeded of
+ Nothing -> send . warn ss . install_2 $ langOf ss
+ Just toInstall -> do
+ traverse_ (report yellow reportUnneededPackages_1) . NEL.nonEmpty $ toList unneeded
+ (nons, toBuild) <- liftMaybeM (Failure connectionFailure_1) $ aurLookup (managerOf ss) toInstall
+ pkgbuildDiffs toBuild
+ traverse_ (report red reportNonPackages_1) . NEL.nonEmpty $ toList nons
+ case NES.fromSet $ S.map (\b -> b { isExplicit = True }) toBuild of
+ Nothing -> throwError $ Failure install_2
+ Just toBuild' -> do
+ send $ notify ss (install_5 $ langOf ss) *> hFlush stdout
+ allPkgs <- depsToInstall repository toBuild'
+ let (repoPkgs, buildPkgs) = second uniquePkgBase $ partitionPkgs allPkgs
+ unless (switch ss NoPkgbuildCheck) $ traverse_ (traverse_ analysePkgbuild) buildPkgs
+ reportPkgsToInstall repoPkgs buildPkgs
+ unless (switch ss DryRun) $ do
+ continue <- send $ optionalPrompt ss install_3
+ if | not continue -> throwError $ Failure install_4
+ | otherwise -> do
+ traverse_ repoInstall $ NEL.nonEmpty repoPkgs
+ let !mbuildPkgs = NEL.nonEmpty buildPkgs
+ traverse_ (send . storePkgbuilds . fold1) mbuildPkgs
+ traverse_ buildAndInstall mbuildPkgs
+
+-- | Determine if a package's PKGBUILD might contain malicious bash code.
+analysePkgbuild :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Buildable -> Eff r ()
+analysePkgbuild b = do
+ ss <- ask
+ let f = do
+ yes <- send $ optionalPrompt ss security_6
+ when yes . throwError $ Failure security_7
+ case parsedPB $ b ^. field @"pkgbuild" of
+ Nothing -> send (warn ss (security_1 (b ^. field @"name") $ langOf ss)) *> f
+ Just l -> case bannedTerms l of
+ [] -> pure ()
+ bts -> do
+ send $ scold ss (security_5 (b ^. field @"name") $ langOf ss)
+ send $ traverse_ (displayBannedTerms ss) bts
+ f
+
+displayBannedTerms :: Settings -> (ShellCommand, BannedTerm) -> IO ()
+displayBannedTerms ss (stmt, b) = do
+ putStrLn $ "\n " <> prettyText stmt <> "\n"
+ warn ss $ reportExploit b lang
+ where lang = langOf ss
+
+-- | Give anything that was installed as a dependency the /Install Reason/ of
+-- "Installed as a dependency for another package".
+annotateDeps :: NonEmptySet Buildable -> IO ()
+annotateDeps bs = unless (null bs') . void . pacmanSuccess $ ["-D", "--asdeps"] <> asFlag (bs' ^.. each . field @"name")
+ where bs' = filter (not . isExplicit) $ toList bs
+
+-- | Reduce a list of candidate packages to build, such that there is only one
+-- instance of each "Package Base". This will ensure that split packages will
+-- only be built once each. Precedence is given to packages that actually
+-- match the base name (e.g. llvm50 vs llvm50-libs).
+uniquePkgBase :: [NonEmptySet Buildable] -> [NonEmptySet Buildable]
+uniquePkgBase bs = mapMaybe (NES.fromSet . S.filter (\b -> (b ^. field @"name") `S.member` goods) . NES.toSet) bs
+ where f a b | (a ^. field @"name") == (a ^. field @"base") = a
+ | (b ^. field @"name") == (b ^. field @"base") = b
+ | otherwise = a
+ goods = S.fromList . (^.. each . field @"name") . M.elems . M.fromListWith f $ map (view (field @"base") &&& id) bs'
+ bs' = foldMap toList bs
+
+confirmIgnored :: (Member (Reader Settings) r, Member IO r) => S.Set PkgName -> Eff r (S.Set PkgName)
+confirmIgnored (toList -> ps) = do
+ ss <- ask
+ S.fromList <$> filterM (send . optionalPrompt ss . confirmIgnored_1) ps
+
+depsToInstall :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ Repository -> NonEmptySet Buildable -> Eff r (NonEmpty (NonEmptySet Package))
+depsToInstall repo bs = do
+ ss <- ask
+ traverse (send . packageBuildable ss) (NES.toNonEmpty bs) >>= resolveDeps repo . NES.fromNonEmpty
+
+repoInstall :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmpty Prebuilt -> Eff r ()
+repoInstall ps = do
+ pacOpts <- asks (asFlag . commonConfigOf)
+ liftEitherM . pacman $ ["-S", "--asdeps"] <> pacOpts <> asFlag (ps ^.. each . field @"name")
+
+buildAndInstall :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmpty (NonEmptySet Buildable) -> Eff r ()
+buildAndInstall bss = do
+ pth <- asks (either id id . cachePathOf . commonConfigOf)
+ cache <- send $ cacheContents pth
+ traverse_ (f cache) bss
+ where f (Cache cache) bs = do
+ ss <- ask
+ let (ps, cached) = fmapEither g $ toList bs
+ g b = case (b ^. super @SimplePkg) `M.lookup` cache of
+ Just pp | not (switch ss ForceBuilding) -> Right pp
+ _ -> Left b
+ built <- traverse (buildPackages . NES.fromNonEmpty) $ NEL.nonEmpty ps
+ traverse_ installPkgFiles $ built <> (NES.fromNonEmpty <$> NEL.nonEmpty cached)
+ send $ annotateDeps bs
+
+------------
+-- REPORTING
+------------
+-- | Display dependencies. The result of @-Ad@.
+displayPkgDeps :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ NonEmptySet PkgName -> Eff r ()
+displayPkgDeps ps = do
+ ss <- ask
+ let f = depsToInstall repository >=> reportDeps (switch ss LowVerbosity) . partitionPkgs
+ (_, goods) <- liftMaybeM (Failure connectionFailure_1) $ aurLookup (managerOf ss) ps
+ traverse_ f $ NES.fromSet goods
+ where reportDeps True = send . uncurry reportListOfDeps
+ reportDeps False = uncurry reportPkgsToInstall
+
+reportPkgsToInstall :: (Member (Reader Settings) r, Member IO r) =>
+ [Prebuilt] -> [NonEmptySet Buildable] -> Eff r ()
+reportPkgsToInstall rps bps = do
+ let (explicits, ds) = partition isExplicit $ foldMap toList bps
+ f reportPkgsToInstall_1 rps
+ f reportPkgsToInstall_3 ds
+ f reportPkgsToInstall_2 explicits
+ where f m xs = traverse_ (report green m) . NEL.nonEmpty . sort $ xs ^.. each . field @"name"
+
+reportListOfDeps :: [Prebuilt] -> [NonEmptySet Buildable] -> IO ()
+reportListOfDeps rps bps = f rps *> f (foldMap toList bps)
+ where f :: HasField' "name" s PkgName => [s] -> IO ()
+ f = traverse_ T.putStrLn . sort . (^.. each . field' @"name" . field' @"name")
+
+pkgbuildDiffs :: (Member (Reader Settings) r, Member IO r) => S.Set Buildable -> Eff r ()
+pkgbuildDiffs ps = ask >>= check
+ where check ss | not $ switch ss DiffPkgbuilds = pure ()
+ | otherwise = traverse_ displayDiff ps
+ displayDiff :: (Member (Reader Settings) r, Member IO r) => Buildable -> Eff r ()
+ displayDiff p = do
+ ss <- ask
+ let pn = p ^. field @"name"
+ lang = langOf ss
+ isStored <- send $ hasPkgbuildStored pn
+ if not isStored
+ then send . warn ss $ reportPkgbuildDiffs_1 pn lang
+ else send $ do
+ setCurrentDirectory "/tmp"
+ let new = "/tmp/new.pb"
+ BL.writeFile new $ p ^. field @"pkgbuild" . field @"pkgbuild"
+ liftIO . warn ss $ reportPkgbuildDiffs_3 pn lang
+ diff ss (pkgbuildPath pn) $ fromAbsoluteFilePath new
diff --git a/lib/Aura/Languages.hs b/lib/Aura/Languages.hs
new file mode 100644
index 0000000..93d8a8e
--- /dev/null
+++ b/lib/Aura/Languages.hs
@@ -0,0 +1,1385 @@
+{-# LANGUAGE LambdaCase, ViewPatterns, OverloadedStrings #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+{-# OPTIONS_HADDOCK prune #-}
+{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}
+
+-- |
+-- Module : Aura.Languages.Fields
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- All output strings that a user can be shown.
+-- All normal restrictions on line length do not apply for this file, and this file only.
+
+{- AURA TRANSLATORS - Thank you all
+Chris "Kwpolska" Warrick | Polish
+Denis Kasak / "stranac" | Croatian
+Fredrik Haikarainen / Daniel Beecham | Swedish
+Lukas Niederbremer / Jonas Platte | German
+Alejandro Gómez / Sergio Conde | Spanish
+Henry Kupty / Thiago Perrotta / Wagner Amaral | Portuguese
+Ma Jiehong / Fabien Dubosson | French
+Kyrylo Silin | Russian
+Bob Valantin | Italian
+Filip Brcic | Serbian
+"chinatsun" | Norwegian
+"pak tua Greg" | Indonesia
+Kai Zhang | Chinese
+Onoue Takuro | Japanese
+-}
+
+module Aura.Languages where
+
+import Aura.Colour
+import qualified Aura.Languages.Fields as Fields
+import Aura.Types
+import BasePrelude hiding ((<+>))
+import Data.Generics.Product (field)
+import qualified Data.Map.Strict as Map (Map, (!), fromList, toList, mapWithKey)
+import qualified Data.Text as T
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Lens.Micro.Extras (view)
+
+---
+
+translators :: Map.Map Language T.Text
+translators = Map.fromList
+ [ (Polish, "Chris Warrick")
+ , (Croatian, "Denis Kasak / \"stranac\"")
+ , (Swedish, "Fredrik Haikarainen / Daniel Beecham")
+ , (German, "Lukas Niederbremer / Jonas Platte")
+ , (Spanish, "Alejandro Gómez / Sergio Conde")
+ , (Portuguese, "Henry Kupty / Thiago Perrotta / Wagner Amaral")
+ , (French, "Ma Jiehong / Fabien Dubosson")
+ , (Russian, "Kyrylo Silin / Alexey Kotlyarov")
+ , (Italian, "Bob Valantin")
+ , (Serbian, "Filip Brcic")
+ , (Norwegian, "\"chinatsun\"")
+ , (Indonesia, "\"pak tua Greg\"")
+ , (Chinese, "Kai Zhang")
+ , (Japanese, "Onoue Takuro")
+ ]
+
+-- These need updating! Or removing...
+languageNames :: Language -> Map.Map Language T.Text
+languageNames = Map.fromList . zip [ Japanese, Polish, Croatian, Swedish, German, Spanish, Portuguese, French, Russian, Italian, Serbian, Norwegian, Indonesia, Chinese ] . \case
+ Japanese -> [ "日本語", "ポーランド語", "クロアチア語", "スウェーデン語", "ドイツ語", "スペイン語", "ポルトガル語", "フランス語", "ロシア語", "イタリア語", "セルビア語", "ノルウェー語", "インドネシア語", "中国語" ]
+ Polish -> [ "Japanese", "polski", "chorwacki", "szwedzki", "niemiecki", "hiszpański", "portugalski", "francuski", "rosyjski", "", "", "", "Indonesian", "Chinese" ]
+ Croatian -> [ "Japanese", "poljski", "hrvatski", "švedski", "njemački", "španjolski", "portugalski", "francuski", "ruski", "talijanski", "srpski", "norveški", "Indonesian", "Chinese" ]
+ Swedish -> [ "Japanese", "Polska", "Kroatiska", "Svenska", "Tyska", "Spanska", "Portugisiska", "Franska", "Ryska", "Italienska", "Serbiska", "Norska", "Indonesian", "Chinese" ]
+ German -> [ "Japanisch", "Polnisch", "Kroatisch", "Schwedisch", "Deutsch", "Spanisch", "Portugiesisch", "Französisch", "Russisch", "Italienisch", "Serbisch", "Norwegisch", "Indonesisch", "Chinese" ]
+ Spanish -> [ "Japanese", "Polaco", "Croata", "Sueco", "Alemán", "Español", "Portugués", "Francés", "Ruso", "Italiano", "Serbio", "Noruego", "Indonesio", "Chinese" ]
+ Portuguese -> [ "Japonês", "Polonês", "Croata", "Sueco", "Alemão", "Espanhol", "Português", "Francês", "Russo", "Italiano", "Sérvio", "Norueguês", "Indonésio", "Chinês" ]
+ French -> [ "Japanese", "Polonais", "Croate", "Suédois", "Allemand", "Espagnol", "Portugais", "Français", "Russe", "Italien", "Serbe", "Norvégien", "Indonesian", "Chinese" ]
+ Russian -> [ "Японский", "Польский", "Хорватский", "Шведский", "Немецкий", "Испанский", "Португальский", "Французский", "Русский", "Итальянский", "Сербский", "Норвежский", "Индонезийский", "Китайский" ]
+ Italian -> [ "Giapponese", "Polacco", "Croato", "Svedese", "Tedesco", "Spagnolo", "Portoghese", "Francese", "Russo", "Italiano", "", "", "Indonesian", "Chinese" ]
+ Serbian -> [ "Japanese", "Пољски", "Хрватски", "Шведски", "Немачки", "Шпански", "Португалски", "Француски", "Руски", "Италијански", "Српски", "", "Indonesian", "Chinese" ]
+ Norwegian -> [ "Japanese", "Polsk", "Kroatisk", "Svensk", "Tysk", "Spansk", "Portugisisk", "Fransk", "Russisk", "Italiensk", "Serbisk", "Norsk", "Indonesian", "Chinese" ]
+ Indonesia -> [ "Japanese", "Polandia", "Kroasia", "Swedia", "Jerman", "Spanyol", "Portugis", "Prancis", "Rusia", "Italia", "Serbia", "Norwegia", "Indonesian", "Chinese" ]
+ Chinese -> [ "日语", "波兰语", "克罗地亚语", "瑞典语", "德语", "西班牙语", "葡萄牙语", "法语", "俄语", "意大利语", "塞尔维亚语", "挪威语", "印度尼西亚语", "中文" ]
+ _ -> [ "Japanese", "Polish", "Croatian", "Swedish", "German", "Spanish", "Portuguese", "French", "Russian", "Italian", "Serbian", "Norwegian", "Indonesian", "Chinese" ]
+
+translatorMsgTitle :: Language -> T.Text
+translatorMsgTitle = \case
+ Japanese -> "Auraの翻訳者:"
+ Polish -> "Tłumacze Aury:"
+ Croatian -> "Aura Prevoditelji:"
+ Swedish -> "Aura Översättare:"
+ German -> "Aura Übersetzer:"
+ Spanish -> "Traductores de Aura:"
+ Portuguese -> "Tradutores de Aura:"
+ French -> "Traducteurs d'Aura:"
+ Russian -> "Переводчики Aura:"
+ Italian -> "Traduttori di Aura:"
+ Serbian -> "Преводиоци Аура:"
+ Norwegian -> "Aura Oversettere:"
+ Indonesia -> "Penerjemah Aura:"
+ Chinese -> "Aura 的翻译者:"
+ _ -> "Aura Translators:"
+
+translatorMsg :: Language -> [T.Text]
+translatorMsg lang = title : names
+ where title = translatorMsgTitle lang
+ names = fmap snd . Map.toList $
+ Map.mapWithKey (\l t -> formatLang (assocLang l t)) translators
+ assocLang lang' translator = (translator, langNames Map.! lang')
+ formatLang (translator, lang') = " (" <> lang' <> ") " <> translator
+ langNames = languageNames lang
+
+-- Wrap a String in backticks
+bt :: T.Text -> Doc AnsiStyle
+bt cs = "`" <> pretty cs <> "`"
+
+whitespace :: Language -> Char
+whitespace Japanese = ' ' -- \12288
+whitespace _ = ' ' -- \32
+
+langFromLocale :: T.Text -> Language
+langFromLocale = T.take 2 >>> \case
+ "ja" -> Japanese
+ "pl" -> Polish
+ "hr" -> Croatian
+ "sv" -> Swedish
+ "de" -> German
+ "es" -> Spanish
+ "pt" -> Portuguese
+ "fr" -> French
+ "ru" -> Russian
+ "it" -> Italian
+ "sr" -> Serbian
+ "nb" -> Norwegian
+ "id" -> Indonesia
+ "zh" -> Chinese
+ _ -> English
+
+----------------------
+-- Aura/Core functions
+----------------------
+-- NEEDS TRANSLATION
+checkDBLock_1 :: Language -> Doc AnsiStyle
+checkDBLock_1 = \case
+ Japanese -> "パッケージデータベースが閉鎖されている状態です。開放したらキーを押して続行してください。"
+ Polish -> "Baza pakietów jest zablokowana. Kiedy zostanie odblokowana naciśnij enter aby kontynuować"
+ Croatian -> "Baza paketa je zaključana. Kad se otključa, pritisnite enter da biste nastavili."
+ German -> "Die Paketdatenbank ist gesperrt. Drücken Sie Enter wenn sie entsperrt ist um fortzufahren."
+ Spanish -> "La base de datos de paquetes está bloqueada. Presiona enter cuando esté desbloqueada para continuar."
+ Norwegian -> "Pakkedatabasen er låst. Trykk enter når den er åpnet for å fortsette."
+ French -> "La base de données des paquets est bloquée. Appuyez sur enter pour continuer."
+ Portuguese -> "Banco de dados de pacote travado. Aperte 'enter' quando estiver destravado para poder continuar."
+ Russian -> "База данных пакетов заблокирована. Нажмите \"Ввод\", когда она разблокируется, чтобы продолжить."
+ Chinese -> "包数据库已锁定。请在解锁后按下回车以继续。"
+ Swedish -> "Paketdatabasen är låst. Klicka på enter när den är upplåst."
+ _ -> "The package database is locked. Press enter when it's unlocked to continue."
+
+trueRoot_3 :: Language -> Doc AnsiStyle
+trueRoot_3 = \case
+ Japanese -> "「root」としてパッケージを作成するのは「makepkg v4.2」で不可能になりました。"
+ German -> "Seit makepkg v4.2 ist es nicht mehr möglich als root zu bauen."
+ Spanish -> "Desde makepkg v4.2 no es posible compilar paquetes como root."
+ Portuguese -> "A partir da versão v4.2 de makepkg, não é mais possível compilar como root."
+ Russian -> "С версии makepkg v4.2 сборка от имени root более невозможна."
+ Chinese -> "自从 makepkg v4.2 以后,就不能以根用户身份构建软件了。"
+ Swedish -> "I makepkg v4.2 och uppåt är det inte tillåtet att bygga som root."
+ _ -> "As of makepkg v4.2, building as root is no longer possible."
+
+mustBeRoot_1 :: Language -> Doc AnsiStyle
+mustBeRoot_1 = let sudo = bt "sudo" in \case
+ Japanese -> sudo <> "を使わないとそれができない!"
+ Polish -> "Musisz użyć " <> sudo <> ", żeby to zrobić."
+ Croatian -> "Morate koristiti" <> sudo <> "za ovu radnju."
+ Swedish -> "Du måste använda " <> sudo <> " för det."
+ German -> "Sie müssen dafür " <> sudo <> " benutzen."
+ Spanish -> "Tienes que utilizar " <> sudo <> " para eso."
+ Portuguese -> "Utilize " <> sudo <> " para isso."
+ French -> "Vous devez utiliser " <> sudo <> " pour ça."
+ Russian -> "Необходимо использовать " <> sudo <> " для этого."
+ Italian -> "È necessario utilizzare " <> sudo <> " per questo."
+ Serbian -> "Морате да користите " <> sudo <> " за ову радњу."
+ Norwegian -> "Du må bruke " <> sudo <> " for det."
+ Indonesia -> "Anda harus menggunakan " <> sudo <> " untuk melakukannya."
+ Chinese -> "除非是根用户,否则不能执行此操作。"
+ _ -> "You cannot perform this operation without using sudo."
+
+-----------------------
+-- Aura/Build functions
+-----------------------
+buildPackages_1 :: PkgName -> Language -> Doc AnsiStyle
+buildPackages_1 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "を作成中・・・"
+ Polish -> "Budowanie " <> p <> "..."
+ Croatian -> "Gradim " <> p <> "..."
+ Swedish -> "Bygger paket " <> p <> "..."
+ German -> "Baue Paket " <> p <> "..."
+ Spanish -> "Compilando " <> p <> "..."
+ Portuguese -> "Compilando " <> p <> "..."
+ French -> "Compilation de " <> p <> "..."
+ Russian -> "Сборка " <> p <> "..."
+ Italian -> "Compilazione di " <> p <> "..."
+ Serbian -> "Градим " <> p <> "..."
+ Norwegian -> "Bygger " <> p <> "..."
+ Indonesia -> "Membangun " <> p <> "..."
+ Chinese -> p <> " 正在构建中..."
+ _ -> "Building " <> p <> "..."
+
+buildFail_5 :: Language -> Doc AnsiStyle
+buildFail_5 = \case
+ Japanese -> "パッケージ作成に失敗しました。"
+ Polish -> "Budowanie nie powiodło się."
+ Croatian -> "Izgradnja nije uspjela."
+ Swedish -> "Gick inte att bygga paket."
+ German -> "Bauen fehlgeschlagen."
+ Spanish -> "La compilación falló."
+ Portuguese -> "Falha na compilação."
+ French -> "Compilation échouée."
+ Russian -> "Сборка не удалась."
+ Italian -> "Compilazione fallita."
+ Serbian -> "Изградња пакета није успела."
+ Norwegian -> "Bygging feilet."
+ Indonesia -> "Proses gagal."
+ Chinese -> "构建失败。"
+ _ -> "Building failed."
+
+-- NEEDS TRANSLATION
+buildFail_6 :: Language -> Doc AnsiStyle
+buildFail_6 = \case
+ Japanese -> "それでも続行しますか?"
+ Polish -> "Czy mimo to chcesz kontynuować?"
+ Croatian -> "Želite li svejedno nastaviti?"
+ German -> "Möchten Sie trotzdem fortfahren?"
+ Spanish -> "¿Deseas continuar de todas formas?"
+ Norwegian -> "Vil du fortsette likevel?"
+ Italian -> "Vuoi continuare comunque?"
+ Portuguese -> "Gostaria de continuar mesmo assim?"
+ French -> "Voulez-vous tout de même continuer ?"
+ Russian -> "Продолжить, несмотря ни на что?"
+ Indonesia -> "Apakah anda tetap ingin melanjutkan?"
+ Chinese -> "你仍然希望继续吗?"
+ Swedish -> "Vill du fortsätta ändå?"
+ _ -> "Would you like to continue anyway?"
+
+-- NEEDS TRANSLATION
+buildFail_7 :: PkgName -> Language -> Doc AnsiStyle
+buildFail_7 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "のビルドスクリプトを収得できませんでした。"
+ Polish -> "Nie udało się pozyskać skryptów budowania dla " <> p <> "."
+ German -> "Herunterladen der Build-Skripte für " <> p <> " fehlgeschlagen."
+ Spanish -> "No se han podido obtener los scripts de compilación de " <> p <> "."
+ Portuguese -> "Falha ao obter scripts de compilação para " <> p <> "."
+ Indonesia -> "Gagal mendapatkan skrip untuk " <> p <> "."
+ Russian -> "Не удалось получить сценарии сборки для " <> p <> "."
+ Chinese -> "无法获得 " <> p <> " 的构建脚本。"
+ Swedish -> "Kunde inte hämta byggskript för " <> p <> "."
+ _ -> "Failed to obtain build scripts for " <> p <> "."
+
+buildFail_8 :: Language -> Doc AnsiStyle
+buildFail_8 = \case
+ Japanese -> "makepkgは失敗しました。"
+ Portuguese -> "Ocorreu um erro ao executar makepkg"
+ Russian -> "Произошла ошибка makepkg."
+ _ -> "There was a makepkg failure."
+
+buildFail_9 :: Language -> Doc AnsiStyle
+buildFail_9 = \case
+ _ -> "Failed to detect any built package files (*.pkg.tar.xz)."
+
+buildFail_10 :: Language -> Doc AnsiStyle
+buildFail_10 = \case
+ _ -> "Every package failed to build."
+
+buildFail_11 :: Language -> Doc AnsiStyle
+buildFail_11 = \case
+ Japanese -> "作成は失敗しました。エラーを見ますか?"
+ _ -> "Building failed. Would you like to see the error?"
+
+------------------------------
+-- Aura/Dependencies functions
+------------------------------
+-- NEEDS UPDATE TO MATCH NEW ENGLISH
+getRealPkgConflicts_1 :: PkgName -> PkgName -> T.Text -> T.Text -> Language -> Doc AnsiStyle
+getRealPkgConflicts_1 (bt . view (field @"name") -> prnt) (bt . view (field @"name") -> p) (bt -> r) (bt -> d) = \case
+ Japanese -> "パッケージ" <> p <> "はバージョン" <> d <> "を要するが" <> "一番最新のバージョンは" <> r <> "。"
+ Polish -> "Zależność " <> p <> " powinna być w wersji " <> d <> ", ale najnowsza wersja to " <> r <> "."
+ Croatian -> "Zavisnost " <> p <> " zahtjeva verziju " <> d <> ", a najnovija dostupna verzija je " <> r <> "."
+ Swedish -> "Beroendepaketet " <> p <> " kräver version " <> d <> " men den senaste versionen är " <> r <> "."
+ German -> "Die Abhängigkeit " <> p <> " verlangt Version " <> d <> ", aber die neuste Version ist " <> r <> "."
+ Spanish -> "La dependencia " <> p <> " requiere la versión " <> d <> " pero la versión más reciente es " <> r <> "."
+ Portuguese -> "A dependência " <> p <> " exige a versão " <> d <> " mas a versão mais recente é " <> r <> "."
+ French -> p <> " est une dépendance nécessitant la version " <> d <> ", mais la plus récente est la version " <> r <> "."
+ Russian -> "Зависимость " <> p <> " требует версию " <> d <> ", однако самой последней версией является " <> r <> "."
+ Italian -> "La dipendenza " <> p <> " richiede la versione " <> d <> " ma la versione disponibile è " <> r <> "."
+ Serbian -> "Зависност " <> p <> " захтева верзију " <> d <> ", али најновија верзија је " <> r <> "."
+ Norwegian -> "Avhengigheten " <> p <> " krever versjon " <> d <>", men den nyeste versjonen er " <> r <> "."
+ Indonesia -> "Dependensi " <> p <> " meminta versi " <> d <> " namun versi paling baru adalah " <> r <> "."
+ Chinese -> "依赖 " <> p <> " 需要版本 " <> d <> ",但是最新的版本是 " <> r <> "。"
+ _ -> "The package " <> prnt <> " depends on version " <> d <> " of " <> p <> ", but the most recent version is " <> r <> "."
+
+getRealPkgConflicts_2 :: PkgName -> Language -> Doc AnsiStyle
+getRealPkgConflicts_2 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "は無視されるパッケージ!`pacman.conf`を参考に。"
+ Polish -> p <> " jest ignorowany! Sprawdź plik `pacman.conf`."
+ Croatian -> p <> " je ignoriran paket! Provjerite svoj `pacman.conf`."
+ Swedish -> p <> " är ett ignorerat paket! Kolla din `pacman.conf`-fil."
+ German -> p <> " ist ein ignoriertes Paket! Siehe /etc/pacman.conf."
+ Spanish -> "¡" <> p <> " es un paquete ignorado! Revisa tu fichero `pacman.conf`."
+ Portuguese -> p <> " é um pacote ignorado conforme configuração em `pacman.conf`!"
+ French -> "Le paquet " <> p <> " est ignoré. Vous devriez jeter un œil à votre `pacman.conf`."
+ Russian -> "Пакет " <> p <> " игнорируется! Проверьте ваш файл `pacman.conf`."
+ Italian -> p <> " è un pacchetto ignorato, controllare `pacman.conf`."
+ Serbian -> "Пакет " <> p <> " је игнорисан! Видите ваш фајл „pacman.conf“."
+ Norwegian -> p <> " er en ignorert pakke! Sjekk din `pacman.conf`-fil."
+ Indonesia -> p <> " merupakan paket yang diabaikan! Lihat `pacman.conf` anda."
+ Chinese -> p <> " 是一个被忽略的包!请查看你的 `pacman.conf` 文件。"
+ _ -> p <> " is an ignored package! See your `pacman.conf` file."
+
+missingPkg_2 :: [DepError] -> Language -> Doc AnsiStyle
+missingPkg_2 ps l = vsep $ map (depError l) ps
+
+depError :: Language -> DepError -> Doc AnsiStyle
+depError _ (VerConflict s) = s
+depError _ (Ignored s) = s
+depError l (NonExistant (PkgName s)) = case l of
+ Portuguese -> "A dependência " <> bt s <> " não foi encontrada."
+ Russian -> "Зависимость " <> bt s <> " не найдена."
+ _ -> "The dependency " <> bt s <> " couldn't be found."
+depError l (BrokenProvides (PkgName pkg) (Provides pro) (PkgName n)) = case l of
+ Russian -> "Пакету " <> bt pkg <> " требуется " <> bt n <> ", предоставляющий " <> bt pro <> "."
+ _ -> "The package " <> bt pkg <> " needs " <> bt n <> ", which provides " <> bt pro <> "."
+
+missingPkg_3 :: Language -> Doc AnsiStyle
+missingPkg_3 = \case
+ _ -> "There was an error reorganizing the dependency graph. If you see this, something is very wrong."
+
+missingPkg_4 :: [[PkgName]] -> Language -> Doc AnsiStyle
+missingPkg_4 pns = \case
+ _ -> vsep $ "The following dependency cycles were detected:" : pns'
+ where pns' = map (hsep . map pretty . intersperse "=>" . map (view (field @"name"))) pns
+
+-----------------
+-- aura functions
+-----------------
+displayOutputLanguages_1 :: Language -> Doc AnsiStyle
+displayOutputLanguages_1 = \case
+ Japanese -> "aura は下記の言語に対応しています:"
+ Polish -> "Następujące języki są dostępne:"
+ Croatian -> "Dostupni su sljedeći jezici:"
+ Swedish -> "Följande språk är tillängliga:"
+ German -> "Die folgenden Sprachen sind verfügbar:"
+ Spanish -> "Los siguientes idiomas están disponibles:"
+ Portuguese -> "Os seguintes idiomas estão disponíveis:"
+ French -> "Les langues suivantes sont disponibles :"
+ Russian -> "Доступны следующие языки:"
+ Italian -> "Sono disponibili le seguenti lingue:"
+ Serbian -> "Доступни су следећи језици:"
+ Norwegian -> "Følgende språk er tilgjengelig:"
+ Indonesia -> "Berikut ini adalah bahasa yang tersedia:"
+ Chinese -> "以下语言是可用的:"
+ _ -> "The following languages are available:"
+
+----------------------------
+-- Aura/Commands/A functions
+----------------------------
+-- NEEDS TRANSLATION
+auraCheck_1 :: Language -> Doc AnsiStyle
+auraCheck_1 = \case
+ Japanese -> "Aura が更新されています。Auraだけ先に更新しますか?"
+ Polish -> "Dostępna jest nowa wersja Aura. Czy chcesz ją najpierw aktualizować?"
+ Croatian -> "Dostupna je nova verzija Aura. Želite li prvo ažurirati?"
+ German -> "Ein Update für aura ist verfügbar. Dies zuerst aktualisieren?"
+ Spanish -> "Hay una actualización de aura disponible. ¿Deseas actualizar aura primero?"
+ Norwegian -> "En Aura-oppdatering er tilgjengelig. Oppdater den først?"
+ Portuguese -> "Uma atualização para Aura está disponível. Deseja atualizar antes?"
+ French -> "Une mise à jour d'Aura est disponible. Voulez-vous la mettre à jour en premier ?"
+ Russian -> "Доступно обновление Aura. Обновить сперва её?"
+ Indonesia -> "Pemutakhiran aura tersedia. Mutakhirkan aura dulu?"
+ Chinese -> "Aura 可以升级。先升级 aura?"
+ Swedish -> "Det finns en uppdatering tillgänglig till Aura. Vill du uppdatera Aura först?"
+ _ -> "Aura update available. Update it first?"
+
+install_2 :: Language -> Doc AnsiStyle
+install_2 = \case
+ Japanese -> "適切なパッケージを入力してください。"
+ Polish -> "Nie podano prawidłowych pakietów."
+ Croatian -> "Nije specificiran nijedan ispravan paket."
+ Swedish -> "Inga giltiga paket valda."
+ German -> "Keine gültigen Pakete angegeben."
+ Spanish -> "No se ha especificado ningún paquete válido."
+ Portuguese -> "Nenhum pacote válido foi especificado."
+ French -> "Aucun paquet valide n'a été spécifié."
+ Russian -> "Валидные пакеты не указаны."
+ Italian -> "Nessun pacchetto valido specificato."
+ Serbian -> "Ниједан исправан пакет није специфициран."
+ Norwegian -> "Ingen gyldige pakker er valgte."
+ Indonesia -> "Tidak ada paket valid yang dispesifikkan."
+ Chinese -> "没有指定有效的包。"
+ _ -> "No valid packages specified."
+
+install_3 :: Language -> Doc AnsiStyle
+install_3 = \case
+ Japanese -> "続行しますか?"
+ Polish -> "Kontynuować?"
+ Croatian -> "Nastaviti?"
+ Swedish -> "Fortsätta?"
+ German -> "Fortsetzen?"
+ Spanish -> "¿Continuar?"
+ Portuguese -> "Continuar?"
+ French -> "Continuer ?"
+ Russian -> "Продолжить?"
+ Italian -> "Continuare?"
+ Serbian -> "Наставити?"
+ Norwegian -> "Fortsett?"
+ Indonesia -> "Lanjut?"
+ Chinese -> "继续?"
+ _ -> "Continue?"
+
+install_4 :: Language -> Doc AnsiStyle
+install_4 = \case
+ Japanese -> "続行は意図的に阻止されました。"
+ Polish -> "Instalacja została przerwana przez użytkownika."
+ Croatian -> "Instalacija prekinuta od strane korisnika."
+ Swedish -> "Installationen avbröts manuellt."
+ German -> "Installation durch Benutzer abgebrochen."
+ Spanish -> "Instalación abortada manualmente."
+ Portuguese -> "Instalação cancelada manualmente."
+ French -> "Installation manuelle annulée."
+ Russian -> "Пользователь прервал установку."
+ Italian -> "Installazione manuale interrotta."
+ Serbian -> "Инсталација је ручно прекинута."
+ Norwegian -> "Installasjonen ble avbrutt manuelt."
+ Indonesia -> "Instalasi dibatalkan secara paksa."
+ Chinese -> "手动安装已中止。"
+ _ -> "Installation manually aborted."
+
+install_5 :: Language -> Doc AnsiStyle
+install_5 = \case
+ Japanese -> "従属パッケージを確認中・・・"
+ Polish -> "Ustalanie zależności..."
+ Croatian -> "Određivanje zavisnosti..."
+ Swedish -> "Avgör beroenden..."
+ German -> "Bestimme Abhängigkeiten..."
+ Spanish -> "Determinando dependencias..."
+ Portuguese -> "Determinando as dependências..."
+ French -> "Détermination des dépendances en cours…"
+ Russian -> "Определение зависимостей..."
+ Italian -> "Determinazione dipendenze..."
+ Serbian -> "Утврђивање зависности..."
+ Norwegian -> "Bestemmer avhengigheter..."
+ Indonesia -> "Menentukan dependensi..."
+ Chinese -> "确定依赖中..."
+ _ -> "Determining dependencies..."
+
+-- 2014 December 7 @ 14:45 - NEEDS TRANSLATIONS
+confirmIgnored_1 :: PkgName -> Language -> Doc AnsiStyle
+confirmIgnored_1 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "は無視されるはずのパッケージです。それでも続行しますか?"
+ Polish -> p <> " jest oznaczony jako ignorowany. Zainstalować mimo tego?"
+ Spanish -> p <> " está marcado como ignorado. ¿Deseas instalarlo de todas formas?"
+ Portuguese -> p <> " está marcado como Ignorado. Deseja instalar mesmo assim?"
+ Russian -> p <> " отмечен как игнорируемый. Всё равно установить?"
+ Chinese -> p <> " 已被标记为忽略。仍然安装?"
+ Swedish -> p <> " är markerad som ignorerad. Vill du installera ändå?"
+ _ -> p <> " is marked as Ignored. Install anyway?"
+
+-- NEEDS UPDATE TO REFLECT CHANGED ENGLISH
+reportNonPackages_1 :: Language -> Doc AnsiStyle
+reportNonPackages_1 = \case
+ Japanese -> "下記はAURパッケージではありません:"
+ Polish -> "To nie są pakiety:"
+ Croatian -> "Ovo nisu AUR paketi:"
+ Swedish -> "Följande är inte paket:"
+ German -> "Folgende sind keine AUR-Pakete:"
+ Spanish -> "Los siguientes paquetes no son de AUR:"
+ Portuguese -> "Os seguintes não são pacotes AUR:"
+ French -> "Les éléments suivants ne sont pas des paquets AUR :"
+ Russian -> "Ниже указано то, что не является пакетами AUR:"
+ Italian -> "I seguenti pacchetti non sono presenti in AUR:"
+ Serbian -> "Ово нису пакети:"
+ Norwegian -> "Det følgende er ikke AUR-pakker:"
+ Indonesia -> "Paket berikut ini bukan merupakan paket AUR:"
+ Chinese -> "以下软件不是 AUR 包:"
+ _ -> "The following are not AUR packages:"
+
+-- NEEDS TRANSLATION
+reportUnneededPackages_1 :: Language -> Doc AnsiStyle
+reportUnneededPackages_1 = \case
+ Japanese -> "下記のパッケージは既にインストールされています:"
+ Polish -> "Następujące pakiety zostały już zainstalowane:"
+ Portuguese -> "Os seguintes pacotes já estão instalados:"
+ Russian -> "Следующие пакеты уже установлены:"
+ German -> "Die folgenden Pakete sind bereits installiert:"
+ Spanish -> "Los siguientes paquetes ya están instalados:"
+ Chinese -> "以下包已被安装:"
+ Swedish -> "Följande paket är redan installerade:"
+ _ -> "The following packages are already installed:"
+
+reportPkgsToInstall_1 :: Language -> Doc AnsiStyle
+reportPkgsToInstall_1 = \case
+ Japanese -> "Pacmanの従属パッケージ:"
+ Polish -> "Zależności z repozytoriów:"
+ Croatian -> "Zavisnosti iz repozitorija:"
+ Swedish -> "Beroenden ifrån lager:"
+ German -> "Abhängigkeiten in den Paketquellen:"
+ Spanish -> "Dependencias del repositorio:"
+ Portuguese -> "Dependências no repositório:"
+ French -> "Dépendances du dépôt :"
+ Russian -> "Зависимости из репозитория:"
+ Italian -> "Dipendenze nei repository:"
+ Serbian -> "Зависности из ризница:"
+ Norwegian -> "Avhengigheter fra depotet:"
+ Indonesia -> "Dependensi dari repositori:"
+ Chinese -> "仓库依赖:"
+ _ -> "Repository dependencies:"
+
+-- NEEDS AN UPDATE
+reportPkgsToInstall_2 :: Language -> Doc AnsiStyle
+reportPkgsToInstall_2 = \case
+ Japanese -> "AURのパッケージ:"
+ Polish -> "AUR Pakiety:"
+ Croatian -> "AUR Paketi:"
+ German -> "AUR Pakete:"
+ Spanish -> "AUR Paquetes:"
+ Norwegian -> "AUR Pakker:"
+ Italian -> "AUR Pacchetti:"
+ Portuguese -> "AUR Pacotes:"
+ French -> "AUR Paquets :"
+ Russian -> "AUR Пакеты:"
+ Indonesia -> "AUR Paket:"
+ Chinese -> "AUR 包:"
+ Swedish -> "AUR Paket:"
+ _ -> "AUR Packages:"
+
+reportPkgsToInstall_3 :: Language -> Doc AnsiStyle
+reportPkgsToInstall_3 = \case
+ Japanese -> "AURの従属パッケージ:"
+ Polish -> "Zależności z AUR:"
+ Croatian -> "Zavisnosti iz AUR-a:"
+ Swedish -> "Beroenden ifrån AUR:"
+ German -> "Abhängigkeiten im AUR:"
+ Spanish -> "Dependencias en AUR:"
+ Portuguese -> "Dependências no AUR:"
+ French -> "Dépendances AUR\xa0:"
+ Russian -> "Зависимости из AUR:"
+ Italian -> "Dipendenze in AUR:"
+ Serbian -> "Зависности из AUR-а:"
+ Norwegian -> "Avhengigheter fra AUR:"
+ _ -> "AUR dependencies:"
+
+-- NEEDS TRANSLATION
+reportPkgbuildDiffs_1 :: PkgName -> Language -> Doc AnsiStyle
+reportPkgbuildDiffs_1 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "のPKGBUILDはまだ保存されていません。"
+ Polish -> p <> " nie ma jeszcze przechowywanego pliku PKGBUILD."
+ Croatian -> p <> " još nema pohranjen PKGBUILD."
+ German -> p <> " hat noch keinen gespeicherten PKGBUILD."
+ Spanish -> p <> " no tiene un PKGBUILD almacenado aún."
+ Portuguese -> p <> " não possui PKGBUILD."
+ French -> p <> " n'a pas encore de PKGBUILD enregistré."
+ Russian -> "У " <> p <> " ещё нет сохраненного PKGBUILD."
+ Italian -> p <> " non ci sono PKGBUILD salvati"
+ Serbian -> p <> " још нема похрањен PKGBUILD."
+ Norwegian -> p <> " har ingen PKGBUILD ennå."
+ Indonesia -> p <> " tidak mempunyai PKGBUILD yang tersimpan untuk saat ini."
+ Chinese -> p <> " 还没有保存的 PKGBUILD。"
+ Swedish -> p <> " har ännu ingen PKGBUILD."
+ _ -> p <> " has no stored PKGBUILD yet."
+
+-- NEEDS TRANSLATION
+reportPkgbuildDiffs_2 :: T.Text -> Language -> Doc AnsiStyle
+reportPkgbuildDiffs_2 (bt -> p) = \case
+ Japanese -> p <> "のPKGBUILDは最新です。"
+ Polish -> "PKGBUILD pakietu " <> p <> " jest aktualny."
+ Croatian -> "PKGBUILD paketa " <> p <> " je na najnovijoj verziji."
+ German -> "PKGBUILD von " <> p <> " ist aktuell."
+ Spanish -> "El PKGBUILD de " <> p <> " está actualizado."
+ Portuguese -> "O PKGBUILD de " <> p <> "está atualizado."
+ Russian -> "PKGBUILD " <> p <> " является новейшим."
+ French -> "Le PKGBUILD de " <> p <> " est à jour."
+ Italian -> "Il PKGBUILD di " <> p <> " è aggiornato."
+ Serbian -> "PKGBUILD пакета " <> p <> " је ажуран."
+ Norwegian -> p <> "'s PKGBUILD er oppdatert."
+ Indonesia -> "PKGBUILD dari paket " <> p <> " sudah mutakhir."
+ Chinese -> p <> " 的 PKGBUILD 已经最新。"
+ Swedish -> "PKGBUILD för " <> p <> " är aktuell."
+ _ -> p <> " PKGBUILD is up to date."
+
+-- NEEDS TRANSLATION
+reportPkgbuildDiffs_3 :: PkgName -> Language -> Doc AnsiStyle
+reportPkgbuildDiffs_3 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "のPKGBUILD変更報告:"
+ Polish -> "Zmiany w PKGBUILD dla " <> p <> ":"
+ Croatian -> "Promjene u PKGBUILD-u za " <> p <> ":"
+ German -> "PKGBUILD-Änderungen von " <> p <> ":"
+ Spanish -> "Cambios en el PKGBUILD de " <> p <> ":"
+ Portuguese -> "Mudanças no PKGBUILD de " <> p <> ":"
+ Russian -> "Изменения, вносимые " <> p <> " PKGBUILD:"
+ French -> "Changements du PKGBUILD de " <> p <> " :"
+ Italian -> "Cambiamenti nel PKGBUILD di " <> p <>":"
+ Serbian -> "Промене PKGBUILD-a за " <> p <> ":"
+ Norwegian -> p <> "'s endringer i PKGBUILD:"
+ Indonesia -> "Perubahan PKGBUILD " <> p <> ":"
+ Chinese -> p <> " 的 PKGBUILD 变化:"
+ Swedish -> "Förändringar i PKGBUILD för " <> p <> ":"
+ _ -> p <> " PKGBUILD changes:"
+
+-- NEEDS TRANSLATION
+reportPkgsToUpgrade_1 :: Language -> Doc AnsiStyle
+reportPkgsToUpgrade_1 = \case
+ Japanese -> "アップグレードするAURパッケージ:"
+ Polish -> "Pakiety z AUR do zaktualizowania:"
+ Croatian -> "AUR paketi za nadogradnju:"
+ Swedish -> "AUR-paket att uppgradera:"
+ German -> "Zu aktualisierendes AUR-Paket:"
+ Spanish -> "Paquetes de AUR a actualizar:"
+ Portuguese -> "Pacotes do AUR para atualizar:"
+ French -> "Paquets AUR à mettre à jour :"
+ Russian -> "Пакеты AUR, готовые для обновления:"
+ Italian -> "Pacchetti in AUR da aggiornare:"
+ Serbian -> "Пакети из AUR-а за надоградњу:"
+ Norwegian -> "AUR-pakker å oppgradere:"
+ Indonesia -> "Paket AUR yang akan ditingkatkan:"
+ Chinese -> "要升级的 AUR 包:"
+ _ -> "AUR Packages to upgrade:"
+
+-- NEEDS UPDATING
+reportBadDowngradePkgs_1 :: Language -> Doc AnsiStyle
+reportBadDowngradePkgs_1 = \case
+ Japanese -> "このパッケージはキャッシュには入っていないので、ダウングレードできません。"
+ Polish -> "Poniższe pakeity nie są zainstalowane, i nie mogą być zainstalowane w starszej wersji:"
+ Croatian -> "Sljedeći paketi nisu instalirani te se stoga ne mogu vratiti na stare verzije:"
+ Swedish -> "Följande paket är inte installerade, och kan därför inte bli nergraderade:"
+ German -> "Folgende Pakete sind in keiner Version im Cache und können daher nicht gedowngradet werden:"
+ Spanish -> "Los siguientes paquetes no tienen versiones en la caché, por lo que no se pueden bajar a versiones anteriores:"
+ Portuguese -> "Os seguintes pacotes não possuem versões no cache, logo não podem retornar a uma versão anterior:"
+ French -> "Aucune version des paquets suivants n'est présente dans le cache ; ils ne peuvent pas être mis à niveau à une version antérieure :"
+ Russian -> "Следующих пакетов нет в кэше. Следовательно, они не могут быть откачены к старой версии:"
+ Italian -> "I seguenti pacchetti non hanno versioni in cache e non posso essere retrocessi:"
+ Serbian -> "Следећи пакети нису ни инсталирани, те се не могу вратити на старију верзију:"
+ Norwegian -> "Følgende pakker har ingen versjoner i cache, og kan derfor ikke bli nedgradert:"
+ Indonesia -> "Berikut ini tidak mempunyai versi pada cache, sehingga tidak akan diturunkan:"
+ Chinese -> "以下包在缓存中没有版本,所以无法被降级:"
+ _ -> "The following have no versions in the cache, and thus can’t be downgraded:"
+
+reportBadDowngradePkgs_2 :: PkgName -> Language -> Doc AnsiStyle
+reportBadDowngradePkgs_2 (PkgName p) = \case
+ _ -> pretty p <+> "has no version in the cache."
+
+upgradeAURPkgs_1 :: Language -> Doc AnsiStyle
+upgradeAURPkgs_1 = \case
+ Japanese -> "パッケージ情報をダウンロード中・・・"
+ Polish -> "Pobieranie informacji o pakietach..."
+ Croatian -> "Preuzimanje podataka o paketima..."
+ Swedish -> "Hämtar paketinformation..."
+ German -> "Rufe Paketinformationen ab..."
+ Spanish -> "Obteniendo información de los paquetes..."
+ Portuguese -> "Obtendo informação dos pacotes..."
+ French -> "Obtention des informations des paquets en cours…"
+ Russian -> "Сборка информации о пакетах..."
+ Italian -> "Ottengo le informazioni del pacchetto..."
+ Serbian -> "Преузимање информација о пакетима..."
+ Norwegian -> "Henter pakkeinformasjon..."
+ Indonesia -> "Mengambil informasi paket..."
+ Chinese -> "正在获取包信息..."
+ _ -> "Fetching package information..."
+
+upgradeAURPkgs_2 :: Language -> Doc AnsiStyle
+upgradeAURPkgs_2 = \case
+ Japanese -> "バージョンを比較中・・・"
+ Polish -> "Porównywanie wersji pakietów..."
+ Croatian -> "Uspoređivanje verzija paketa..."
+ Swedish -> "Jämför paket-versioner..."
+ German -> "Vergleiche Paketversionen..."
+ Spanish -> "Comparando versiones de los paquetes..."
+ Portuguese -> "Comparando versões dos pacotes..."
+ French -> "Comparaison des versions des paquets en cours…"
+ Russian -> "Сравнение версий пакетов..."
+ Italian -> "Confronto le ersioni del pacchetto..."
+ Serbian -> "Упоређивање верзија пакета..."
+ Norwegian -> "Sammenligner pakkeversjoner..."
+ Indonesia -> "Membandingkan versi paket..."
+ Chinese -> "正在比较包的版本..."
+ _ -> "Comparing package versions..."
+
+upgradeAURPkgs_3 :: Language -> Doc AnsiStyle
+upgradeAURPkgs_3 = \case
+ Japanese -> "アップグレードは必要ありません。"
+ Polish -> "Nie jest wymagana aktualizacja pakietów z AUR."
+ Croatian -> "Svi AUR paketi su ažurirani."
+ Swedish -> "Inga AUR-paketsuppgraderingar behövs."
+ German -> "Keine Aktualisierungen für AUR-Paket notwendig."
+ Spanish -> "No es necesario actualizar paquetes de AUR."
+ Portuguese -> "Nenhum pacote do AUR precisa de atualização."
+ French -> "Aucune mise à jour de paquet AUR n'est nécessaire."
+ Russian -> "Обновление пакетов из AUR не требуется."
+ Italian -> "Non è necessario aggiornare pacchetti di AUR."
+ Serbian -> "Ажурирање пакета из AUR-а није потребно."
+ Norwegian -> "Ingen pakkeoppgradering fra AUR nødvendig."
+ Indonesia -> "Tidak ada peningkatan AUR yang dibutuhkan."
+ Chinese -> "没有需要升级的 AUR 包。"
+ _ -> "No AUR package upgrades necessary."
+
+removeMakeDepsAfter_1 :: Language -> Doc AnsiStyle
+removeMakeDepsAfter_1 = \case
+ Japanese -> "あと片付け。必要ないパッケージを削除:"
+ Polish -> "Usuwanie niepotrzebnych zależności potrzebnych do budowy..."
+ Croatian -> "Uklanjanje nepotrebnih zavisnosti vezanih uz izgradnju..."
+ Swedish -> "Tar bort obehövda beroenden för `make`..."
+ German -> "Entferne nicht benötigte make-Abhängigkeiten..."
+ Spanish -> "Removiendo dependencias `make` innecesarias..."
+ Portuguese -> "Removendo dependências `make` desnecessárias..."
+ French -> "Suppression des dépendances inutiles…"
+ Russian -> "Удаление ненужных зависимостей make..."
+ Italian -> "Rimuovo le dipendenze di compilazione..."
+ Serbian -> "Уклањање непотребних зависности за изградњу..."
+ Norwegian -> "Fjerner unødvendige make-avhengigheter..."
+ Indonesia -> "Menghapus dependensi `make` yang tidak dibutuhkan..."
+ Chinese -> "移除不需要的 make 依赖..."
+ _ -> "Removing unneeded make dependencies..."
+
+----------------------------
+-- Aura/Commands/B functions
+----------------------------
+-- NEEDS TRANSLATION
+cleanStates_2 :: Int -> Language -> Doc AnsiStyle
+cleanStates_2 n@(bt . T.pack . show -> s) = \case
+ Japanese -> s <> "個のパッケージ状態記録だけが残される。その他削除?"
+ Polish -> s <> " stan pakietów zostanie zachowany. Usunąć resztę?"
+ Croatian -> s <> " stanja paketa će biti zadržano. Ukloniti ostatak?"
+ German -> s <> " Paketzustände werden behalten. Den Rest entfernen?"
+ Spanish -> "El estado del paquete" <> s <> " se mantendrá. ¿Deseas eliminar el resto?"
+ Serbian -> s <> " стања пакета ће бити сачувано. Уклонити остатак?"
+ Norwegian -> s <> " pakketilstander vil bli beholdt. Vil du fjerne resten?"
+ Italian -> s <> " lo stato dei pacchetti sarà mantenuto. Rimuovere i rimanenti?"
+ Portuguese -> s <> " estados de pacotes serão mantidos. Remover o resto?"
+ French -> s <> " états des paquets vont être conservés. Supprimer le reste ?"
+ Russian -> s <> pluralRussian " состояние пакетов будет оставлено." " состояния пакетов будут оставлены." " состояний пакетов будет оставлено." n <> " Удалить оставшиеся?"
+ Indonesia -> s <> " paket akan tetap sama. Hapus yang lainnya?"
+ Chinese -> s <> " 个包的状态将会保留。删除其它的?"
+ Swedish -> s <> " paket kommer att bevaras. Ta bort resten?"
+ _ -> s <> " package states will be kept. Remove the rest?"
+
+-- NEEDS TRANSLATION
+cleanStates_3 :: Language -> Doc AnsiStyle
+cleanStates_3 = \case
+ Japanese -> "何も削除しないで終了します。"
+ Polish -> "Żaden stan pakietu nie został usunięty."
+ Croatian -> "Nijedno stanje paketa nije uklonjeno."
+ German -> "Keine Paketzustände wurden entfernt."
+ Spanish -> "No se han eliminado estados de los paquetes."
+ Serbian -> "Ниједно стање пакета није уклоњено."
+ Norwegian -> "Ingen pakketilstander ble fjernet."
+ Italian -> "Nessuno stato di pacchetto verrà rimosso."
+ Portuguese -> "Nenhum estado de pacote será removido."
+ French -> "Aucun état des paquets n'a été supprimé."
+ Russian -> "Состояния пакетов отались нетронутыми."
+ Indonesia -> "Tidak ada paket yang dihapus."
+ Chinese -> "没有删除任何包。"
+ Swedish -> "Inga paket togs bort."
+ _ -> "No package states were removed."
+
+cleanStates_4 :: Int -> Language -> Doc AnsiStyle
+cleanStates_4 n = \case
+ Japanese -> "現在のパッケージ状態記録:" <> pretty n <> "個。"
+ Russian -> "У вас сейчас " <+> pretty n <+> pluralRussian " сохраненное состояние пакета" " сохраненных состояний пакета" " сохраненных состояний пакетов." n
+ _ -> "You currently have" <+> pretty n <+> "saved package states."
+
+cleanStates_5 :: T.Text -> Language -> Doc AnsiStyle
+cleanStates_5 t = \case
+ Japanese -> "一番最近に保存されたのは:" <> pretty t
+ Russian -> "Последнее сохраненное:" <+> pretty t
+ _ -> "Mostly recently saved:" <+> pretty t
+
+cleanStates_6 :: Int -> Language -> Doc AnsiStyle
+cleanStates_6 n = \case
+ _ -> pretty n <+> "of these are pinned, and won't be removed."
+
+readState_1 :: Language -> Doc AnsiStyle
+readState_1 = \case
+ Portuguese -> "O arquivo de estado não pôde ser interpretado. É um arquivo JSON válido?"
+ Russian -> "Это состояние не распознано. Это корректный JSON?"
+ _ -> "That state file failed to parse. Is it legal JSON?"
+
+----------------------------
+-- Aura/Commands/C functions
+----------------------------
+getDowngradeChoice_1 :: PkgName -> Language -> Doc AnsiStyle
+getDowngradeChoice_1 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "はどのバージョンにしますか?"
+ Polish -> "Którą wersję pakietu " <> p <> " zainstalować?"
+ Croatian -> "Koju verziju paketa " <> p <> " želite?"
+ Swedish -> "Vilken version av " <> p <> " vill du ha?"
+ German -> "Welche Version von " <> p <> " möchten Sie haben?"
+ Spanish -> "¿Qué versión de " <> p <> " deseas?"
+ Portuguese -> "Qual versão de " <> p <> " deseja?"
+ French -> "Quelle version de " <> p <> " voulez-vous ?"
+ Russian -> "Какую версию " <> p <> " вы хотите?"
+ Italian -> "Quale versione di " <> p <> " preferisci?"
+ Serbian -> "Коју верзију " <> p <> "-а желите?"
+ Norwegian -> "Hvilken versjon av " <> p <> " vil du ha?"
+ Indonesia -> "Versi dari paket " <> p <> " mana yang anda inginkan?"
+ Chinese -> "你希望安装 " <> p <> " 的哪个版本?"
+ _ -> "What version of " <> p <> " do you want?"
+
+backupCache_3 :: Language -> Doc AnsiStyle
+backupCache_3 = \case
+ Japanese -> "バックアップ先は存在しません。"
+ Polish -> "Lokalizacja kopii zapasowych nie istnieje."
+ Croatian -> "Lokacija sigurnosne kopije ne postoji."
+ Swedish -> "Specifierad backup-plats finns inte."
+ German -> "Der Sicherungsort existiert nicht."
+ Spanish -> "La localización para copia de seguridad no existe."
+ Portuguese -> "Localização do backup não existe."
+ French -> "Le chemin des copies de sauvegarde spécifié n'existe pas."
+ Russian -> "Путь к бэкапу не существует."
+ Italian -> "L'indirizzo del salvataggio non esiste."
+ Serbian -> "Путања ка бекапу не постоји."
+ Norwegian -> "Spesifisert backup-plass finnes ikke."
+ Indonesia -> "Lokasi `backup` tidak ada."
+ Chinese -> "备份位置不存在。"
+ _ -> "The backup location does not exist."
+
+backupCache_4 :: FilePath -> Language -> Doc AnsiStyle
+backupCache_4 (bt . T.pack -> dir) = \case
+ Japanese -> "キャッシュのバックアップ先:" <> dir
+ Polish -> "Tworzenie kopii zapasowej pamięci podręcznej w " <> dir
+ Croatian -> "Stvaram sigurnosnu kopiju u " <> dir
+ Swedish -> "Tar backup på cache-filer till " <> dir
+ German -> "Sichere Cache in " <> dir
+ Spanish -> "Haciendo una copia de seguridad de la caché en " <> dir
+ Portuguese -> "Backup do cache sendo feito em " <> dir
+ French -> "Copie de sauvegarde dans " <> dir <> "."
+ Russian -> "Бэкап создается в директории " <> dir
+ Italian -> "Salvataggio della chace in " <> dir
+ Serbian -> "Бекапујем кеш у " <> dir
+ Norwegian -> "Tar backup på cache til " <> dir
+ Indonesia -> "Melakukan `backup` pada direktori " <> dir
+ Chinese -> "正在将缓存备份到 " <> dir
+ _ -> "Backing up cache to " <> dir
+
+backupCache_5 :: Int -> Language -> Doc AnsiStyle
+backupCache_5 (bt . T.pack . show -> n) = \case
+ Japanese -> "パッケージのファイル数:" <> n
+ Polish -> "Pliki będące częścią\xa0kopii zapasowej: " <> n
+ Croatian -> "Datoteke koje su dio sigurnosne kopije: " <> n
+ Swedish -> "Paket-filer att ta backup på: " <> n
+ German -> "Zu sichernde Paketdateien: " <> n
+ Spanish -> "Ficheros de paquetes de los que se hará copia de seguridad: " <> n
+ Portuguese -> "Arquivos de pacotes para backup: " <> n
+ French -> "Copie de sauvegarde des fichiers de paquets suivants : " <> n
+ Russian -> "Файлы пакета для бэкапа: " <> n
+ Italian -> "File del pacchetto da salvare: " <> n
+ Serbian -> "Датотеке за бекап: " <> n
+ Norwegian -> "Pakker som blir tatt backup på: " <> n
+ Indonesia -> "Jumlah paket yang di-`backup`: " <> n
+ Chinese -> "将要备份的包文件:" <> n
+ _ -> "Package files to backup: " <> n
+
+backupCache_6 :: Language -> Doc AnsiStyle
+backupCache_6 = \case
+ Japanese -> "バックアップを実行しますか?"
+ Polish -> "Kontynuować tworzenie kopii zapasowej?"
+ Croatian -> "Nastavi sa stvaranjem sigurnosne kopije?"
+ Swedish -> "Fortsätt med backup?"
+ German -> "Sicherung fortsetzen?"
+ Spanish -> "¿Proceder con la copia de seguridad?"
+ Portuguese -> "Proceder com o backup?"
+ French -> "Procéder à la copie de sauvegarde ?"
+ Russian -> "Продолжить создание бэкапа?"
+ Italian -> "Procedere con il salvataggio?"
+ Serbian -> "Наставити бекаповање?"
+ Norwegian -> "Fortsett med backup?"
+ Indonesia -> "Lanjutkan dengan `backup`?"
+ Chinese -> "开始备份?"
+ _ -> "Proceed with backup?"
+
+backupCache_7 :: Language -> Doc AnsiStyle
+backupCache_7 = \case
+ Japanese -> "バックアップは意図的に阻止されました。"
+ Polish -> "Tworzenie kopii zapasowej zostało przerwane przez użytkownika."
+ Croatian -> "Stvaranje sigurnosne kopije prekinuto od strane korisnika."
+ Swedish -> "Backup avbröts manuellt."
+ German -> "Backup durch Benutzer abgebrochen."
+ Spanish -> "Copia de seguridad abortada manualmente."
+ Portuguese -> "Backup cancelado manualmente."
+ French -> "Copie de sauvegarde manuelle annulée."
+ Russian -> "Создание бэкапа прервано пользователем."
+ Italian -> "Salvataggio manuale interrotto."
+ Serbian -> "Бекаповање је ручно прекинуто."
+ Norwegian -> "Backup ble avbrutt manuelt."
+ Indonesia -> "Proses `backup` dibatalkan secara paksa."
+ Chinese -> "手动备份已中止。"
+ _ -> "Backup manually aborted."
+
+backupCache_8 :: Language -> Doc AnsiStyle
+backupCache_8 = \case
+ Japanese -> "バックアップ中。数分かかるかもしれません。"
+ Polish -> "Tworzenie kopii zapasowej. To może potrwać kilka minut..."
+ Croatian -> "Stvaranje sigurnosne kopije. Ovo može potrajati nekoliko minuta..."
+ Swedish -> "Tar backup. Det här kan ta ett tag..."
+ German -> "Sichere. Dies kann einige Minuten dauern..."
+ Spanish -> "Haciendo copia de seguridad. Esto puede tardar unos minutos..."
+ Portuguese -> "Efetuando backup. Isso pode levar alguns minutos..."
+ French -> "Copie de sauvegarde en cours. Ceci peut prendre quelques minutes…"
+ Russian -> "Создается бэкап. Это может занять пару минут..."
+ Italian -> "Salvataggio. Questo potrebbe richiedere qualche minuto..."
+ Serbian -> "Бекапујем. Ово може да потраје пар минута..."
+ Norwegian -> "Tar backup. Dette kan ta en stund..."
+ Indonesia -> "Melakukan `backup`. Proses ini akan berjalan untuk beberapa menit..."
+ Chinese -> "正在备份中。可能需要几分钟的时间..."
+ _ -> "Backing up. This may take a few minutes..."
+
+copyAndNotify_1 :: Int -> Language -> Doc AnsiStyle
+copyAndNotify_1 (cyan . pretty -> n) = \case
+ Japanese -> "#[" <> n <> "]をコピー中・・・"
+ Polish -> "Kopiowanie #[" <> n <> "]"
+ Croatian -> "Kopiranje #[" <> n <> "]"
+ Swedish -> "Kopierar #[" <> n <> "]"
+ German -> "Kopiere #[" <> n <> "]"
+ Spanish -> "Copiando #[" <> n <> "]"
+ Portuguese -> "Copiando #[" <> n <> "]"
+ French -> "Copie de #[" <> n <> "]"
+ Russian -> "Копируется #[" <> n <> "]"
+ Italian -> "Copiando #[" <>n <> "]"
+ Serbian -> "Копирам #[" <> n <> "]"
+ Norwegian -> "Kopierer #[" <> n <> "]"
+ Indonesia -> "Menyalin #[" <> n <> "]"
+ Chinese -> "正在复制 #[" <> n <> "]"
+ _ -> "Copying #[" <> n <> "]"
+
+cleanCache_2 :: Language -> Doc AnsiStyle
+cleanCache_2 = \case
+ Japanese -> "パッケージ・キャッシュは完全に削除されます。"
+ Polish -> "To usunie WSZYSTKIE pakiety z pamięci podręcznej."
+ Croatian -> "Ovo će izbrisati CIJELI cache paketa."
+ Swedish -> "Detta kommer ta bort HELA paket-cachen."
+ German -> "Dies wird den GESAMTEN Paketcache leeren."
+ Spanish -> "Esto eliminará POR COMPLETO la caché de paquetes."
+ Portuguese -> "Isso removerá TODOS OS PACOTES do cache."
+ French -> "Ceci va supprimer la TOTALITÉ du cache des paquets."
+ Russian -> "Это действие ВСЕЦЕЛО уничтожит кэш пакетов."
+ Italian -> "Questo cancellera l'INTERA cache dei pacchetti."
+ Serbian -> "Ово ће избрисати ЦЕО кеш пакета."
+ Norwegian -> "Dette vil slette HELE pakke-cachen."
+ Indonesia -> "Akan menghapus SEMUA `cache` paket"
+ Chinese -> "这将会删除全部的包缓存。"
+ _ -> "This will delete the ENTIRE package cache."
+
+cleanCache_3 :: Word -> Language -> Doc AnsiStyle
+cleanCache_3 n@(bt . T.pack . show -> s) = \case
+ Japanese -> "パッケージ・ファイルは" <> s <> "個保存されます。"
+ Polish -> s <> " wersji każdego pakietu zostanie zachowane."
+ Croatian -> s <> " zadnjih verzija svakog paketa će biti zadržano."
+ Swedish -> s <> " av varje paketfil kommer att sparas."
+ German -> s <> " jeder Paketdatei wird behalten."
+ Spanish -> "Se mantendrán " <> s <> " ficheros de cada paquete."
+ Portuguese -> s <> " arquivos de cada pacote serão mantidos."
+ French -> s <> " fichiers de chaque paquet sera conservé."
+ Russian -> s <> pluralRussian " версия каждого пакета будет нетронута." " версии каждого пакета будут нетронуты." " версий каждого пакета будут нетронуты." n
+ Italian -> s <> " di ciascun pacchetto sarà mantenuto."
+ Serbian -> s <> " верзије сваког од пакета ће бити сачуване."
+ Norwegian -> s <> " av hver pakkefil blir beholdt."
+ Indonesia -> s <> " berkas dari tiap paket akan disimpan."
+ Chinese -> "每个包文件将会保存 " <> s <> " 个版本。"
+ _ -> s <> " of each package file will be kept."
+
+cleanCache_4 :: Language -> Doc AnsiStyle
+cleanCache_4 = \case
+ Japanese -> "残りは全部削除されます。承知していますか?"
+ Polish -> "Wszystko inne zostanie usunięte. Na pewno?"
+ Croatian -> "Ostali paketi će biti izbrisani. Jeste li sigurni?"
+ Swedish -> "Resten kommer att tas bort. Är det OK?"
+ German -> "Der Rest wird gelöscht. Ist das OK?"
+ Spanish -> "El resto se eliminarán. ¿De acuerdo?"
+ Portuguese -> "O resto será removido. OK?"
+ French -> "Le reste sera supprimé. Êtes-vous d'accord ?"
+ Russian -> "Всё остальное будет удалено. Годится?"
+ Italian -> "Il resto verrà mantenuto. Continuare?"
+ Serbian -> "Остатак ће бити избрисан. Да ли је то у реду?"
+ Norwegian -> "Resten vil bli slettet. Er det OK?"
+ Indonesia -> "Selainnya akan dihapus. Ikhlas kan?"
+ Chinese -> "其余的将会被删除。确定?"
+ _ -> "The rest will be deleted. Okay?"
+
+cleanCache_5 :: Language -> Doc AnsiStyle
+cleanCache_5 = \case
+ Japanese -> "削除の続行は意図的に阻止されました。"
+ Polish -> "Czyszczenie pamięci podręcznej zostało przerwane przez użytkownika."
+ Croatian -> "Čišćenje cache-a paketa prekinuto od strane korisnika."
+ Swedish -> "Cache-rensning avbröts manuellt."
+ German -> "Leeren des Caches durch Benutzer abgebrochen."
+ Spanish -> "Limpieza de la caché abortada manualmente."
+ Portuguese -> "Limpeza do cache cancelada manualmente."
+ French -> "Le nettoyage du cache a été arrêté manuellement."
+ Russian -> "Очистка кэша прервана пользователем."
+ Italian -> "Pulitura manuale della cache interrotta."
+ Serbian -> "Чишћење кеша је ручно прекинуто."
+ Norwegian -> "Cache-rensing ble avbrutt manuelt."
+ Indonesia -> "Pembersihan `cache` dibatalkan secara paksa."
+ Chinese -> "手动清理缓存已中止。"
+ _ -> "Cache cleaning manually aborted."
+
+cleanCache_6 :: Language -> Doc AnsiStyle
+cleanCache_6 = \case
+ Japanese -> "パッケージ・キャッシュを掃除中・・・"
+ Polish -> "Czyszczenie pamięci podręcznej..."
+ Croatian -> "Čišćenje cache-a paketa..."
+ Swedish -> "Rensar paket-cache..."
+ German -> "Leere Paketcache..."
+ Spanish -> "Limpiando la caché de paquetes..."
+ Portuguese -> "Limpando cache de pacotes..."
+ French -> "Nettoyage du cache des paquets…"
+ Russian -> "Очистка кэша пакета..."
+ Italian -> "Ripulisco la cache..."
+ Serbian -> "Чишћење кеша..."
+ Norwegian -> "Renser pakke-cache..."
+ Indonesia -> "Membersihkan `cache` paket..."
+ Chinese -> "正在清理包缓存..."
+ _ -> "Cleaning package cache..."
+
+-- NEEDS TRANSLATION
+cleanNotSaved_1 :: Language -> Doc AnsiStyle
+cleanNotSaved_1 = \case
+ Japanese -> "不要パッケージファイルを確認・・・"
+ Polish -> "Określanie niepotrzebnych plków pakietów"
+ Croatian -> "Pronalazim nepotrebne datoteke paketa..."
+ German -> "Bestimme nicht benötigte Paketdateien..."
+ Spanish -> "Determinando ficheros de paquetes innecesarios..."
+ Norwegian -> "Finner unødige pakkefiler..."
+ Italian -> "Determino i pacchetti non più necessari..."
+ Portuguese -> "Determinando pacotes não necessários..."
+ French -> "Détermination des fichiers de paquet inutiles…"
+ Russian -> "Вычисляются ненужные файлы пакетов..."
+ Indonesia -> "Menentukan berkas paket yang tidak dibutuhkan..."
+ Chinese -> "正在确定不需要的包文件..."
+ Swedish -> "Beräknar onödiga paketfiler..."
+ _ -> "Determining unneeded package files..."
+
+-- NEEDS TRANSLATION
+cleanNotSaved_2 :: Int -> Language -> Doc AnsiStyle
+cleanNotSaved_2 n@(cyan . pretty -> s) = \case
+ Japanese -> "「" <> s <> "」の不要パッケージファイルがあります。削除しますか?"
+ Polish -> s <> " niepotrzebnych plików zostało znalezionych. Usunąć?"
+ Croatian -> s <> " nepotrebnih datoteka pronađeno. Obrisati?"
+ German -> s <> " nicht benötigte Paketdateien gefunden. Löschen?"
+ Spanish -> s <> " ficheros innecesarios de paquetes encontrados. ¿Deseas eliminarlos?"
+ Norwegian -> s <> " unødige pakkefiler funnet. Vil du slette?"
+ Italian -> s <> " pacchetti non necessari trovati. Cancellarli?"
+ Portuguese -> s <> " pacotes não necessários encontrados. Removê-los?"
+ French -> s <> " paquets inutiles trouvés. Les supprimer ?"
+ Russian -> pluralRussian ("Обнаружен " <> s <> " ненужный файл пакета.") ("Обнаружены " <> s <> " ненужных файла пакетов.") ("Обнаружено " <> s <> " ненужных файлов пакетов.") n <> " Удалить?"
+ Indonesia -> s <> " berkas paket yang tidak dibutuhkan ditemukan. Hapus?"
+ Chinese -> "发现了 " <> s <> " 个不需要的包文件。是否删除?"
+ Swedish -> s <> " oanvända paket hittades. Ta bort?"
+ _ -> s <> " unneeded package files found. Delete?"
+
+----------------------------
+-- Aura/Commands/L functions
+----------------------------
+logLookUpFields :: Language -> [T.Text]
+logLookUpFields = sequence [ Fields.package
+ , Fields.firstInstall
+ , Fields.upgrades
+ , Fields.recentActions ]
+
+reportNotInLog_1 :: Language -> Doc AnsiStyle
+reportNotInLog_1 = \case
+ Japanese -> "logファイルには出ていない:"
+ Polish -> "Tych pakietów nie ma w dzienniku:"
+ Croatian -> "Ovih paketa nema u dnevniku:"
+ Swedish -> "Dessa har inte framkommit i loggfiler:"
+ German -> "Diese sind nicht in der Logdatei aufgetaucht:"
+ Spanish -> "Estos no aparecen en el fichero log:"
+ Portuguese -> "Os seguintes não apareceram no arquivo de log:"
+ French -> "Ceci n'apparaît pas des les journaux (log) :"
+ Russian -> "Следующих пакетов нет в лог-файле:"
+ Italian -> "Questo non apparirà nei file di log;"
+ Serbian -> "Ови пакети се не спомињу у дневнику:"
+ Norwegian -> "Følgende har ikke vist seg i loggen:"
+ Indonesia -> "Tidak terlihat pada berkas log:"
+ Chinese -> "这些没有在日志文件中出现:"
+ _ -> "These have not appeared in the log file:"
+
+-------------------------------
+-- Aura/AUR functions
+-------------------------------
+
+-- https://github.com/aurapm/aura/issues/498
+connectionFailure_1 :: Language -> Doc AnsiStyle
+connectionFailure_1 = \case
+ _ -> "Failed to contact the AUR. Do you have an internet connection?"
+
+infoFields :: Language -> [T.Text]
+infoFields = sequence [ Fields.repository
+ , Fields.name
+ , Fields.version
+ , Fields.aurStatus
+ , Fields.maintainer
+ , Fields.projectUrl
+ , Fields.aurUrl
+ , Fields.license
+ , Fields.dependsOn
+ , Fields.buildDeps
+ , Fields.votes
+ , Fields.popularity
+ , Fields.description
+ ]
+
+outOfDateMsg :: Maybe Int -> Language -> Doc AnsiStyle
+outOfDateMsg (Just _) = red . \case
+ Japanese -> "AURで要更新!"
+ Polish -> "Nieaktualny!"
+ Croatian -> "Zastarjelo!"
+ Swedish -> "Utdaterad!"
+ German -> "Veraltet!"
+ Spanish -> "¡Desactualizado!"
+ Portuguese -> "Desatualizado!"
+ French -> "Périmé !"
+ Russian -> "Устарел!"
+ Italian -> "Out of Date!"
+ Serbian -> "Застарео!"
+ Norwegian -> "Utdatert!"
+ Indonesia -> "Ketinggalan Zaman!"
+ Chinese -> "过期!"
+ _ -> "Out of Date!"
+outOfDateMsg Nothing = green . \case
+ Japanese -> "最新"
+ Polish -> "Aktualny"
+ Croatian -> "Ažurirano"
+ Swedish -> "Aktuell"
+ German -> "Aktuell"
+ Spanish -> "Actualizado"
+ Portuguese -> "Atualizado"
+ French -> "À jour"
+ Russian -> "Новейший"
+ Italian -> "Aggiornato"
+ Serbian -> "Ажуран"
+ Norwegian -> "Oppdatert"
+ Indonesia -> "Mutakhir"
+ Chinese -> "最新"
+ _ -> "Up to Date"
+
+-- NEEDS TRANSLATION
+orphanedMsg :: Maybe T.Text -> Language -> Doc AnsiStyle
+orphanedMsg (Just m) = const (pretty m)
+orphanedMsg Nothing = red . \case
+ Japanese -> "孤児です!"
+ Polish -> "Osierocony!"
+ Croatian -> "Nema roditelja!"
+ German -> "Verwaist!"
+ Spanish -> "¡Huérfano!"
+ Norwegian -> "Foreldreløs!"
+ Portuguese -> "Órfão!"
+ French -> "Abandonné !"
+ Russian -> "Осиротевший!"
+ Indonesia -> "Tak dipelihara!"
+ Chinese -> "孤包!"
+ Swedish -> "Föräldralös!"
+ _ -> "Orphaned!"
+
+-----------------------
+-- Aura/State functions
+-----------------------
+-- NEEDS TRANSLATION
+saveState_1 :: Language -> Doc AnsiStyle
+saveState_1 = \case
+ Japanese -> "パッケージ状態の保存完了。"
+ Polish -> "Zachowano stan pakietów"
+ Croatian -> "Stanje paketa spremljeno."
+ German -> "Paketzustand gesichert."
+ Spanish -> "Estado del paquete salvado."
+ Serbian -> "Сачувано стање пакета."
+ Norwegian -> "Lagret pakketilstand."
+ Italian -> "Stato del pacchetto salvato."
+ Portuguese -> "Estado de pacote salvo."
+ French -> "État des paquets sauvegardé."
+ Russian -> "Состояние пакетов сохранено."
+ Indonesia -> "Kondisi paket tersimpan."
+ Chinese -> "已保存包状态。"
+ Swedish -> "Det lokala pakettillståndet har sparats."
+ _ -> "Saved package state."
+
+-- NEEDS TRANSLATION
+restoreState_1 :: Language -> Doc AnsiStyle
+restoreState_1 = \case
+ Japanese -> "対象バージョンがないパッケージ:"
+ Polish -> "Starsze wersje nie są dostępne dla:"
+ Croatian -> "Tražene stare verzije nisu dostupne za:"
+ German -> "Gewünschte Downgrade-Versionen nicht verfügbar für:"
+ Spanish -> "Versiones anteriores no disponibles para:"
+ Serbian -> "Захтеване старе верзије нису доступне за:"
+ Norwegian -> "De spesifiserte nedgraderingsversjonene er ikke tilgjengelig for:"
+ Italian -> "Richiesta di retrocessione di versione non disponibile per:"
+ Portuguese -> "Versões anteriores requisitadas não disponívels para:"
+ French -> "Version antérieure requise non disponible pour :"
+ Russian -> "Запрошенные версии для отката не доступны для:"
+ Indonesia -> "Versi yang diturunkan tidak tersedia untuk: "
+ Chinese -> "请求的降级版本对以下包不可用:"
+ Swedish -> "Den begärda nedgraderingen finns inte tillgänglig för:"
+ _ -> "Requested downgrade versions not available for:"
+
+restoreState_2 :: Language -> Doc AnsiStyle
+restoreState_2 = \case
+ Japanese -> "保存されたパッケージ状態がない。作るには「-B」を。"
+ Portuguese -> "Nenhum estado disponível para ser recuperado. (Utilize -B para salvar o estado atual)"
+ Russian -> "Нет сохраненных состояний для восстановления. (Используйте -B для сохранения текущего состояния)"
+ Chinese -> "没有要恢复的已保存状态。(使用 -B 保存当前状态)"
+ Swedish -> "Inga sparade tillstånd att återhämta. (Använd -B för att spara det nuvarande tillståndet)"
+ _ -> "No saved states to be restored. (Use -B to save the current state)"
+
+-- NEEDS TRANSLATION
+reinstallAndRemove_1 :: Language -> Doc AnsiStyle
+reinstallAndRemove_1 = \case
+ Japanese -> "パッケージを変更する必要はありません。"
+ Polish -> "Żaden pakiet nie wymaga zmian"
+ Croatian -> "Nema paketa kojima su potrebne izmjene."
+ German -> "Keine Pakete brauchen Änderungen."
+ Spanish -> "Ningún paquete necesita cambios."
+ Serbian -> "Ниједан пакет не захтева измене."
+ Norwegian -> "Ingen pakker trenger forandring."
+ Italian -> "Nessun pacchetto necessita cambiamenti."
+ Portuguese -> "Nenhum pacote requer alteração."
+ French -> "Aucun paquet n'a besoin de changement."
+ Russian -> "Пакеты не нуждаются в изменениях."
+ Indonesia -> "Tidak ada paket yang diubah."
+ Chinese -> "没有包需要改变。"
+ Swedish -> "Inga paket behöver ändras."
+ _ -> "No packages need changing."
+
+--------------------------------------
+-- Aura/Settings/BadPackages functions
+--------------------------------------
+whoIsBuildUser_1 :: Language -> Doc AnsiStyle
+whoIsBuildUser_1 = \case
+ Portuguese -> "Não foi possível determinal o usuário que executará a compilação."
+ Russian -> "Не удается определить, от имени какого пользователя производить сборку."
+ _ -> "Can't determine which user account to build with."
+
+------------------------
+-- Aura/Pacman functions
+------------------------
+-- NEEDS TRANSLATION
+pacmanFailure_1 :: Language -> Doc AnsiStyle
+pacmanFailure_1 = \case
+ Japanese -> "入力を確認して下さい。"
+ Polish -> "Sprawdź swoje dane wejściowe"
+ Croatian -> "Molim vas, provjerite svoj unos."
+ German -> "Bitte überprüfen Sie Ihre Eingabe."
+ Spanish -> "Por favor comprueba los datos proporcionados."
+ Serbian -> "Молим Вас, проверите ваш унос."
+ Norwegian -> "Vennligst sjekk din oppføring."
+ Italian -> "Controllare il proprio input."
+ Portuguese -> "Por favor, verifique os dados informados."
+ French -> "Merci de vérifier les donnés entrées."
+ Russian -> "Пожалуйста, проверьте ваши введенные данные."
+ Indonesia -> "Mohon periksa masukan anda."
+ Chinese -> "请检查你的输入。"
+ Swedish -> "Var god dubbelkolla indata."
+ _ -> "Please check your input."
+
+confParsing_1 :: Language -> Doc AnsiStyle
+confParsing_1 = \case
+ Portuguese -> "Não foi possível interpretar o arquivo pacman.conf ."
+ Russian -> "Не удается распознать формат вашего файла pacman.conf."
+ _ -> "Unable to parse your pacman.conf file."
+
+provides_1 :: PkgName -> Doc AnsiStyle
+provides_1 (bt . view (field @"name") -> pro) =
+ pro <+> "is required as a dependency, which is provided by multiple packages. Please select one:"
+
+----------------------------------
+-- Aura/Pkgbuild/Editing functions
+----------------------------------
+hotEdit_1 :: PkgName -> Language -> Doc AnsiStyle
+hotEdit_1 (bt . view (field @"name") -> p) = \case
+ Japanese -> p <> "のPKGBUILDを編成しますか?"
+ Polish -> "Czy chcesz edytować PKGBUILD " <> p <> "?"
+ Croatian -> "Želite li izmjeniti PKGBUILD " <> p <> "?"
+ Swedish -> "Vill du ändra PKGBUILD-filen ifrån " <> p <> "?"
+ German -> "Möchten Sie die PKGBUILD-Datei für " <> p <> " bearbeiten?"
+ Spanish -> "¿Deseas editar el PKGBUILD de " <> p <> "?"
+ Portuguese -> "Deseja editar o PKGBUILD de " <> p <> "?"
+ French -> "Voulez-vous éditer le PKGBUILD de " <> p <> " ?"
+ Russian -> "Отредактировать PKGBUILD пакета " <> p <> "?"
+ Italian -> "Volete modificare il PKGBUILD di " <> p <> "?"
+ Serbian -> "Желите ли да измените PKGBUILD за " <> p <> "?"
+ Norwegian -> "Vil du endre PKGBUILD for " <> p <> "?"
+ Indonesia -> "Apakah anda ingin menyunting PKGBUILD untuk paket " <> p <> "?"
+ Chinese -> "你希望编辑 " <> p <> " 的 PKGBUILD 文件吗?"
+ _ -> "Would you like to edit the PKGBUILD of " <> p <> "?"
+
+customizepkg_1 :: Language -> Doc AnsiStyle
+customizepkg_1 = let customizepkg = bt "customizepkg" in \case
+ Japanese -> customizepkg <+> "はインストールされていません。"
+ Polish -> customizepkg <+> "nie zainstalowany."
+ Croatian -> customizepkg <+> "nije instaliran."
+ German -> customizepkg <+> "ist nicht installiert."
+ Spanish -> customizepkg <+> "no está instalado."
+ Norwegian -> customizepkg <+> "er ikke installert."
+ Italian -> customizepkg <+> "non è installato."
+ Portuguese -> customizepkg <+> "não está instalado."
+ French -> customizepkg <+> "n'est pas installé."
+ Russian -> customizepkg <+> "не установлен."
+ Indonesia -> customizepkg <+> "tidak terinstal."
+ Chinese -> customizepkg <+> " 没有被安装。"
+ Swedish -> customizepkg <+> "är inte installerad"
+ _ -> customizepkg <+> "isn't installed."
+
+------------------------------
+-- Pkgbuild Security functions
+------------------------------
+security_1 :: PkgName -> Language -> Doc AnsiStyle
+security_1 (PkgName p) = \case
+ _ -> "The PKGBUILD of" <+> bt p <+> "was too complex to parse - it may be obfuscating malicious code."
+
+security_2 :: T.Text -> Language -> Doc AnsiStyle
+security_2 (bt -> t) = \case
+ _ -> t <+> "can be used to download arbitrary scripts that aren't tracked by this PKGBUILD."
+
+security_3 :: T.Text -> Language -> Doc AnsiStyle
+security_3 (bt -> t) = \case
+ _ -> t <+> "can be used to execute arbitrary code not tracked by this PKGBUILD."
+
+security_4 :: T.Text -> Language -> Doc AnsiStyle
+security_4 (bt -> t) = \case
+ _ -> t <+> "indicates that someone may be trying to gain root access to your machine."
+
+security_5 :: PkgName -> Language -> Doc AnsiStyle
+security_5 (PkgName p) = \case
+ _ -> "WARNING: The PKGBUILD of" <+> bt p <+> "contains blacklisted bash expressions."
+
+security_6 :: Language -> Doc AnsiStyle
+security_6 = \case
+ _ -> "Do you wish to quit the build process?"
+
+security_7 :: Language -> Doc AnsiStyle
+security_7 = \case
+ _ -> "Cancelled further processing to avoid potentially malicious bash code."
+
+security_8 :: T.Text -> Language -> Doc AnsiStyle
+security_8 (bt -> t) = \case
+ _ -> t <+> "is a bash command inlined in your PKGBUILD array fields."
+
+security_9 :: T.Text -> Language -> Doc AnsiStyle
+security_9 (bt -> t) = \case
+ _ -> t <+> "is a strange thing to have in your array fields. Is it safe?"
+
+security_10 :: T.Text -> Language -> Doc AnsiStyle
+security_10 (bt -> t) = \case
+ _ -> t <+> "implies that someone was trying to be clever with variables to hide malicious commands."
+
+-----------------------
+-- Aura/Utils functions
+-----------------------
+yesNoMessage :: Language -> Doc ann
+yesNoMessage = \case
+ Polish -> "[T/n]"
+ Croatian -> "[D/n]"
+ German -> "[J/n]"
+ Spanish -> "[S/n]"
+ Norwegian -> "[J/n]"
+ Italian -> "[S/n]"
+ Portuguese -> "[S/n]"
+ French -> "[O/n]"
+ Russian -> "[Д/н]"
+ _ -> "[Y/n]"
+
+yesPattern :: Language -> [T.Text]
+yesPattern = \case
+ Polish -> ["t", "tak"]
+ Croatian -> ["d", "da"]
+ German -> ["j", "ja"]
+ Spanish -> ["s", "si"]
+ Norwegian -> ["j", "ja"]
+ Italian -> ["s", "si"]
+ Portuguese -> ["s", "sim"]
+ French -> ["o", "oui"]
+ Russian -> ["д", "да"]
+ _ -> ["y", "yes"]
+
+----------------------
+-- Pluralization rules
+----------------------
+pluralRussian :: Integral n => a -> a -> a -> n -> a
+pluralRussian singular plural1 plural2 n | n % 10 == 1 && n % 100 /= 11 = singular
+ | n % 10 `elem` [2, 3, 4] = plural1
+ | otherwise = plural2
diff --git a/lib/Aura/Languages/Fields.hs b/lib/Aura/Languages/Fields.hs
new file mode 100644
index 0000000..dd5b70b
--- /dev/null
+++ b/lib/Aura/Languages/Fields.hs
@@ -0,0 +1,290 @@
+{-# LANGUAGE LambdaCase, OverloadedStrings #-}
+{-# OPTIONS_HADDOCK prune #-}
+{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}
+
+-- |
+-- Module : Aura.Languages.Fields
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- The various fields for @-Ai@ output.
+
+module Aura.Languages.Fields where
+
+import Aura.Types (Language(..))
+import qualified Data.Text as T
+
+---
+
+package :: Language -> T.Text
+package = \case
+ Japanese -> "パッケージ"
+ Polish -> "Pakiet"
+ Croatian -> "Paket"
+ Swedish -> "Paket"
+ German -> "Paket"
+ Spanish -> "Paquete"
+ Portuguese -> "Pacote"
+ French -> "Paquet"
+ Russian -> "Пакет"
+ Italian -> "Package"
+ Serbian -> "Пакет"
+ Norwegian -> "Pakke"
+ Indonesia -> "Paket"
+ _ -> "Package"
+
+firstInstall :: Language -> T.Text
+firstInstall = \case
+ Japanese -> "初インストール"
+ Polish -> "Pierwsza instalacja"
+ Croatian -> "Prva instalacija"
+ Swedish -> "Första installation"
+ German -> "Erste Installation"
+ Spanish -> "Primera instalación"
+ Portuguese -> "Primeira instalação"
+ French -> "Première installation"
+ Russian -> "Первая установка"
+ Italian -> "Prima installazione"
+ Serbian -> "Прва инсталација"
+ Norwegian -> "Første installasjon"
+ Indonesia -> "Versi sistem"
+ _ -> "First Install"
+
+upgrades :: Language -> T.Text
+upgrades = \case
+ Japanese -> "アップグレード回数"
+ Polish -> "Aktualizacje"
+ Croatian -> "Nadogradnje"
+ Swedish -> "Uppgraderingar"
+ German -> "Aktualisierungen"
+ Spanish -> "Actualizaciones"
+ Portuguese -> "Atualizações"
+ French -> "Mises à jours"
+ Russian -> "Обновления"
+ Italian -> "Upgrades"
+ Serbian -> "Ажурирања"
+ Norwegian -> "Oppgraderinger"
+ Indonesia -> "Tingkatkan"
+ _ -> "Upgrades"
+
+recentActions :: Language -> T.Text
+recentActions = \case
+ Japanese -> "近況"
+ Polish -> "Ostatnie akcje"
+ Croatian -> "Nedavne radnje"
+ Swedish -> "Nyliga händelser"
+ German -> "Letzte Aktionen"
+ Spanish -> "Acciones Recientes"
+ Portuguese -> "Ações Recentes"
+ French -> "Actions récentes"
+ Russian -> "Недавние действия"
+ Italian -> "Azioni recenti"
+ Serbian -> "Недавне радње"
+ Norwegian -> "Nylige hendelser"
+ Indonesia -> "Aksi sekarang"
+ _ -> "Recent Actions"
+
+repository :: Language -> T.Text
+repository = \case
+ Japanese -> "リポジトリ"
+ Polish -> "Repozytorium"
+ Croatian -> "Repozitorij"
+ Swedish -> "Repository"
+ German -> "Repository"
+ Spanish -> "Repositorio"
+ Portuguese -> "Repositório"
+ French -> "Dépôt"
+ Russian -> "Репозиторий"
+ Italian -> "Repository"
+ Serbian -> "Ризница"
+ Norwegian -> "Depot"
+ Indonesia -> "Lumbung"
+ _ -> "Repository"
+
+name :: Language -> T.Text
+name = \case
+ Japanese -> "名前"
+ Polish -> "Nazwa"
+ Croatian -> "Ime"
+ Swedish -> "Namn"
+ German -> "Name"
+ Spanish -> "Nombre"
+ Portuguese -> "Nome"
+ French -> "Nom"
+ Russian -> "Название"
+ Italian -> "Nome"
+ Serbian -> "Име"
+ Norwegian -> "Navn"
+ Indonesia -> "Nama"
+ _ -> "Name"
+
+version :: Language -> T.Text
+version = \case
+ Japanese -> "バージョン"
+ Polish -> "Wersja"
+ Croatian -> "Verzija"
+ Swedish -> "Version"
+ German -> "Version"
+ Spanish -> "Versión"
+ Portuguese -> "Versão"
+ French -> "Version"
+ Russian -> "Версия"
+ Italian -> "Versione"
+ Serbian -> "Верзија"
+ Norwegian -> "Versjon"
+ Indonesia -> "Versi"
+ _ -> "Version"
+
+aurStatus :: Language -> T.Text
+aurStatus = \case
+ Japanese -> "パッケージ状態"
+ Polish -> "Status w AUR"
+ Croatian -> "AUR Stanje"
+ German -> "AUR-Status"
+ Spanish -> "Estado en AUR"
+ Portuguese -> "Estado no AUR"
+ French -> "Statut de AUR"
+ Russian -> "Статус в AUR"
+ Italian -> "Stato in AUR"
+ Serbian -> "Статус у AUR-у"
+ Indonesia -> "Status AUR"
+ _ -> "AUR Status"
+
+-- NEEDS TRANSLATION
+maintainer :: Language -> T.Text
+maintainer = \case
+ Japanese -> "管理者"
+ Spanish -> "Mantenedor"
+ Portuguese -> "Mantenedor"
+ French -> "Mainteneur"
+ Russian -> "Ответственный"
+ Norwegian -> "Vedlikeholder"
+ Indonesia -> "Pemelihara"
+ _ -> "Maintainer"
+
+projectUrl :: Language -> T.Text
+projectUrl = \case
+ Japanese -> "プロジェクト"
+ Polish -> "URL Projektu"
+ Croatian -> "URL Projekta"
+ Swedish -> "Projekt URL"
+ German -> "Projekt-URL"
+ Spanish -> "URL del proyecto"
+ Portuguese -> "URL do projeto"
+ French -> "URL du projet"
+ Russian -> "URL проекта"
+ Italian -> "URL del progetto"
+ Serbian -> "Страница пројекта"
+ Norwegian -> "Prosjekt-URL"
+ Indonesia -> "URL Proyek"
+ _ -> "Project URL"
+
+aurUrl :: Language -> T.Text
+aurUrl = \case
+ Japanese -> "パッケージページ"
+ Polish -> "URL w AUR"
+ German -> "AUR-URL"
+ Spanish -> "URL de AUR"
+ Portuguese -> "URL no AUR"
+ French -> "URL AUR"
+ Russian -> "URL в AUR"
+ Italian -> "URL AUR"
+ Serbian -> "Страница у AUR-у"
+ Indonesia -> "URL AUR"
+ _ -> "AUR URL"
+
+license :: Language -> T.Text
+license = \case
+ Japanese -> "ライセンス"
+ Polish -> "Licencja"
+ Croatian -> "Licenca"
+ Swedish -> "Licens"
+ German -> "Lizenz"
+ Spanish -> "Licencia"
+ Portuguese -> "Licença"
+ French -> "Licence"
+ Russian -> "Лицензия"
+ Italian -> "Licenza"
+ Serbian -> "Лиценца"
+ Norwegian -> "Lisens"
+ Indonesia -> "Lisensi"
+ _ -> "License"
+
+dependsOn :: Language -> T.Text
+dependsOn = \case
+ Japanese -> "従属パッケージ"
+ Polish -> "Zależności"
+ Croatian -> "Zavisnosti"
+ German -> "Hängt ab von"
+ Spanish -> "Dependencias"
+ Portuguese -> "Dependências"
+ French -> "Dépends de"
+ Russian -> "Зависит от"
+ Italian -> "Dipende da"
+ Norwegian -> "Er avhengig av"
+ Indonesia -> "Bergantung pada"
+ _ -> "Depends On"
+
+buildDeps :: Language -> T.Text
+buildDeps = \case
+ Japanese -> "作成時従属パ"
+ German -> "Build-Abhängigkeiten"
+ Spanish -> "Dependencias de compilación"
+ Portuguese -> "Dependências de compilação"
+ French -> "Dépendances de compilation"
+ Russian -> "Зависимости сборки"
+ Indonesia -> "Dependensi bangun"
+ _ -> "Build Deps"
+
+votes :: Language -> T.Text
+votes = \case
+ Japanese -> "投票数"
+ Polish -> "Głosy"
+ Croatian -> "Glasovi"
+ Swedish -> "Röster"
+ German -> "Stimmen"
+ Spanish -> "Votos"
+ Portuguese -> "Votos"
+ French -> "Votes"
+ Russian -> "Голоса"
+ Italian -> "Voti"
+ Serbian -> "Гласови"
+ Norwegian -> "Stemmer"
+ Indonesia -> "Suara"
+ _ -> "Votes"
+
+popularity :: Language -> T.Text
+popularity = \case
+ Japanese -> "人気"
+ Portuguese -> "Popularidade"
+ _ -> "Popularity"
+
+description :: Language -> T.Text
+description = \case
+ Japanese -> "概要"
+ Polish -> "Opis"
+ Croatian -> "Opis"
+ Swedish -> "Beskrivning"
+ German -> "Beschreibung"
+ Spanish -> "Descripción"
+ Portuguese -> "Descrição"
+ French -> "Description"
+ Russian -> "Описание"
+ Italian -> "Descrizione"
+ Serbian -> "Опис"
+ Norwegian -> "Beskrivelse"
+ Indonesia -> "Deskripsi"
+ _ -> "Description"
+
+makeDeps :: Language -> T.Text
+makeDeps = \case
+ Polish -> "Zależności Make"
+ Croatian -> "Make Zavisnosti"
+ German -> "Make-Abhängigkeiten"
+ Spanish -> "Dependencias de compilación"
+ Portuguese -> "Dependências de compilação"
+ French -> "Dépendances de compilation"
+ Russian -> "Зависимости Make"
+ Indonesia -> "Dependensi bangun"
+ _ -> "Make Deps"
diff --git a/lib/Aura/Logo.hs b/lib/Aura/Logo.hs
new file mode 100644
index 0000000..8ef350c
--- /dev/null
+++ b/lib/Aura/Logo.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Logo
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Print an animated AURA version message.
+
+module Aura.Logo ( animateVersionMsg ) where
+
+import Aura.Colour (yellow, dtot)
+import Aura.Languages (translatorMsg)
+import Aura.Pacman (verMsgPad)
+import Aura.Settings
+import Aura.Utils
+import BasePrelude
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Text.Prettyprint.Doc
+import System.IO (stdout, hFlush)
+
+---
+
+-- | Show an animated version message, but only when the output target
+-- is a terminal.
+animateVersionMsg :: Settings -> T.Text -> [T.Text] -> IO ()
+animateVersionMsg ss auraVersion verMsg = do
+ when (isTerminal ss) $ do
+ hideCursor
+ traverse_ (T.putStrLn . padString (fromIntegral verMsgPad)) verMsg -- Version message
+ raiseCursorBy 7 -- Initial reraising of the cursor.
+ drawPills 3
+ traverse_ T.putStrLn $ renderPacmanHead ss 0 Open -- Initial rendering of head.
+ raiseCursorBy 4
+ takeABite ss 0 -- Initial bite animation.
+ traverse_ pillEating pillsAndWidths
+ clearGrid
+ T.putStrLn auraLogo
+ T.putStrLn $ "AURA Version " <> auraVersion
+ T.putStrLn " by Colin Woodbury\n"
+ traverse_ T.putStrLn . translatorMsg . langOf $ ss
+ when (isTerminal ss) showCursor
+ where pillEating (p, w) = clearGrid *> drawPills p *> takeABite ss w
+ pillsAndWidths = [(2, 5), (1, 10), (0, 15)]
+
+data MouthState = Open | Closed deriving (Eq)
+
+-- Taken from: figlet -f small "aura"
+auraLogo :: T.Text
+auraLogo = " __ _ _ _ _ _ __ _ \n" <>
+ "/ _` | || | '_/ _` |\n" <>
+ "\\__,_|\\_,_|_| \\__,_|"
+
+openMouth :: Settings -> [T.Text]
+openMouth ss = map f
+ [ " .--."
+ , "/ _.-'"
+ , "\\ '-."
+ , " '--'" ]
+ where f | shared ss (Colour Never) = id
+ | otherwise = dtot . yellow . pretty
+
+closedMouth :: Settings -> [T.Text]
+closedMouth ss = map f
+ [ " .--."
+ , "/ _..\\"
+ , "\\ ''/"
+ , " '--'" ]
+ where f | shared ss (Colour Never) = id
+ | otherwise = dtot . yellow . pretty
+
+pill :: [T.Text]
+pill = [ ""
+ , ".-."
+ , "'-'"
+ , "" ]
+
+takeABite :: Settings -> Int -> IO ()
+takeABite ss pad = drawMouth Closed *> drawMouth Open
+ where drawMouth mouth = do
+ traverse_ T.putStrLn $ renderPacmanHead ss pad mouth
+ raiseCursorBy 4
+ hFlush stdout
+ threadDelay 125000
+
+drawPills :: Int -> IO ()
+drawPills numOfPills = traverse_ T.putStrLn pills
+ where pills = renderPills numOfPills
+
+clearGrid :: IO ()
+clearGrid = T.putStr blankLines *> raiseCursorBy 4
+ where blankLines = fold . replicate 4 . padString 23 $ "\n"
+
+renderPill :: Int -> [T.Text]
+renderPill pad = padString pad <$> pill
+
+renderPills :: Int -> [T.Text]
+renderPills numOfPills = take numOfPills pillPostitions >>= render
+ where pillPostitions = [17, 12, 7]
+ render pos = renderPill pos <> [ cursorUpLineCode 5 ]
+
+renderPacmanHead :: Settings -> Int -> MouthState -> [T.Text]
+renderPacmanHead ss pad Open = map (padString pad) $ openMouth ss
+renderPacmanHead ss pad Closed = map (padString pad) $ closedMouth ss
+
+padString :: Int -> T.Text -> T.Text
+padString pad cs = T.justifyRight (pad + T.length cs) ' ' cs
diff --git a/lib/Aura/MakePkg.hs b/lib/Aura/MakePkg.hs
new file mode 100644
index 0000000..03d09eb
--- /dev/null
+++ b/lib/Aura/MakePkg.hs
@@ -0,0 +1,81 @@
+{-# LANGUAGE OverloadedStrings, TupleSections #-}
+
+-- |
+-- Module : Aura.State
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Interface to `makepkg`.
+
+module Aura.MakePkg
+ ( makepkg
+ , makepkgSource
+ , makepkgConfFile
+ ) where
+
+import Aura.Languages
+import Aura.Settings
+import Aura.Types
+import Aura.Utils (strictText, optionalPrompt)
+import BasePrelude
+import Control.Error.Util (note)
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Data.List.NonEmpty as NEL
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Lens.Micro ((^.), _2)
+import System.Path (Path, Absolute, fromAbsoluteFilePath, (</>), toFilePath)
+import System.Path.IO (getCurrentDirectory, getDirectoryContents)
+import System.Process.Typed
+
+---
+
+-- | The default location of the makepkg configuration: \/etc\/makepkg.conf
+makepkgConfFile :: Path Absolute
+makepkgConfFile = fromAbsoluteFilePath "/etc/makepkg.conf"
+
+makepkgCmd :: Path Absolute
+makepkgCmd = fromAbsoluteFilePath "/usr/bin/makepkg"
+
+-- | Given the current user name, build the package of whatever
+-- directory we're in.
+makepkg :: Settings -> User -> IO (Either Failure (NonEmptySet (Path Absolute)))
+makepkg ss usr = make ss usr (proc cmd $ opts <> colour) >>= g
+ where (cmd, opts) = runStyle usr . foldMap asFlag . makepkgFlagsOf $ buildConfigOf ss
+ g (ExitSuccess, _, fs) = pure . note (Failure buildFail_9) . fmap NES.fromNonEmpty $ NEL.nonEmpty fs
+ g (ExitFailure _, se, _) = do
+ unless (switch ss DontSuppressMakepkg) $ do
+ showError <- optionalPrompt ss buildFail_11
+ when showError $ BL.putStrLn se
+ pure . Left $ Failure buildFail_8
+ colour | shared ss (Colour Never) = ["--nocolor"]
+ | shared ss (Colour Always) = []
+ | isTerminal ss = []
+ | otherwise = ["--nocolor"]
+
+-- | Actually build the package, guarding on exceptions.
+-- Yields the filepaths of the built package tarballs.
+make :: MonadIO m => Settings -> User -> ProcessConfig stdin stdout stderr -> m (ExitCode, BL.ByteString, [Path Absolute])
+make ss (User usr) pc = do
+ (ec, se) <- runIt ss pc
+ res <- readProcess $ proc "sudo" ["-u", T.unpack usr, toFilePath makepkgCmd, "--packagelist"]
+ let fs = map (fromAbsoluteFilePath . T.unpack . strictText) . BL.lines $ res ^. _2
+ pure (ec, se, fs)
+
+runIt :: MonadIO m => Settings -> ProcessConfig stdin stdout stderr -> m (ExitCode, BL.ByteString)
+runIt ss pc | switch ss DontSuppressMakepkg = (,mempty) <$> runProcess pc
+ | otherwise = (\(ec, _, se) -> (ec, se)) <$> readProcess pc
+
+-- | Make a source package. See `man makepkg` and grep for `--allsource`.
+makepkgSource :: User -> IO [Path Absolute]
+makepkgSource usr = do
+ runProcess $ proc cmd opts
+ pwd <- getCurrentDirectory
+ filter (T.isSuffixOf ".src.tar.gz" . T.pack . toFilePath) . map (pwd </>) <$> getDirectoryContents pwd
+ where (cmd, opts) = runStyle usr ["--allsource"]
+
+-- | As of makepkg v4.2, building with `--asroot` is no longer allowed.
+runStyle :: User -> [String] -> (FilePath, [String])
+runStyle (User usr) opts = ("sudo", ["-u", T.unpack usr, toFilePath makepkgCmd] <> opts)
diff --git a/lib/Aura/Packages/AUR.hs b/lib/Aura/Packages/AUR.hs
new file mode 100644
index 0000000..341fe09
--- /dev/null
+++ b/lib/Aura/Packages/AUR.hs
@@ -0,0 +1,135 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts, TypeApplications, MonoLocalBinds, DataKinds #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+-- |
+-- Module : Aura.Packages.AUR
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Module for connecting to the AUR servers, downloading PKGBUILDs and package sources.
+
+module Aura.Packages.AUR
+ ( -- * Batch Querying
+ aurLookup
+ , aurRepo
+ -- * Single Querying
+ , aurInfo
+ , aurSearch
+ -- * Source Retrieval
+ , clone
+ , pkgUrl
+ ) where
+
+import Aura.Core
+import Aura.Languages
+import Aura.Pkgbuild.Fetch
+import Aura.Settings
+import Aura.Types
+import BasePrelude hiding (head)
+import Control.Concurrent.STM.TQueue
+import Control.Concurrent.Throttled (throttle)
+import Control.Error.Util (hush)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Maybe
+import Data.Generics.Product (field)
+import Data.List.NonEmpty (head)
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Set.NonEmpty as NES
+import qualified Data.Text as T
+import Data.Versions (versioning)
+import Lens.Micro ((^.), (^..), each, to)
+import Linux.Arch.Aur
+import Network.HTTP.Client (Manager)
+import System.Path
+import System.Path.IO (getCurrentDirectory)
+import System.Process.Typed
+
+---
+
+-- | Attempt to retrieve info about a given `S.Set` of packages from the AUR.
+aurLookup :: Manager -> NonEmptySet PkgName -> IO (Maybe (S.Set PkgName, S.Set Buildable))
+aurLookup m names = runMaybeT $ MaybeT (info m (foldMap (\(PkgName pn) -> [pn]) names)) >>= \infos -> do
+ badsgoods <- lift $ throttle (\_ a -> buildable m a) infos >>= atomically . flushTQueue
+ let (bads, goods) = partitionEithers badsgoods
+ goodNames = S.fromList $ goods ^.. each . field @"name"
+ pure (S.fromList bads <> NES.toSet names S.\\ goodNames, S.fromList goods)
+
+-- | Yield fully realized `Package`s from the AUR.
+aurRepo :: Repository
+aurRepo = Repository $ \ss ps -> runMaybeT $ do
+ (bads, goods) <- MaybeT $ aurLookup (managerOf ss) ps
+ pkgs <- lift . traverse (packageBuildable ss) $ toList goods
+ pure (bads, S.fromList pkgs)
+
+buildable :: Manager -> AurInfo -> IO (Either PkgName Buildable)
+buildable m ai = do
+ let !bse = PkgName $ pkgBaseOf ai
+ mver = hush . versioning $ aurVersionOf ai
+ mpb <- getPkgbuild m bse -- Using the package base ensures split packages work correctly.
+ case (,) <$> mpb <*> mver of
+ Nothing -> pure . Left . PkgName $ aurNameOf ai
+ Just (pb, ver) -> pure $ Right Buildable
+ { name = PkgName $ aurNameOf ai
+ , version = ver
+ , base = bse
+ , provides = list (Provides $ aurNameOf ai) (Provides . head) $ providesOf ai
+ -- TODO This is a potentially naughty mapMaybe, since deps that fail to parse
+ -- will be silently dropped. Unfortunately there isn't much to be done - `aurLookup`
+ -- and `aurRepo` which call this function only report existence errors
+ -- (i.e. "this package couldn't be found at all").
+ , deps = mapMaybe parseDep $ dependsOf ai ++ makeDepsOf ai
+ , pkgbuild = pb
+ , isExplicit = False }
+
+----------------
+-- AUR PKGBUILDS
+----------------
+aurLink :: Path Unrooted
+aurLink = fromUnrootedFilePath "https://aur.archlinux.org"
+
+-- | A package's home URL on the AUR.
+pkgUrl :: PkgName -> T.Text
+pkgUrl (PkgName pkg) = T.pack . toUnrootedFilePath $ aurLink </> fromUnrootedFilePath "packages" </> fromUnrootedFilePath (T.unpack pkg)
+
+-------------------
+-- SOURCES FROM GIT
+-------------------
+-- | Attempt to clone a package source from the AUR.
+clone :: Buildable -> IO (Maybe (Path Absolute))
+clone b = do
+ ec <- runProcess . setStderr closed . setStdout closed $ proc "git" [ "clone", "--depth", "1", toUnrootedFilePath url ]
+ case ec of
+ (ExitFailure _) -> pure Nothing
+ ExitSuccess -> do
+ pwd <- getCurrentDirectory
+ pure . Just $ pwd </> (b ^. field @"base" . field @"name" . to (fromUnrootedFilePath . T.unpack))
+ where url = aurLink </> (b ^. field @"base" . field @"name" . to (fromUnrootedFilePath . T.unpack)) <.> FileExt "git"
+
+------------
+-- RPC CALLS
+------------
+sortAurInfo :: Maybe BuildSwitch -> [AurInfo] -> [AurInfo]
+sortAurInfo bs ai = sortBy compare' ai
+ where compare' = case bs of
+ Just SortAlphabetically -> compare `on` aurNameOf
+ _ -> \x y -> compare (aurVotesOf y) (aurVotesOf x)
+
+-- | Frontend to the `aur` library. For @-As@.
+aurSearch :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => T.Text -> Eff r [AurInfo]
+aurSearch regex = do
+ ss <- ask
+ res <- liftMaybeM (Failure connectionFailure_1) $ search (managerOf ss) regex
+ pure $ sortAurInfo (bool Nothing (Just SortAlphabetically) $ switch ss SortAlphabetically) res
+
+-- | Frontend to the `aur` library. For @-Ai@.
+aurInfo :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmpty PkgName -> Eff r [AurInfo]
+aurInfo pkgs = do
+ m <- asks managerOf
+ res <- liftMaybeM (Failure connectionFailure_1) . info m . map (^. field @"name") $ toList pkgs
+ pure $ sortAurInfo (Just SortAlphabetically) res
diff --git a/lib/Aura/Packages/Repository.hs b/lib/Aura/Packages/Repository.hs
new file mode 100644
index 0000000..f87b89b
--- /dev/null
+++ b/lib/Aura/Packages/Repository.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE OverloadedStrings, TupleSections, DuplicateRecordFields #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Packages.Repository
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle the testing and dependency solving of official repository packages.
+
+module Aura.Packages.Repository
+ ( pacmanRepo
+ , extractVersion
+ ) where
+
+import Aura.Core
+import Aura.Languages (provides_1)
+import Aura.Pacman (pacmanOutput)
+import Aura.Settings (Settings, CommonSwitch(..), shared)
+import Aura.Types
+import Aura.Utils (getSelection, strictText)
+import BasePrelude hiding (try)
+import Control.Compactable (traverseEither)
+import Control.Concurrent.STM.TQueue
+import Control.Concurrent.Throttled (throttle)
+import Control.Error.Util (hush, note)
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import qualified Data.Set as S
+import qualified Data.Text as T
+import Data.Versions
+import Lens.Micro ((^.))
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+---
+
+-- | Repository package source.
+-- We expect no matches to be found when the package is actually an AUR package.
+pacmanRepo :: Repository
+pacmanRepo = Repository $ \ss names -> do
+ bgs <- throttle (const $ resolveName ss) names >>= atomically . flushTQueue
+ let (bads, goods) = partitionEithers bgs
+ (bads', goods') <- traverseEither f goods
+ pure $ Just (S.fromList $ bads <> bads', S.fromList goods')
+ where f (r, p) = fmap (FromRepo . packageRepo r p) <$> mostRecentVersion r
+
+packageRepo :: PkgName -> Provides -> Versioning -> Prebuilt
+packageRepo pn pro ver = Prebuilt { name = pn
+ , version = ver
+ , base = pn
+ , provides = pro }
+
+-- TODO Bind to libalpm /just/ for the @-Ssq@ functionality. These shell
+-- calls are one of the remaining bottlenecks.
+-- | If given a virtual package, try to find a real package to install.
+resolveName :: Settings -> PkgName -> IO (Either PkgName (PkgName, Provides))
+resolveName ss pn = do
+ provs <- map (PkgName . strictText) . BL.lines <$> pacmanOutput ["-Ssq", "^" <> T.unpack (pn ^. field @"name") <> "$"]
+ case provs of
+ [] -> pure $ Left pn
+ _ -> Right . (, Provides $ pn ^. field @"name") <$> chooseProvider ss pn provs
+
+-- | Choose a providing package, favoring installed packages.
+-- If `--noconfirm` is provided, it will try to automatically select the provider
+-- with the same name as the dependency. If that doesn't exist, it will select
+-- the first available provider.
+chooseProvider :: Settings -> PkgName -> [PkgName] -> IO PkgName
+chooseProvider _ pn [] = pure pn
+chooseProvider _ _ [p] = pure p
+chooseProvider ss pn ps@(a:as) =
+ throttle (const isInstalled) ps >>= atomically . flushTQueue >>= maybe f pure . listToMaybe . catMaybes
+ where f | shared ss NoConfirm = pure . bool a pn $ pn `elem` ps
+ | otherwise = warn ss (provides_1 pn) >> getSelection (^. field @"name") (a :| as)
+
+-- | The most recent version of a package, if it exists in the respositories.
+mostRecentVersion :: PkgName -> IO (Either PkgName Versioning)
+mostRecentVersion p@(PkgName s) = note p . extractVersion . strictText <$> pacmanOutput ["-Si", T.unpack s]
+
+-- | Parses the version number of a package from the result of a
+-- @pacman -Si@ call.
+extractVersion :: T.Text -> Maybe Versioning
+extractVersion = hush . parse p "extractVersion"
+ where p = do
+ takeWhile1P Nothing (/= '\n') *> newline
+ takeWhile1P Nothing (/= '\n') *> newline
+ string "Version" *> space1 *> char ':' *> space1 *> v
+ v = choice [ try (fmap Ideal semver' <* string "Description")
+ , try (fmap General version' <* string "Description")
+ , fmap Complex mess' <* string "Description" ]
diff --git a/lib/Aura/Pacman.hs b/lib/Aura/Pacman.hs
new file mode 100644
index 0000000..d466eb7
--- /dev/null
+++ b/lib/Aura/Pacman.hs
@@ -0,0 +1,145 @@
+{-# LANGUAGE MultiWayIf, OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts, MonoLocalBinds #-}
+
+-- |
+-- Module : Aura.Pacman
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- An interface to @pacman@.
+-- Takes any pacman arguments and applies it to pacman through the shell.
+
+module Aura.Pacman
+ ( -- * Calling Pacman
+ pacman
+ , pacmanOutput, pacmanSuccess
+ -- * Paths
+ , lockFile
+ , pacmanConfFile
+ , defaultLogFile
+ , getCachePath
+ , getLogFilePath
+ -- * Pacman Config
+ , Config(..), config
+ , getPacmanConf
+ , getIgnoredPkgs, getIgnoredGroups
+ , groupPackages
+ -- * Misc.
+ , getVersionInfo
+ , verMsgPad
+ ) where
+
+import Aura.Languages
+import Aura.Types
+import Aura.Utils (strictText)
+import BasePrelude hiding (some, try)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import Data.Set.NonEmpty (NonEmptySet)
+import qualified Data.Text as T
+import Data.Text.Encoding (decodeUtf8With)
+import Data.Text.Encoding.Error (lenientDecode)
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import Lens.Micro
+import Lens.Micro.GHC ()
+import System.Path (Path, Absolute, fromAbsoluteFilePath, toFilePath)
+import System.Process.Typed
+import Text.Megaparsec
+import Text.Megaparsec.Char
+import qualified Text.Megaparsec.Char.Lexer as L
+
+---
+
+-- | The (meaningful) contents of the Pacman config file.
+newtype Config = Config (M.Map T.Text [T.Text]) deriving (Show)
+
+-- | Parse a `Config`, the pacman configuration file.
+config :: Parsec Void T.Text Config
+config = Config . M.fromList . rights <$> (garbage *> some (fmap Right (try pair) <|> fmap Left single) <* eof)
+
+single :: Parsec Void T.Text ()
+single = L.lexeme garbage . void $ manyTill letterChar newline
+
+pair :: Parsec Void T.Text (T.Text, [T.Text])
+pair = L.lexeme garbage $ do
+ n <- takeWhile1P Nothing (/= ' ')
+ space
+ char '='
+ space
+ rest <- T.words <$> takeWhile1P Nothing (/= '\n')
+ pure (n, rest)
+
+-- | Using `[]` as block comment markers is a trick to skip conf file "section" lines.
+garbage :: Parsec Void T.Text ()
+garbage = L.space space1 (L.skipLineComment "#") (L.skipBlockComment "[" "]")
+
+-- | Default location of the pacman config file: \/etc\/pacman.conf
+pacmanConfFile :: Path Absolute
+pacmanConfFile = fromAbsoluteFilePath "/etc/pacman.conf"
+
+-- | Default location of the pacman log flie: \/var\/log\/pacman.log
+defaultLogFile :: Path Absolute
+defaultLogFile = fromAbsoluteFilePath "/var/log/pacman.log"
+
+-- | Default location of the pacman database lock file: \/var\/lib\/pacman\/db.lck
+lockFile :: Path Absolute
+lockFile = fromAbsoluteFilePath "/var/lib/pacman/db.lck"
+
+-- | Given a filepath to the pacman config, try to parse its contents.
+getPacmanConf :: Path Absolute -> IO (Either Failure Config)
+getPacmanConf fp = do
+ file <- decodeUtf8With lenientDecode <$> BS.readFile (toFilePath fp)
+ pure . first (const (Failure confParsing_1)) $ parse config "pacman config" file
+
+-- | Fetches the @IgnorePkg@ entry from the config, if it's there.
+getIgnoredPkgs :: Config -> S.Set PkgName
+getIgnoredPkgs (Config c) = maybe S.empty (S.fromList . map PkgName) $ M.lookup "IgnorePkg" c
+
+-- | Fetches the @IgnoreGroup@ entry from the config, if it's there.
+getIgnoredGroups :: Config -> S.Set PkgGroup
+getIgnoredGroups (Config c) = maybe S.empty (S.fromList . map PkgGroup) $ M.lookup "IgnoreGroup" c
+
+-- | Given a `Set` of package groups, yield all the packages they contain.
+groupPackages :: NonEmptySet PkgGroup -> IO (S.Set PkgName)
+groupPackages igs | null igs = pure S.empty
+ | otherwise = fmap f . pacmanOutput $ "-Qg" : asFlag igs
+ where f = S.fromList . map (PkgName . strictText . (!! 1) . BL.words) . BL.lines
+
+-- | Fetches the @CacheDir@ entry from the config, if it's there.
+getCachePath :: Config -> Maybe (Path Absolute)
+getCachePath (Config c) = c ^? at "CacheDir" . _Just . _head . to (fromAbsoluteFilePath . T.unpack)
+
+-- | Fetches the @LogFile@ entry from the config, if it's there.
+getLogFilePath :: Config -> Maybe (Path Absolute)
+getLogFilePath (Config c) = c ^? at "LogFile" . _Just . _head . to (fromAbsoluteFilePath . T.unpack)
+
+----------
+-- ACTIONS
+----------
+-- | Run a pacman action that may fail. Will never throw an IO exception.
+pacman :: [String] -> IO (Either Failure ())
+pacman args = do
+ ec <- runProcess $ proc "pacman" args
+ pure . bool (Left $ Failure pacmanFailure_1) (Right ()) $ ec == ExitSuccess
+
+-- | Run some `pacman` process, but only care about whether it succeeded.
+pacmanSuccess :: [String] -> IO Bool
+pacmanSuccess = fmap (== ExitSuccess) . runProcess . setStderr closed . setStdout closed . proc "pacman"
+
+-- | Runs pacman silently and returns only the stdout.
+pacmanOutput :: [String] -> IO BL.ByteString
+pacmanOutput = fmap (^. _2) . readProcess . proc "pacman"
+
+-- | Yields the lines given by `pacman -V` with the pacman image stripped.
+getVersionInfo :: IO [T.Text]
+getVersionInfo = do
+ out <- pacmanOutput ["-V"]
+ pure . map (TL.toStrict . TL.drop verMsgPad . TL.decodeUtf8With lenientDecode) $ BL.lines out
+
+-- | The amount of whitespace before text in the lines given by `pacman -V`
+verMsgPad :: Int64
+verMsgPad = 23
diff --git a/lib/Aura/Pkgbuild/Base.hs b/lib/Aura/Pkgbuild/Base.hs
new file mode 100644
index 0000000..a89962d
--- /dev/null
+++ b/lib/Aura/Pkgbuild/Base.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Pkgbuild.Base
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+
+module Aura.Pkgbuild.Base
+ ( -- * Paths
+ pkgbuildCache
+ , pkgbuildPath
+ ) where
+
+import Aura.Types
+import qualified Data.Text as T
+import System.Path (Path, Absolute, FileExt(..), fromAbsoluteFilePath, fromUnrootedFilePath, (</>), (<.>))
+
+---
+
+-- | The default location: \/var\/cache\/aura\/pkgbuilds\/
+pkgbuildCache :: Path Absolute
+pkgbuildCache = fromAbsoluteFilePath "/var/cache/aura/pkgbuilds/"
+
+-- | The expected path to a stored PKGBUILD, given some package name.
+pkgbuildPath :: PkgName -> Path Absolute
+pkgbuildPath (PkgName p) = pkgbuildCache </> fromUnrootedFilePath (T.unpack p) <.> FileExt "pb"
diff --git a/lib/Aura/Pkgbuild/Editing.hs b/lib/Aura/Pkgbuild/Editing.hs
new file mode 100644
index 0000000..f884ef0
--- /dev/null
+++ b/lib/Aura/Pkgbuild/Editing.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Pkgbuild.Base
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- For handling the editing of PKGBUILDs.
+
+module Aura.Pkgbuild.Editing ( hotEdit ) where
+
+import Aura.Languages
+import Aura.Settings
+import Aura.Types
+import Aura.Utils
+import BasePrelude
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import Lens.Micro ((^.), (.~))
+import System.Directory (setCurrentDirectory)
+import System.Path (toFilePath)
+import System.Path.IO (getCurrentDirectory, getTemporaryDirectory)
+import System.Process.Typed (runProcess, proc)
+
+---
+
+-- | Write a PKGBUILD to the filesystem temporarily, run some effectful
+-- function over it, then read it back in before proceeding with
+-- package building.
+edit :: (FilePath -> IO a) -> Buildable -> IO Buildable
+edit f p = do
+ BL.writeFile filename $ p ^. field @"pkgbuild" . field @"pkgbuild"
+ void $ f filename
+ newPB <- BL.readFile filename
+ pure (p & field @"pkgbuild" .~ Pkgbuild newPB)
+ where filename = "PKGBUILD"
+
+-- | Allow the user to edit the PKGBUILD if they asked to do so.
+hotEdit :: Settings -> Buildable -> IO Buildable
+hotEdit ss b
+ | not $ switch ss HotEdit = pure b
+ | otherwise = do
+ ans <- liftIO $ optionalPrompt ss (hotEdit_1 $ b ^. field @"name")
+ bool (pure b) f ans
+ where f = do
+ here <- getCurrentDirectory
+ tmp <- getTemporaryDirectory
+ setCurrentDirectory $ toFilePath tmp
+ b' <- edit (runProcess . proc (editorOf ss) . (:[])) b
+ setCurrentDirectory $ toFilePath here
+ pure b'
diff --git a/lib/Aura/Pkgbuild/Fetch.hs b/lib/Aura/Pkgbuild/Fetch.hs
new file mode 100644
index 0000000..fad7c05
--- /dev/null
+++ b/lib/Aura/Pkgbuild/Fetch.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.State
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Query the AUR for a package's PKGBUILD.
+
+module Aura.Pkgbuild.Fetch
+ ( getPkgbuild
+ , pkgbuildUrl
+ ) where
+
+import Aura.Types (PkgName(..), Pkgbuild(..))
+import Aura.Utils (urlContents)
+import BasePrelude
+import Control.Exception (SomeException, catch)
+import Control.Monad.Trans (MonadIO, liftIO)
+import Data.Generics.Product (field)
+import qualified Data.Text as T
+import Lens.Micro ((^.))
+import Network.HTTP.Client (Manager)
+import Network.URI (escapeURIString, isUnescapedInURIComponent)
+import System.FilePath ((</>))
+
+---
+
+type E = SomeException
+
+baseUrl :: String
+baseUrl = "https://aur.archlinux.org/"
+
+-- | The location of a given package's PKGBUILD on the AUR servers.
+pkgbuildUrl :: String -> String
+pkgbuildUrl p = baseUrl </> "cgit/aur.git/plain/PKGBUILD?h="
+ ++ escapeURIString isUnescapedInURIComponent p
+
+-- | The PKGBUILD of a given package, retrieved from the AUR servers.
+getPkgbuild :: MonadIO m => Manager -> PkgName -> m (Maybe Pkgbuild)
+getPkgbuild m p = e $ do
+ t <- urlContents m . pkgbuildUrl . T.unpack $ p ^. field @"name"
+ pure $ fmap Pkgbuild t
+ where e f = liftIO $ f `catch` (\(_ :: E) -> return Nothing)
diff --git a/lib/Aura/Pkgbuild/Records.hs b/lib/Aura/Pkgbuild/Records.hs
new file mode 100644
index 0000000..f475009
--- /dev/null
+++ b/lib/Aura/Pkgbuild/Records.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE TypeApplications, DataKinds #-}
+
+-- |
+-- Module : Aura.Pkgbuild.Records
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle the storing of PKGBUILDs.
+
+module Aura.Pkgbuild.Records
+ ( hasPkgbuildStored
+ , storePkgbuilds
+ ) where
+
+import Aura.Pkgbuild.Base
+import Aura.Types
+import BasePrelude
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import Data.Set.NonEmpty (NonEmptySet)
+import Lens.Micro ((^.))
+import System.Path (toFilePath)
+import System.Path.IO (doesFileExist, createDirectoryIfMissing)
+
+---
+
+-- | Does a given package has a PKGBUILD stored?
+-- This is `True` when a package has been built successfully once before.
+hasPkgbuildStored :: PkgName -> IO Bool
+hasPkgbuildStored = doesFileExist . pkgbuildPath
+
+-- | Write the PKGBUILDs of some `Buildable`s to disk.
+storePkgbuilds :: NonEmptySet Buildable -> IO ()
+storePkgbuilds bs = do
+ createDirectoryIfMissing True pkgbuildCache
+ traverse_ (\p -> writePkgbuild (p ^. field @"name") (p ^. field @"pkgbuild")) bs
+
+writePkgbuild :: PkgName -> Pkgbuild -> IO ()
+writePkgbuild pn (Pkgbuild pb) = BL.writeFile (toFilePath $ pkgbuildPath pn) pb
diff --git a/lib/Aura/Pkgbuild/Security.hs b/lib/Aura/Pkgbuild/Security.hs
new file mode 100644
index 0000000..24aedac
--- /dev/null
+++ b/lib/Aura/Pkgbuild/Security.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE OverloadedStrings, TupleSections, TypeApplications, DeriveGeneric #-}
+
+-- |
+-- Module : Aura.Pkgbuild.Security
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Analyse PKGBUILDs for potentially malicious bash code.
+
+module Aura.Pkgbuild.Security
+ ( BannedTerm(..), BanCategory(..)
+ , parsedPB, bannedTerms
+ , reportExploit
+ ) where
+
+import Aura.Languages
+import Aura.Types (Pkgbuild(..), Language)
+import Aura.Utils (strictText)
+import BasePrelude hiding (Last, Word)
+import Control.Error.Util (hush)
+import Data.Generics.Product
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import Data.Text.Prettyprint.Doc (Doc)
+import Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle)
+import Language.Bash.Parse (parse)
+import Language.Bash.Syntax
+import Language.Bash.Word (Word, unquote, Span(..))
+import Lens.Micro
+
+---
+
+-- | A bash term which should never appear in a PKGBUILD. If one does, it's
+-- either a sign of maintainer negligence or malicious behaviour.
+data BannedTerm = BannedTerm T.Text BanCategory deriving (Eq, Ord, Show, Generic)
+
+-- | The reason why the bash term is black-listed.
+data BanCategory = Downloading
+ | ScriptRunning
+ | Permissions
+ | InlinedBash
+ | StrangeBashism
+ | CleverRedirect
+ deriving (Eq, Ord, Show)
+
+blacklist :: M.Map T.Text BannedTerm
+blacklist = M.fromList $ downloading <> running <> permissions
+ where downloading = map (\t -> (t, BannedTerm t Downloading)) ["curl", "wget", "rsync", "scp"]
+ running = map (\t -> (t, BannedTerm t ScriptRunning)) ["sh", "bash", "eval", "zsh", "fish"]
+ permissions = map (\t -> (t, BannedTerm t Permissions)) ["sudo", "ssh"]
+
+-- | Attempt to parse a PKGBUILD. Should succeed for all reasonable PKGBUILDs.
+parsedPB :: Pkgbuild -> Maybe List
+parsedPB (Pkgbuild pb) = hush . parse "PKGBUILD" . T.unpack $ strictText pb -- TODO wasteful conversion!
+
+-- | Discover any banned terms lurking in a parsed PKGBUILD, paired with
+-- the surrounding context lines.
+bannedTerms :: List -> [(ShellCommand, BannedTerm)]
+bannedTerms = simpleCommands >=> bannedCommand
+
+banned :: Word -> Maybe BannedTerm
+banned w = M.lookup (T.pack $ unquote w) blacklist
+
+-- | Extract all `SimpleCommand`s from a parsed bash AST.
+simpleCommands :: List -> [ShellCommand]
+simpleCommands l = l ^.. types @ShellCommand . to p . each
+ where p sc@(SimpleCommand _ _) = [sc]
+ p sc = sc ^.. types @List . to simpleCommands . each
+
+bannedCommand :: ShellCommand -> [(ShellCommand, BannedTerm)]
+bannedCommand s@(SimpleCommand [] (g:c:_))
+ | g == [Char 'g', Char 'i', Char 't'] &&
+ c == [Char 'c', Char 'l', Char 'o', Char 'n', Char 'e'] = [(s, BannedTerm "git" Downloading)]
+bannedCommand s@(SimpleCommand [] (c:_)) = maybeToList $ (s,) <$> banned c
+bannedCommand s@(SimpleCommand as _) = as ^.. each . typed @RValue . to r . each
+ where
+ r rv@(RValue w) = maybeToList ((s,) <$> (banned w & _Just . typed @BanCategory .~ CleverRedirect)) <> q rv
+ r rv = q rv
+
+ q :: RValue -> [(ShellCommand, BannedTerm)]
+ q rv = rv ^.. types @Word . each . to p . each . to (s,)
+
+ p (CommandSubst str) = maybeToList (hush $ parse "CommandSubst" str) >>= simpleCommands >>= map snd . bannedCommand
+ p (ArithSubst str) = [BannedTerm (T.pack str) StrangeBashism]
+ p (ProcessSubst _ str) = [BannedTerm (T.pack str) StrangeBashism]
+ p sp = sp ^.. types @Word . each . to p . each
+bannedCommand _ = []
+
+------------
+-- REPORTING
+------------
+
+-- | Dispatch different error messages depending on the category of a `BannedTerm`.
+reportExploit :: BannedTerm -> (Language -> Doc AnsiStyle)
+reportExploit (BannedTerm t bc) = case bc of
+ Downloading -> security_2 t
+ ScriptRunning -> security_3 t
+ Permissions -> security_4 t
+ InlinedBash -> security_8 t
+ StrangeBashism -> security_9 t
+ CleverRedirect -> security_10 t
diff --git a/lib/Aura/Settings.hs b/lib/Aura/Settings.hs
new file mode 100644
index 0000000..ac00e3d
--- /dev/null
+++ b/lib/Aura/Settings.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+-- |
+-- Module : Aura.Settings
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Definition of the runtime environment.
+
+module Aura.Settings
+ ( Settings(..)
+ -- * Aura Configuration
+ , BuildConfig(..), BuildSwitch(..)
+ , switch
+ , Truncation(..)
+ , defaultBuildDir
+ -- * Pacman Interop
+ , CommonConfig(..), CommonSwitch(..)
+ , ColourMode(..)
+ , shared
+ -- * Makepkg Interop
+ , Makepkg(..)
+ ) where
+
+import Aura.Types
+import BasePrelude
+import qualified Data.Set as S
+import Network.HTTP.Client (Manager)
+import System.Path (Path, Absolute, toFilePath, fromAbsoluteFilePath)
+
+---
+
+-- | How @-As@ should truncate its results.
+data Truncation = None | Head Word | Tail Word deriving (Eq, Show)
+
+-- | CLI flags that will be passed down to @makepkg@ when building packages.
+data Makepkg = IgnoreArch | AllSource | SkipInteg deriving (Eq, Ord, Show)
+
+instance Flagable Makepkg where
+ asFlag IgnoreArch = ["--ignorearch"]
+ asFlag AllSource = ["--allsource"]
+ asFlag SkipInteg = ["--skipinteg"]
+
+-- | Flags that are common to both Aura and Pacman.
+-- Aura will react to them, but also pass them through to `pacman`
+-- calls if necessary.
+data CommonConfig = CommonConfig { cachePathOf :: !(Either (Path Absolute) (Path Absolute))
+ , configPathOf :: !(Either (Path Absolute) (Path Absolute))
+ , logPathOf :: !(Either (Path Absolute) (Path Absolute))
+ , commonSwitchesOf :: !(S.Set CommonSwitch) } deriving (Show, Generic)
+
+instance Flagable CommonConfig where
+ asFlag (CommonConfig cap cop lfp cs) =
+ either (const []) (\p -> ["--cachedir", toFilePath p]) cap
+ ++ either (const []) (\p -> ["--config", toFilePath p]) cop
+ ++ either (const []) (\p -> ["--logfile", toFilePath p]) lfp
+ ++ asFlag cs
+
+-- | Yes/No-style switches that are common to both Aura and Pacman.
+-- Aura acts on them first, then passes them down to @pacman@ if necessary.
+data CommonSwitch = NoConfirm | NeededOnly | Debug | Colour ColourMode deriving (Eq, Ord, Show)
+
+instance Flagable CommonSwitch where
+ asFlag NoConfirm = ["--noconfirm"]
+ asFlag NeededOnly = ["--needed"]
+ asFlag Debug = ["--debug"]
+ asFlag (Colour m) = "--color" : asFlag m
+
+-- | Matches Pacman's colour options. `Auto` will ensure that text will only be coloured
+-- when the output target is a terminal.
+data ColourMode = Never | Always | Auto deriving (Eq, Ord, Show)
+
+instance Flagable ColourMode where
+ asFlag Never = ["never"]
+ asFlag Always = ["always"]
+ asFlag Auto = ["auto"]
+
+-- | Settings unique to the AUR package building process.
+data BuildConfig = BuildConfig { makepkgFlagsOf :: !(S.Set Makepkg)
+ , buildPathOf :: !(Path Absolute)
+ , buildUserOf :: !(Maybe User)
+ , truncationOf :: !Truncation -- For `-As`
+ , buildSwitchesOf :: !(S.Set BuildSwitch) } deriving (Show)
+
+-- | Extra options for customizing the build process.
+data BuildSwitch = DeleteMakeDeps
+ | DiffPkgbuilds
+ | DontSuppressMakepkg
+ | DryRun
+ | HotEdit
+ | LowVerbosity
+ | RebuildDevel
+ | SortAlphabetically -- For `-As`
+ | UseCustomizepkg
+ | ForceBuilding
+ | NoPkgbuildCheck
+ deriving (Eq, Ord, Show)
+
+-- | Is some Aura-specific setting turned on for this run?
+switch :: Settings -> BuildSwitch -> Bool
+switch ss bs = S.member bs . buildSwitchesOf $ buildConfigOf ss
+
+-- | Is some Aura/Pacman common setting turned on for this run?
+shared :: Settings -> CommonSwitch -> Bool
+shared ss c = S.member c . commonSwitchesOf $ commonConfigOf ss
+
+-- | The global settings as set by the user with command-line flags.
+data Settings = Settings { managerOf :: !Manager
+ , envOf :: !Environment
+ , langOf :: !Language
+ , editorOf :: !FilePath
+ , isTerminal :: !Bool
+ , ignoresOf :: !(S.Set PkgName)
+ , commonConfigOf :: !CommonConfig
+ , buildConfigOf :: !BuildConfig }
+
+-- | Unless otherwise specified, packages will be built within @/tmp@.
+defaultBuildDir :: Path Absolute
+defaultBuildDir = fromAbsoluteFilePath "/tmp"
diff --git a/lib/Aura/State.hs b/lib/Aura/State.hs
new file mode 100644
index 0000000..2147c87
--- /dev/null
+++ b/lib/Aura/State.hs
@@ -0,0 +1,164 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts, TypeApplications, MonoLocalBinds, DataKinds #-}
+
+-- |
+-- Module : Aura.State
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Handle the saving and restoring of installed package states.
+
+module Aura.State
+ ( PkgState(..)
+ , saveState
+ , restoreState
+ , inState
+ , readState
+ , stateCache
+ , getStateFiles
+ ) where
+
+import Aura.Cache
+import Aura.Colour (red)
+import Aura.Core (warn, notify, liftEitherM, report)
+import Aura.Languages
+import Aura.Pacman (pacmanOutput, pacman)
+import Aura.Settings
+import Aura.Types
+import Aura.Utils
+import BasePrelude hiding (Version, mapMaybe)
+import Control.Error.Util (hush)
+import Control.Monad.Freer
+import Control.Monad.Freer.Error
+import Control.Monad.Freer.Reader
+import Data.Aeson
+import Data.Aeson.Types (typeMismatch)
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy.Char8 as BL
+import Data.Generics.Product (field)
+import Data.List.NonEmpty (nonEmpty)
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import Data.Time
+import Data.Versions
+import Data.Witherable (mapMaybe)
+import Lens.Micro ((^.))
+import System.Path
+import System.Path.IO (createDirectoryIfMissing, getDirectoryContents)
+
+---
+
+-- | All packages installed at some specific `ZonedTime`. Any "pinned" PkgState will
+-- never be deleted by `-Bc`.
+data PkgState = PkgState { timeOf :: ZonedTime, pinnedOf :: Bool, pkgsOf :: M.Map PkgName Versioning }
+
+instance ToJSON PkgState where
+ toJSON (PkgState t pnd ps) = object [ "time" .= t, "pinned" .= pnd, "packages" .= fmap prettyV ps ]
+
+instance FromJSON PkgState where
+ parseJSON (Object v) = PkgState
+ <$> v .: "time"
+ <*> v .: "pinned"
+ <*> fmap f (v .: "packages")
+ where f = mapMaybe (hush . versioning)
+ parseJSON invalid = typeMismatch "PkgState" invalid
+
+data StateDiff = StateDiff { _toAlter :: [SimplePkg], _toRemove :: [PkgName] }
+
+-- | The default location of all saved states: \/var\/cache\/aura\/states
+stateCache :: Path Absolute
+stateCache = fromAbsoluteFilePath "/var/cache/aura/states"
+
+-- | Does a given package have an entry in a particular `PkgState`?
+inState :: SimplePkg -> PkgState -> Bool
+inState (SimplePkg n v) s = maybe False (v ==) . M.lookup n $ pkgsOf s
+
+rawCurrentState :: IO [SimplePkg]
+rawCurrentState = mapMaybe (simplepkg' . strictText) . BL.lines <$> pacmanOutput ["-Q"]
+
+currentState :: IO PkgState
+currentState = do
+ pkgs <- rawCurrentState
+ time <- getZonedTime
+ pure . PkgState time False . M.fromAscList $ map (\(SimplePkg n v) -> (n, v)) pkgs
+
+compareStates :: PkgState -> PkgState -> StateDiff
+compareStates old curr = tcar { _toAlter = olds old curr <> _toAlter tcar }
+ where tcar = toChangeAndRemove old curr
+
+-- | All packages that were changed and newly installed.
+toChangeAndRemove :: PkgState -> PkgState -> StateDiff
+toChangeAndRemove old curr = uncurry StateDiff . M.foldrWithKey status ([], []) $ pkgsOf curr
+ where status k v (d, r) = case M.lookup k (pkgsOf old) of
+ Nothing -> (d, k : r)
+ Just v' | v == v' -> (d, r)
+ | otherwise -> (SimplePkg k v' : d, r)
+
+-- | Packages that were uninstalled since the last record.
+olds :: PkgState -> PkgState -> [SimplePkg]
+olds old curr = map (uncurry SimplePkg) . M.assocs $ M.difference (pkgsOf old) (pkgsOf curr)
+
+-- | The filepaths of every saved package state.
+getStateFiles :: IO [Path Absolute]
+getStateFiles = do
+ createDirectoryIfMissing True stateCache
+ sort . map (stateCache </>) <$> getDirectoryContents stateCache
+
+-- | Save a package state.
+-- In writing the first state file, the `states` directory is created automatically.
+saveState :: Settings -> IO ()
+saveState ss = do
+ state <- currentState
+ let filename = stateCache </> fromUnrootedFilePath (dotFormat (timeOf state)) <.> FileExt "json"
+ createDirectoryIfMissing True stateCache
+ BL.writeFile (toFilePath filename) $ encode state
+ notify ss . saveState_1 $ langOf ss
+
+dotFormat :: ZonedTime -> String
+dotFormat (ZonedTime t _) = intercalate "." items
+ where items = [ show ye
+ , printf "%02d(%s)" mo (mnths !! (mo - 1))
+ , printf "%02d" da
+ , printf "%02d" (todHour $ localTimeOfDay t)
+ , printf "%02d" (todMin $ localTimeOfDay t)
+ , printf "%02d" ((round . todSec $ localTimeOfDay t) :: Int) ]
+ (ye, mo, da) = toGregorian $ localDay t
+ mnths :: [String]
+ mnths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
+
+-- | Does its best to restore a state chosen by the user.
+restoreState :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => Eff r ()
+restoreState = send getStateFiles >>= maybe (throwError $ Failure restoreState_2) f . nonEmpty
+ where f sfs = do
+ ss <- ask
+ let pth = either id id . cachePathOf $ commonConfigOf ss
+ mpast <- send $ selectState sfs >>= readState
+ case mpast of
+ Nothing -> throwError $ Failure readState_1
+ Just past -> do
+ curr <- send currentState
+ Cache cache <- send $ cacheContents pth
+ let StateDiff rein remo = compareStates past curr
+ (okay, nope) = partition (`M.member` cache) rein
+ traverse_ (report red restoreState_1 . fmap (^. field @"name")) $ nonEmpty nope
+ reinstallAndRemove (mapMaybe (`M.lookup` cache) okay) remo
+
+selectState :: NonEmpty (Path Absolute) -> IO (Path Absolute)
+selectState = getSelection (T.pack . toFilePath)
+
+-- | Given a `FilePath` to a package state file, attempt to read and parse
+-- its contents. As of Aura 2.0, only state files in JSON format are accepted.
+readState :: Path Absolute -> IO (Maybe PkgState)
+readState = fmap decode . BL.readFile . toFilePath
+
+-- | `reinstalling` can mean true reinstalling, or just altering.
+reinstallAndRemove :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) =>
+ [PackagePath] -> [PkgName] -> Eff r ()
+reinstallAndRemove [] [] = ask >>= \ss -> send (warn ss . reinstallAndRemove_1 $ langOf ss)
+reinstallAndRemove down remo
+ | null remo = reinstall
+ | null down = remove
+ | otherwise = reinstall *> remove
+ where remove = liftEitherM . pacman $ "-R" : asFlag remo
+ reinstall = liftEitherM . pacman $ "-U" : map (toFilePath . path) down
diff --git a/lib/Aura/Types.hs b/lib/Aura/Types.hs
new file mode 100644
index 0000000..90dc1c2
--- /dev/null
+++ b/lib/Aura/Types.hs
@@ -0,0 +1,258 @@
+{-# LANGUAGE OverloadedStrings, MultiWayIf #-}
+{-# LANGUAGE DataKinds, TypeApplications, DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving, DeriveGeneric #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+-- |
+-- Module : Aura.Types
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Core Aura types.
+
+module Aura.Types
+ ( -- * Package Types
+ Package(..), pname, pprov, pver
+ , Dep(..), parseDep
+ , Buildable(..)
+ , Prebuilt(..)
+ , SimplePkg(..), simplepkg, simplepkg'
+ -- * Typeclasses
+ , Flagable(..)
+ -- * Package Building
+ , VersionDemand(..), _VersionDemand
+ , InstallType(..)
+ -- * Errors
+ , DepError(..)
+ , Failure(..)
+ -- * Language
+ , Language(..)
+ -- * Other Wrappers
+ , PkgName(..)
+ , PkgGroup(..)
+ , Provides(..)
+ , PackagePath(..)
+ , Pkgbuild(..)
+ , Environment(..)
+ , User(..)
+ -- * Misc.
+ , list
+ ) where
+
+import BasePrelude hiding (try)
+import Control.Error.Util (hush)
+import Data.Aeson (ToJSONKey, FromJSONKey)
+import Data.Bitraversable
+import qualified Data.ByteString.Lazy as BL
+import Data.Generics.Product (field, super)
+import Data.List.NonEmpty (nonEmpty)
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import Data.Text.Prettyprint.Doc hiding (space, list)
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Data.Versions hiding (Traversal')
+import GHC.Generics (Generic)
+import Lens.Micro
+import System.Path (Path, Absolute, takeFileName, toUnrootedFilePath)
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+---
+
+-- | Types whose members can be converted to CLI flags.
+class Flagable a where
+ asFlag :: a -> [String]
+
+instance Flagable T.Text where
+ asFlag t = [T.unpack t]
+
+instance (Foldable f, Flagable a) => Flagable (f a) where
+ asFlag = foldMap asFlag
+
+-- | A package to be installed.
+data Package = FromRepo Prebuilt | FromAUR Buildable deriving (Eq)
+
+-- | The name of a `Package`.
+pname :: Package -> PkgName
+pname (FromRepo pb) = pb ^. field @"name"
+pname (FromAUR b) = b ^. field @"name"
+
+-- | Other names which allow this `Package` to be satisfied as a dependency.
+pprov :: Package -> Provides
+pprov (FromRepo pb) = pb ^. field @"provides"
+pprov (FromAUR b) = b ^. field @"provides"
+
+-- | The version of a `Package`.
+pver :: Package -> Versioning
+pver (FromRepo pb) = pb ^. field @"version"
+pver (FromAUR b) = b ^. field @"version"
+
+-- TODO Figure out how to do this more generically.
+instance Ord Package where
+ compare (FromAUR a) (FromAUR b) = compare a b
+ compare (FromRepo a) (FromRepo b) = compare a b
+ compare (FromAUR a) (FromRepo b) = compare (a ^. super @SimplePkg) (b ^. super @SimplePkg)
+ compare (FromRepo a) (FromAUR b) = compare (a ^. super @SimplePkg) (b ^. super @SimplePkg)
+
+-- | A `Package` from the AUR that's buildable in some way on the user's machine.
+data Buildable = Buildable { name :: !PkgName
+ , version :: !Versioning
+ , base :: !PkgName
+ , provides :: !Provides
+ , deps :: ![Dep]
+ , pkgbuild :: !Pkgbuild
+ , isExplicit :: !Bool } deriving (Eq, Ord, Show, Generic)
+
+-- | A prebuilt `Package` from the official Arch repositories.
+data Prebuilt = Prebuilt { name :: !PkgName
+ , version :: !Versioning
+ , base :: !PkgName
+ , provides :: !Provides } deriving (Eq, Ord, Show, Generic)
+
+-- | A dependency on another package.
+data Dep = Dep { name :: !PkgName
+ , demand :: !VersionDemand } deriving (Eq, Ord, Show, Generic)
+
+-- | Parse a dependency entry as it would appear in a PKGBUILD:
+--
+-- @
+-- >>> parseDep "pacman>1.2.3"
+-- Just (Dep {name = PkgName {name = "pacman"}, demand = >1.2.3})
+-- @
+parseDep :: T.Text -> Maybe Dep
+parseDep = hush . parse dep "dep"
+ where dep = Dep <$> n <*> v
+ n = PkgName <$> takeWhile1P Nothing (\c -> c /= '<' && c /= '>' && c /= '=')
+ v = do
+ end <- atEnd
+ if | end -> pure Anything
+ | otherwise -> choice [ char '<' *> fmap LessThan versioning'
+ , string ">=" *> fmap AtLeast versioning'
+ , char '>' *> fmap MoreThan versioning'
+ , char '=' *> fmap MustBe versioning'
+ , pure Anything ]
+
+-- | The versioning requirement of some package's dependency.
+data VersionDemand = LessThan Versioning
+ | AtLeast Versioning
+ | MoreThan Versioning
+ | MustBe Versioning
+ | Anything
+ deriving (Eq, Ord)
+
+instance Show VersionDemand where
+ show (LessThan v) = T.unpack $ "<" <> prettyV v
+ show (AtLeast v) = T.unpack $ ">=" <> prettyV v
+ show (MoreThan v) = T.unpack $ ">" <> prettyV v
+ show (MustBe v) = T.unpack $ "=" <> prettyV v
+ show Anything = "Anything"
+
+-- | Attempt to zoom into the `Versioning` hiding within a `VersionDemand`.
+_VersionDemand :: Traversal' VersionDemand Versioning
+_VersionDemand f (LessThan v) = LessThan <$> f v
+_VersionDemand f (AtLeast v) = AtLeast <$> f v
+_VersionDemand f (MoreThan v) = MoreThan <$> f v
+_VersionDemand f (MustBe v) = MustBe <$> f v
+_VersionDemand _ p = pure p
+
+-- | The installation method.
+data InstallType = Pacman PkgName | Build Buildable deriving (Eq)
+
+-- | A package name with its version number.
+data SimplePkg = SimplePkg { name :: !PkgName, version :: !Versioning } deriving (Eq, Ord, Show, Generic)
+
+-- | Attempt to create a `SimplePkg` from filepaths like
+-- @\/var\/cache\/pacman\/pkg\/linux-3.2.14-1-x86_64.pkg.tar.xz@
+simplepkg :: PackagePath -> Maybe SimplePkg
+simplepkg (PackagePath t) = uncurry SimplePkg <$> bitraverse hush hush (parse n "name" t', parse v "version" t')
+ where t' = T.pack . toUnrootedFilePath $ takeFileName t
+
+ n :: Parsec Void T.Text PkgName
+ n = PkgName . T.pack <$> manyTill anyChar (try finished)
+
+ -- | Assumes that a version number will never start with a letter,
+ -- and that a package name section (i.e. abc-def-ghi) will never start
+ -- with a number.
+ finished = char '-' *> lookAhead digitChar
+ v = manyTill anyChar (try finished) *> ver
+ ver = try (fmap Ideal semver' <* post) <|> try (fmap General version' <* post) <|> fmap Complex mess'
+ post = char '-' *> (string "x86_64" <|> string "any") *> string ".pkg.tar.xz"
+
+-- | Attempt to create a `SimplePkg` from text like:
+-- xchat 2.8.8-19
+simplepkg' :: T.Text -> Maybe SimplePkg
+simplepkg' = hush . parse parser "name-and-version"
+ where parser = SimplePkg <$> (PkgName <$> takeWhile1P Nothing (/= ' ')) <*> (space *> versioning')
+
+-- | Filepaths like:
+--
+-- * \/var\/cache\/pacman\/pkg\/linux-3.2.14-1-x86_64.pkg.tar.xz
+-- * \/var\/cache\/pacman\/pkg\/wine-1.4rc6-1-x86_64.pkg.tar.xz
+-- * \/var\/cache\/pacman\/pkg\/ruby-1.9.3_p125-4-x86_64.pkg.tar.xz
+newtype PackagePath = PackagePath { path :: Path Absolute } deriving (Eq, Generic)
+
+-- | If they have the same package names, compare by their versions.
+-- Otherwise, do raw comparison of the path string.
+instance Ord PackagePath where
+ compare a b | nameA /= nameB = compare (path a) (path b)
+ | otherwise = compare verA verB
+ where (nameA, verA) = f a
+ (nameB, verB) = f b
+ f = ((^? _Just . field @"name") &&& (^? _Just . field @"version")) . simplepkg
+
+-- | The contents of a PKGBUILD file.
+newtype Pkgbuild = Pkgbuild { pkgbuild :: BL.ByteString } deriving (Eq, Ord, Show, Generic)
+
+-- | All human languages available for text output.
+data Language = English
+ | Japanese
+ | Polish
+ | Croatian
+ | Swedish
+ | German
+ | Spanish
+ | Portuguese
+ | French
+ | Russian
+ | Italian
+ | Serbian
+ | Norwegian
+ | Indonesia
+ | Chinese
+ deriving (Eq, Enum, Bounded, Ord, Show)
+
+-- | The various ways that dependency resolution can fail.
+data DepError = NonExistant PkgName
+ | VerConflict (Doc AnsiStyle)
+ | Ignored (Doc AnsiStyle)
+ | BrokenProvides PkgName Provides PkgName
+
+-- | Some failure message that when given the current runtime `Language`
+-- will produce a human-friendly error.
+newtype Failure = Failure { failure :: Language -> Doc AnsiStyle }
+
+-- | Shell environment variables.
+type Environment = M.Map T.Text T.Text
+
+-- | The name of a user account on a Linux system.
+newtype User = User { user :: T.Text } deriving (Eq, Show, Generic)
+
+-- | The name of an Arch Linux package.
+newtype PkgName = PkgName { name :: T.Text }
+ deriving stock (Eq, Ord, Show, Generic)
+ deriving newtype (Flagable, ToJSONKey, FromJSONKey, IsString)
+
+-- | A group that a `Package` could belong too, like @base@, @base-devel@, etc.
+newtype PkgGroup = PkgGroup { group :: T.Text }
+ deriving stock (Eq, Ord, Show, Generic)
+ deriving newtype (Flagable)
+
+-- | The dependency which some package provides. May not be the same name
+-- as the package itself (e.g. cronie provides cron).
+newtype Provides = Provides { provides :: T.Text } deriving (Eq, Ord, Show, Generic)
+
+-- | Similar to `maybe` and `either`, but not quite the same.
+list :: b -> (NonEmpty a -> b) -> [a] -> b
+list def f as = maybe def f $ nonEmpty as
diff --git a/lib/Aura/Utils.hs b/lib/Aura/Utils.hs
new file mode 100644
index 0000000..4c3d7af
--- /dev/null
+++ b/lib/Aura/Utils.hs
@@ -0,0 +1,224 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module : Aura.Utils
+-- Copyright : (c) Colin Woodbury, 2012 - 2018
+-- License : GPL3
+-- Maintainer: Colin Woodbury <colin@fosskers.ca>
+--
+-- Utility functions specific to Aura.
+
+module Aura.Utils
+ ( -- * Strings
+ Pattern(..)
+ , replaceByPatt, searchLines
+ , strictText
+ -- * Network
+ , urlContents
+ -- * Shell
+ , csi, cursorUpLineCode, hideCursor, showCursor, raiseCursorBy
+ , getTrueUser, getEditor, getLocale
+ , hasRootPriv, isTrueRoot
+ , chown
+ -- * File IO
+ , ifFile
+ -- * Output
+ , putStrLnA
+ , colourCheck
+ , entrify
+ -- * User Input
+ , optionalPrompt
+ , getSelection
+ ) where
+
+import Aura.Colour
+import Aura.Languages (whitespace, yesNoMessage, yesPattern)
+import Aura.Settings
+import Aura.Types (Language, Environment(..), User(..))
+import BasePrelude hiding (Version, (<+>))
+import Control.Monad.Trans (MonadIO)
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import Data.Text.Encoding.Error (lenientDecode)
+import qualified Data.Text.IO as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import Data.Text.Prettyprint.Doc
+import Data.Text.Prettyprint.Doc.Render.Terminal
+import Network.HTTP.Client
+import Network.HTTP.Types.Status (statusCode)
+import System.IO (stdout, hFlush)
+import System.Path (Path, Absolute, toFilePath)
+import System.Path.IO (doesFileExist)
+import System.Process.Typed (runProcess, proc)
+
+---
+
+---------
+-- STRING
+---------
+
+-- | For regex-like find-and-replace in some `T.Text`.
+data Pattern = Pattern { _pattern :: T.Text, _target :: T.Text }
+
+-- | Replaces a (p)attern with a (t)arget in a line if possible.
+replaceByPatt :: [Pattern] -> T.Text -> T.Text
+replaceByPatt [] l = l
+replaceByPatt (Pattern p t : ps) l = case T.breakOn p l of
+ -- No match.
+ (_, "") -> replaceByPatt ps l
+ -- Matched. The matched pattern is still present at the head of `rest`,
+ -- so we need to drop it first.
+ (cs, rest) -> replaceByPatt ps (cs <> t <> T.drop (T.length p) rest)
+
+-- | Find lines which contain some given `T.Text`.
+searchLines :: T.Text -> [T.Text] -> [T.Text]
+searchLines pat = filter (T.isInfixOf pat)
+
+-- | Get strict Text out of a lazy ByteString.
+strictText :: BL.ByteString -> T.Text
+strictText = TL.toStrict . TL.decodeUtf8With lenientDecode
+
+-----
+-- IO
+-----
+-- | Given a number of selections, allows the user to choose one.
+getSelection :: Foldable f => (a -> T.Text) -> f a -> IO a
+getSelection f choiceLabels = do
+ let quantity = length choiceLabels
+ valids = show <$> [1..quantity]
+ pad = show . length . show $ quantity
+ choices = zip valids $ toList choiceLabels
+ traverse_ (\(l,v) -> printf ("%" <> pad <> "s. %s\n") l (f v)) choices
+ putStr ">> "
+ hFlush stdout
+ userChoice <- getLine
+ case userChoice `lookup` choices of
+ Just valid -> pure valid
+ Nothing -> getSelection f choiceLabels -- Ask again.
+
+-- | If a file exists, it performs action `t` on the argument.
+-- | If the file doesn't exist, it performs `f` and returns the argument.
+ifFile :: MonadIO m => (a -> m a) -> m b -> Path Absolute -> a -> m a
+ifFile t f file x = liftIO (doesFileExist file) >>= bool (f $> x) (t x)
+
+----------
+-- NETWORK
+----------
+-- | Assumes the given URL is correctly formatted.
+urlContents :: Manager -> String -> IO (Maybe L.ByteString)
+urlContents m url = f <$> httpLbs (parseRequest_ url) m
+ where f res | statusCode (responseStatus res) == 200 = Just $ responseBody res
+ | otherwise = Nothing
+
+--------
+-- SHELL
+--------
+-- | Code borrowed from `ansi-terminal` library by Max Bolingbroke.
+csi :: [Int] -> T.Text -> T.Text
+csi args code = "\ESC[" <> T.intercalate ";" (map (T.pack . show) args) <> code
+
+-- | Terminal code for raising the cursor.
+cursorUpLineCode :: Int -> T.Text
+cursorUpLineCode n = csi [n] "F"
+
+-- | This will get the true user name regardless of sudo-ing.
+getTrueUser :: Environment -> Maybe User
+getTrueUser env | isTrueRoot env = Just $ User "root"
+ | hasRootPriv env = User <$> M.lookup "SUDO_USER" env
+ | otherwise = User <$> M.lookup "USER" env
+
+-- | Is the current user of Aura the true @root@ user, and not just a sudo user?
+isTrueRoot :: Environment -> Bool
+isTrueRoot env = M.lookup "USER" env == Just "root" && not (M.member "SUDO_USER" env)
+
+-- | Is the user root, or using sudo?
+hasRootPriv :: Environment -> Bool
+hasRootPriv env = M.member "SUDO_USER" env || isTrueRoot env
+
+-- | `vi` is a sensible default, it should be installed by
+-- on any Arch system.
+getEditor :: Environment -> FilePath
+getEditor = maybe "vi" T.unpack . M.lookup "EDITOR"
+
+-- | This will get the locale variable for translations from the environment
+getLocale :: Environment -> T.Text
+getLocale env = fromMaybe "C" . asum $ map (`M.lookup` env) ["LC_ALL", "LC_MESSAGES", "LANG"]
+
+-- | Mark some `Path` as being owned by a `User`.
+chown :: MonadIO m => User -> Path Absolute -> [String] -> m ()
+chown (User usr) pth args = void . runProcess $ proc "chown" (args <> [T.unpack usr, toFilePath pth])
+
+-- | Hide the cursor in a terminal.
+hideCursor :: IO ()
+hideCursor = T.putStr hideCursorCode
+
+-- | Restore a cursor to visiblity in the terminal.
+showCursor :: IO ()
+showCursor = T.putStr showCursorCode
+
+hideCursorCode :: T.Text
+hideCursorCode = csi [] "?25l"
+
+showCursorCode :: T.Text
+showCursorCode = csi [] "?25h"
+
+-- | Raise the cursor by @n@ lines.
+raiseCursorBy :: Int -> IO ()
+raiseCursorBy = T.putStr . cursorUpLineCode
+
+----------------
+-- CUSTOM OUTPUT
+----------------
+
+-- | Print a `Doc` with Aura flair after performing a `colourCheck`.
+putStrLnA :: Settings -> Doc AnsiStyle -> IO ()
+putStrLnA ss d = putStrA ss $ d <> hardline
+
+-- | Will remove all colour annotations if the user specified @--color=never@.
+putStrA :: Settings -> Doc AnsiStyle -> IO ()
+putStrA ss d = T.putStr . dtot $ "aura >>=" <+> colourCheck ss d
+
+-- | Strip colours from a `Doc` if @--color=never@ is specified,
+-- or if the output target isn't a terminal.
+colourCheck :: Settings -> Doc ann -> Doc ann
+colourCheck ss | shared ss (Colour Never) = unAnnotate
+ | shared ss (Colour Always) = id
+ | isTerminal ss = id
+ | otherwise = unAnnotate
+
+----------
+-- PROMPTS
+----------
+yesNoPrompt :: Settings -> Doc AnsiStyle -> IO Bool
+yesNoPrompt ss msg = do
+ putStrA ss . yellow $ msg <+> yesNoMessage (langOf ss) <> " "
+ hFlush stdout
+ response <- T.getLine
+ pure $ isAffirmative (langOf ss) response
+
+-- | An empty response emplies "yes".
+isAffirmative :: Language -> T.Text -> Bool
+isAffirmative l t = T.null t || elem t (yesPattern l)
+
+-- | Doesn't prompt when `--noconfirm` is used.
+optionalPrompt :: Settings -> (Language -> Doc AnsiStyle) -> IO Bool
+optionalPrompt ss msg | shared ss NoConfirm = pure True
+ | otherwise = yesNoPrompt ss (msg $ langOf ss)
+
+-------
+-- MISC
+-------
+-- | Format two lists into two nice rows a la `-Qi` or `-Si`.
+entrify :: Settings -> [T.Text] -> [Doc AnsiStyle] -> Doc AnsiStyle
+entrify ss fs es = vsep $ zipWith combine fs' es
+ where fs' = padding ss fs
+ combine f e = annotate bold (pretty f) <+> ":" <+> e
+
+-- | Right-pads strings according to the longest string in the group.
+padding :: Settings -> [T.Text] -> [T.Text]
+padding ss fs = map (T.justifyLeft longest ws) fs
+ where ws = whitespace $ langOf ss
+ longest = maximum $ map T.length fs
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
index 0000000..8a60a5d
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main ( main ) where
+
+import Aura.Languages
+import Aura.Packages.Repository
+import Aura.Pacman
+import Aura.Pkgbuild.Security
+import Aura.Types
+import BasePrelude
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import Data.Versions
+-- import Language.Bash.Pretty (prettyText)
+import System.Path
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Megaparsec
+-- import Text.Pretty.Simple (pPrintNoColor)
+
+---
+
+main :: IO ()
+main = do
+ conf <- T.readFile "test/pacman.conf"
+ pkb <- Pkgbuild <$> BL.readFile "test/aura.PKGBUILD"
+ defaultMain $ suite conf pkb
+
+suite :: T.Text -> Pkgbuild -> TestTree
+suite conf pb = testGroup "Unit Tests"
+ [ testGroup "Aura.Core"
+ [ testCase "parseDep - python2" $ parseDep "python2" @?= Just (Dep "python2" Anything)
+ , testCase "parseDep - python2-lxml>=3.1.0"
+ $ parseDep "python2-lxml>=3.1.0" @?= Just (Dep "python2-lxml" . AtLeast . Ideal $ SemVer 3 1 0 [] [])
+ , testCase "parseDep - foobar>1.2.3"
+ $ parseDep "foobar>1.2.3" @?= Just (Dep "foobar" . MoreThan . Ideal $ SemVer 1 2 3 [] [])
+ , testCase "parseDep - foobar=1.2.3"
+ $ parseDep "foobar=1.2.3" @?= Just (Dep "foobar" . MustBe . Ideal $ SemVer 1 2 3 [] [])
+ ]
+ , testGroup "Aura.Types"
+ [ testCase "simplepkg"
+ $ simplepkg (PackagePath $ fromAbsoluteFilePath "/var/cache/pacman/pkg/linux-is-cool-3.2.14-1-x86_64.pkg.tar.xz")
+ @?= Just (SimplePkg "linux-is-cool" . Ideal $ SemVer 3 2 14 [[Digits 1]] [])
+ , testCase "simplepkg'"
+ $ simplepkg' "xchat 2.8.8-19" @?= Just (SimplePkg "xchat" . Ideal $ SemVer 2 8 8 [[Digits 19]] [])
+ ]
+ , testGroup "Aura.Packages.Repository"
+ [ testCase "extractVersion" $ extractVersion firefox @?= Just (Ideal $ SemVer 60 0 2 [[Digits 1]] [])
+ ]
+ , testGroup "Aura.Pacman"
+ [ testCase "Parsing pacman.conf" $ do
+ let p = parse config "pacman.conf" conf
+ r = either (const Nothing) (\(Config c) -> Just c) p >>= M.lookup "HoldPkg"
+ r @?= Just ["pacman", "glibc"]
+ ]
+ , testGroup "Aura.Languages"
+ [ testCase "Language names are complete" $ do
+ case [minBound..maxBound] :: [Language] of
+ [] -> assertFailure "No languages found"
+ lang:restOfLangs -> do
+ let names = languageNames lang
+ for_ restOfLangs $ \otherLang -> do
+ let otherNames = languageNames otherLang
+ assertEqual ("Language name maps for " ++ show lang ++ " and " ++ show otherLang ++ " have different size") (M.size names) (M.size otherNames)
+ ]
+ , testGroup "Aura.Pkgbuild.Security"
+ [ testCase "Parsing - aura.PKGBUILD" $ do
+ -- pPrintNoColor $ map (first prettyText) . bannedTerms <$> ppb
+ -- pPrintNoColor ppb
+ assertBool "Failed to parse" $ isJust ppb
+ , testCase "Detecting banned terms" $ (length . bannedTerms <$> ppb) @?= Just 5
+ ]
+ ]
+ where ppb = parsedPB pb
+
+firefox :: T.Text
+firefox = "Repository : extra\n\
+\Name : firefox\n\
+\Version : 60.0.2-1\n\
+\Description : Standalone web browser from mozilla.org"