1
0
Fork 0
mirror of https://git.sr.ht/~nixgoat/vento synced 2025-07-26 23:20:54 +00:00

Compare commits

...

99 commits
v0.1 ... master

Author SHA1 Message Date
Lux Aliaga e96110748e
history: Add history migration
This adds an option to the vento command which allows users to migrate
from the old history file to the newer database. It parses the history
file, writes the changes into the database, and then deletes the
previous file.
2024-02-22 01:14:41 -03:00
Lux Aliaga 46ac7ee8b7
history: Add history viewer
It works by showing n changes before and after the current position
in a table. Requires an 83+ column wide terminal. Command usage is
"vento -v [<n>]".
2024-02-22 01:14:41 -03:00
Lux Aliaga 600c3e4f3a
history: Implement new database system
The new database system uses SQLite3, and allows users to move across
a linear history of their actions, including moving forward from a
previous action. Whenever a user runs a new action, the actions after
the current position within the history will be removed.
2024-02-22 01:14:35 -03:00
Lux Aliaga 92b2942471
manifest: Bump version to v1.4-alpha
After a few months, it's time to prepare the next Vento release. I've
been cooking some ideas, so this one might be big. 👀
2024-02-21 20:16:56 -03:00
Lux Aliaga cba202fd0d manifest: Bump version to v1.3
Goals for this release have been worked on and the software has been
tested enough. Bump the version and update the installation
instructions.
2023-08-28 09:06:15 -04:00
Lux Aliaga 73ef278372 inv: Use is_empty() method for empty comparisons
This was suggested by cargo-clippy. Instead of comparing against "",
it's better use the method is_empty() to determine if the dir argument
in the list() function is not empty.
2023-08-28 02:03:29 -04:00
Lux Aliaga 2b8126eb53 build: Update manpages
Changes the maintainer information, adds the new configuration
directives and specifies format for hierarchies.
2023-08-28 01:56:50 -04:00
Lux Aliaga 343c97e92c item: Remove extra whitespace on Take message
If you were to take a file with display_dir enabled and the slot flag,
it would add an additional whitespace. Remove it.
2023-08-26 21:36:10 -04:00
Lux Aliaga 9589fcb674 inv: Don't display slot unless argument is passed
Similarly to item actions, when the slot argument is not passed, it is
implied that the slot used is the active slot, therefore it is not
necessary to reiterate it.
2023-08-26 15:32:27 -04:00
Lux Aliaga e9aa1b2ee1 common: Add Item hierarchy in config
To avoid confusion, this moves the config "display_dir" to a new
section named "Item". For now this is the only config this new section
will have.
2023-08-26 11:24:15 -04:00
Lux Aliaga 1506dc811c item: Don't display slot if not stated explicitly
It is implied that if you're working without the slot argument you are
working with the active slot, therefore it's not needed for the message
to output that.
2023-08-26 10:54:53 -04:00
Lux Aliaga 63777e6f61 history: Add config to disable displaying dirs
This option is separate from the item actions, since it may be
important for some users to still see where the actions are being
undone. Therefore, I've added this option to a new hierarchy named
"history", which is why I've imported serde into the crate.
2023-08-26 10:51:20 -04:00
Lux Aliaga 2ab970c371 item: Add display_dir config
This option allows the user to prevent showing the path where the file
was taken/dropped from. Next up would be implementing this in history
changes.
2023-08-23 08:48:26 -04:00
Lux Aliaga bb02a070f9 help: Deprecate in favor of Clap
Since Clap integrates its own help page, help.rs is not necessary
anymore. Remove it.
2023-08-21 13:41:41 -04:00
Lux Aliaga a841ba974c bin: Refactor binaries on Clap
This has been an idea that has been on my mind for a bit. My argument
parser was very complex and hard to maintain. Therefore, I've decided
to refactor them using the Clap crate, which should make this more
straight forward.
2023-08-21 13:34:01 -04:00
Lux Aliaga 52a830331c src: Linting and formatting
I passed the file through rustfmt and rust-clippy in order to fulfill
some suggestions. Didn't do this before because I didn't have these
utilities in hand, but here we go!
2023-08-20 12:36:45 -04:00
Lux Aliaga 56c12f5d4e readme: Update logo URL
When migrating this repo, I didn't change the URL for the logo, which
was hosted on Codeberg. Update it to Sourcehut.
2023-08-19 20:33:44 -04:00
Lux Aliaga 925513a136 manifest: Update crate dependencies
I've changed the format of the versions to the ones recommended for
each crate. This should keep the package up-to-date when updates come
for it.
2023-08-19 20:30:00 -04:00
Lux Aliaga c1576270b9 common: Implement display_colors config
This works using the override_color() function within common.rs, which,
using colored's set_override function and if set to false in the config
file, will disable all coloring within the program, in case your
terminal isn't that fancy.
2023-08-19 20:26:28 -04:00
Lux Aliaga d80ccbea07 manifest: Update metadata and installation guide
Add a step to make the user check out to the latest stable release.
Also, update my email in the crate's maintainer list.
2023-08-19 18:58:40 -04:00
Lux Aliaga 17217173f4 src: Revamp message displaying
Errors and messages will now be handled by a single module called
message.rs. This module has a new function called append_emoji(), which
will prefix emojis to each message. It also adds an enum named
EmojiType, which will contain each possible emoji prefix. Lastly, add
two new configs: "display_emoji" and "display_colors". "display_colors"
is yet to be implemented.
2023-08-19 18:54:43 -04:00
Lux Aliaga 1e2fb49dac inv: Remove whitespace between emoji and message
This happened to be an issue with the terminal I was using to test out
Vento. It would not space emojis correctly, causing them to require an
additional whitespace to be displayed correctly. Now that I've switched
terminals, it's now displayed as it really is: two whitespaces. Remove
the additional whitespaces I've accidentally added.
2023-08-19 18:54:43 -04:00
Lux Aliaga 941d4c07d5 manifest: Bump version to v1.3-alpha
Finally, I've learned my lesson and on development trees I'll start
using prefixed version numbers in order to not cause confusions.
2023-08-19 18:54:36 -04:00
Lux Aliaga b5c0818719
manifest: Bump version to v1.2
New features, new version!
2023-02-19 18:24:27 -03:00
Lux Aliaga 0424ec2ba9
src: Add public documentation for remaining functions
Essentially the documentation that will go into docs.rs.
2023-02-19 18:23:00 -03:00
Lux Aliaga 27b42e963e
error: Add more error types
Since error refactoring was quite incomplete, I've added more errors for other parts of Vento. This should cover almost every error except slot errors, since those require their own formatting.
2023-02-19 14:42:26 -03:00
Lux Aliaga 67d2da8767
error: Fix switchup between SpecifyFile and SpecifySlot
Turns out SpecifyFile would show the message for SpecifySlot and
viceversa. Whoops!
2023-02-19 13:34:32 -03:00
Lux Aliaga 5e2763f5df
manifest: Remove unused flate2 crate
I accidentally added this crate which ended up not being used. Oh well.
2023-02-19 13:32:40 -03:00
Lux Aliaga 75fb04d674
help: Document the archiving flags
The only caveat is the limitations of the man library will not
allow me to write more in-depth documentation of the new flags. Maybe if
I go through refactoring the binary side using clap this will be easier.
2023-02-19 13:30:48 -03:00
Lux Aliaga d148a96e06
archive: Reword Vento directory archives
Calling the action of archiving the Vento directory "Archiving Vento
installs" was a bit misleading, so I changed it to Vento directories.
2023-02-19 13:19:20 -03:00
Lux Aliaga 2a4ee258a3
archive: Add install importing
Allows exported installs to be imported using the command `vento -G`.
Mind the uppercase "G" there!
2023-02-19 13:04:33 -03:00
Lux Aliaga 13d0889ad1
archive: Add inventory importing
Allows exported inventories to be imported using the command `vento -g`
2023-02-19 12:49:11 -03:00
Lux Aliaga c5cc1ea97a
archive: Add install exporting
This exports all contents in your vento directory into an xz tarball.
Needs documentation, but essentially it works using `vento -E`. Mind the
uppercase E there!
2023-02-17 13:39:56 -03:00
Lux Aliaga faf8036b19
archive: Fixed slot colors
In the confirmation message the slot being exported showed as white. It
should now be colored in either green, blue or red.
2023-02-17 12:44:18 -03:00
Lux Aliaga b6162a9dcb
archive: Add inventory exporting
Currently undocumented, but this should allow users to export their
inventory slots as xz tarballs using `vento -e`. Ideal for sharing
collections of files to friends!
2023-02-17 02:07:28 -03:00
Lux Aliaga 791cdf3193
src: Supress clippy warnings
Most of them were related to unnecessary borrows, although one was
because I pointlessly added a format! inside a println!. Whoops.
2023-02-09 21:35:12 -03:00
Lux Aliaga 4427dadcfc
src: bin: Remove unused crates
After refactoring the compiler started warning about some crates that
were unused, so I removed them.
2023-02-09 21:30:09 -03:00
Lux Aliaga 9e5e0716d1
src: Error refactoring on vento CLI
Since many of the errors were being reiterated in the files for the
binaries I've decided to refactor them so we borrow which errors we want
to show from an enum and then execute a function which essentially bails
and matches each error.
2023-02-09 21:10:39 -03:00
Lux Aliaga 986c0f7f20
help: Update year in copyright notice
Since we're not in 2022 anymore and I'll be working further on this
later, I'm extending the year in the copyright notice to 2023.
2023-02-09 12:03:01 -03:00
Lux Aliaga 6750a387db
manifest: Bump version to v1.1.3
To apply the changes I've made in Crates.io, I'll need to create a new
release.
2022-11-16 17:20:56 -03:00
Lux Aliaga 86d3a00f02
project: Sourcehut migration
After the situation with the developers of Gitea wanting to privatize
their project and experiment with DAOs, I've decided to finally switch
to Sourcehut. The opinions on this forge are generally favorable, so I'm
hoping this will be for the best.
2022-11-16 17:19:41 -03:00
Lux Aliaga 1b533160e6
manifest: Remove patch version for Anyhow
This should make it so Anyhow gets the latest patch updates instead of
staying on v1.0.65.
2022-11-16 11:44:01 -03:00
Lux Aliaga 5a0988d2c6
manifest: Bump version to v1.1.2
I don't really have more changes to add to Vento after this, so I guess
I'll push these changes to a new release. Have fun!
2022-11-07 18:21:02 -03:00
Lux Aliaga 833669af3c
item: Add slot to take/drop messages
To be more consistent with undo messages, I've added the slot in which
items are involved to take and drop messages. Simple as that.
2022-11-07 18:18:25 -03:00
Lux Aliaga dadb0d721f
readme: Fixed typo
The command to undo the last action is "vento -u" and not "vento -c".
2022-11-03 22:56:55 -03:00
Lux Aliaga ab313e2f50
manifest: Bump version to v1.1.1
Enough changes have been done for a newer version and I can confidently
call this a stable release. Enjoy.
2022-11-03 21:32:30 -03:00
Lux Aliaga ef3dcf8acc
inv: Emoji consistency
The slot switching action had a different emoji for confirming the
command was successful compared to other actions, so I switched it for
the green checkmark.
2022-11-03 21:15:00 -03:00
Lux Aliaga 1cbfc5a568
history: Nicer messages for undo result
It includes more details such as:
- What action was undone
- What item was affected in the action
- What that action involved
2022-11-03 21:06:28 -03:00
Lux Aliaga 19aa1955c8
inv: Add "message" arg to slot switching
When undoing a slot switching command, it would show the message
normally show when running the command by itself, which is not supposed
to happen. This commit fixes that by adding a boolean argument that will
print the message when true.
2022-11-03 19:42:39 -03:00
Lux Aliaga c1533e6b41
inv: Include slot switching in undoable actions
Turns out the history didn't include undoing slot switching, so an
action could accidentally be undone in the wrong slot. I've fixed that
and also reorganized the source so undoing actions wouldn't be pegged to
the item module.
2022-11-03 19:26:15 -03:00
Lux Aliaga 6ad43bbf82
src: Remove periods from end of sentences
For many messages there were some consistency issues in terms of
punctuation. I've fixed this issue by removing every period from every
sentence.
2022-11-03 19:08:50 -03:00
Lux Aliaga af0a27464b
manifest: Bump version to v1.1.0
New features, new version!
2022-10-26 17:15:58 -03:00
Lux Aliaga 0434457475
readme: Add "vento -u" to examples.
This is probably the last bit of documentation this new command needs.
Time to release!
2022-10-26 17:14:36 -03:00
Lux Aliaga 4f0c52ac16
doc: Add "vento -u" to documentation
I was about to release v1.1.0 until I forgot this command was completely
undocumented, so here it is.
2022-10-26 17:02:23 -03:00
Lux Aliaga 69ce377ec1
src: Improve documentation
Also removes an extra unnecessary comma.
2022-10-26 16:49:45 -03:00
Lux Aliaga 26699fe765
item: Implement undo command
Brand new feature! Now you can easily undo your last action using "vento
-u". While not a fully-fledged history, if you messed something up
during your last action, you can now easily undo it! Thank you
@Ephera@lemmy.ml for the suggestion!
(https://slrpnk.net/post/103331/comment/24020)
2022-10-26 16:37:52 -03:00
Lux Aliaga f8750f3515
item: Store last action
This is part of a user-suggested feature made by @Ephera@lemmy.ml over
on Lemmy (https://slrpnk.net/post/103331/comment/24020). It stores the
last action so it can later be undone by the user. Maybe if I feel
generous I might store a longer history in an SQLite database, but for
now a simple text file will do. The command for undoing will follow.
2022-10-24 17:57:46 -03:00
Lux Aliaga 5f5ae6e41e
src: Removed redundant clones
These suggestions were made by cargo clippy. These clones were
previously there to remove compiler messages, but it seems like they're
not needed anymore.
2022-10-23 12:54:23 -03:00
Lux Aliaga 839d4dcc76
item: Added take and drop messages
This was suggested by @Ephera@lemmy.ml on Lemmy
(https://slrpnk.net/post/103331/comment/24020) and I've decided to
implement it, since it's not really a hard thing to do and makes the
tool more intuitive.
2022-10-23 11:11:52 -03:00
Lux Aliaga d0ddc93b47 Merge pull request 'Refactoring' (#9) from refactor into master
Reviewed-on: https://codeberg.org/nixgoat/vento/pulls/9
2022-10-18 01:22:37 +02:00
Lux Aliaga 3b9a405614
build: Use struct for page data
The tuple approach was a bit complicated, so I decided to switch it out
for a Page struct. Yes, I have an obsession with structs.
2022-10-17 20:09:44 -03:00
Lux Aliaga 5e05bfa2ef
src: Removed unnecessary contexts
This commit should remove some contexts that actually made some errors
more difficult to read. They will now display the idiomatic errors
generated by anyhow.
2022-10-17 19:56:23 -03:00
Lux Aliaga d51163ef09
common: Use struct to deliver settings
After learning more about structs, I've decided to try and use them in
the common module. It replaces the original behavior of delivering the
data using a vector in favor of the Settings struct, also defined on the
same file.
2022-10-17 18:11:38 -03:00
Lux Aliaga b9cea29592
manifest: Bump version to v1.0.4
Another minor update! Really tiny and probably the last one in a good
while.
2022-10-01 17:40:08 -03:00
Lux Aliaga 4712c67bc2
src: Implemented clippy recommendations
Should make some code look less silly.
2022-10-01 17:38:14 -03:00
Lux Aliaga b627d304d8
manifest: Bump version to v1.0.3
And yup, new tiny release! Probably one of the last minor new releases.
2022-09-28 22:10:00 -03:00
Lux Aliaga 56ecc7d580
manifest: Binary size optimizations
This worked like magic! The release binaries went from ~5MB to ~800KB.
This is going in a new release.
2022-09-28 22:02:07 -03:00
Lux Aliaga 26cbbef870
readme: Asciinema demo
Should make showing the functionality of the tool clearer for users.
2022-09-28 17:45:52 -03:00
Lux Aliaga 9007b775e2
manifest: Bump version to v1.0.2
Another minor release!
2022-09-28 17:26:18 -03:00
Lux Aliaga 21c477e2ce
makefile: Disallow macOS from installing manpages.
Since the directory structure on macOS is not even close to the one in
Linux systems, I've decided to stop making it attempt to install the
manpages.
2022-09-28 15:01:43 -03:00
Lux Aliaga 0c08bc0a29
readme: Rewrite for Crates.io
So the readme on Crates.io looks kind of bad. I decided to redo it so it
looks well on there.
2022-09-28 15:01:42 -03:00
Lux Aliaga 96178a11b3
manifest: Bump version to v1.0.1
Just to register the bugfix release.
2022-09-27 22:38:44 -03:00
Lux Aliaga a98a8147e9
item: Fix taking files recursively
This should fix an issue which made it impossible to take files from
directories other than the one the user is sitting at.
2022-09-27 21:23:43 -03:00
Lux Aliaga 6ec32037db
src: Code comments and cleanup
Basically some last refinements to make the code look a bit more
readable and presentable.
2022-09-27 20:05:08 -03:00
Lux Aliaga 6fa0cba8ca
item: Pre-init error handling
Also adds another error handler for invalid slots. Solves #8.
2022-09-27 19:38:44 -03:00
Lux Aliaga 45dfcc9f19
fixup! src: Universal error handling 2022-09-27 19:29:54 -03:00
Lux Aliaga 8fe5bca167
src: Universal error handling
This commit should unify the way Vento handles errors. Solves #7.
2022-09-27 19:16:18 -03:00
Lux Aliaga ef5205ce3a
fixup! common: Add custom config 2022-09-27 00:05:06 -03:00
Lux Aliaga 3cd5ad758c
common: Add custom config
This should allow users to customize the location for their Vento
directory by modifying a single key in vento.toml. More information is
available in the manual page for vento.toml. Powered by config-rs.
Solves #4.
2022-09-26 23:58:42 -03:00
Lux Aliaga d3a2391cf2
readme: Fix syntax
vento help -> vento -h
2022-09-26 17:05:23 -03:00
Lux Aliaga ac986f8822
vento: The BIG commit
Since I worked on a LOT of stuff here, it's quite hard for me to
separate every change I've done. Take this as one huge refactor, which
includes:
- Changing the syntax of the Vento command
- Separating Vento into three binaries: vento, take, drop
- Moving the help messages into a new file called help.rs
- Removing unnecessary format macros
- Changing expect macros to context, for better anyhow compatibility
- Updating the documentation for these changes.
2022-09-26 16:58:33 -03:00
Lux Aliaga aa233555a4
readme: Update quickstart
I know this is not accurate yet but I want to not bother with this
later.
2022-09-24 22:56:44 -03:00
Lux Aliaga 13e28e445c
build: Create manpages for Vento.
I've backtracked from using Clap and instead will keep using my own
custom command parser. I'll be using the man crate instead. These
manpages have the new syntax which I'll implement in the following
commits. This also uses cargo-make to install the manpages because I
don't want the manpages being replaced everytime I rebuild.
2022-09-24 22:51:57 -03:00
Lux Aliaga 61235e7a63
manifest: Bump version to v1.0
Also added some manifest information and the "clap" and "clap_mangen"
dependencies.
2022-09-19 22:25:34 -03:00
Lux Aliaga edcf1408ee
readme: Credit jo!
Seriously, couldn't have done it without her lol
2022-09-19 00:32:00 -03:00
Lux Aliaga 76ad4aad5a
src: Better error handling
Should make it more Rusty™. Solves #2. Thank you @j0 for the help. (i'm
big stupid lol)
2022-09-19 00:31:30 -03:00
Lux Aliaga 756c56ea9f
readme: Add logo
yay we have a new project logo! 💖
2022-09-18 01:43:49 -03:00
Lux Aliaga d24727d301
inv: Prevent non-files from displaying file size
Maybe for folders I could later add a folder size but the method I'm
using to calculate file sizes doesn't work with folders, so yeah.
2022-09-18 00:35:50 -03:00
Lux Aliaga d11a200d6f
inv: Added directory listing
Now you can list the contents of directories with the list command!
2022-09-18 00:31:23 -03:00
Lux Aliaga 6c51a8da6e
inv: Show new message if inventory is empty
Basically so it looks less dumb if there aren't any items in the
inventory the user asked to list.
2022-09-17 22:53:44 -03:00
Lux Aliaga 445b5b1671
inv: Add directory count to inventory list
This commit modifies the spacing before an item in the inventory list.
2022-09-17 22:43:34 -03:00
Lux Aliaga 6c10953e88
inv: Add file size to inventory list
This commit also improves formatting in the inventory list.
2022-09-17 18:33:40 -03:00
Lux Aliaga 28bed7faff
vento: Added Anyhow as a dependency
For better error handling. (thank you @j0 for the suggestion)
2022-09-17 17:43:24 -03:00
Lux Aliaga 30f99bbda4
inv: Add file types to inventory list
This will be specially useful once we can access directories from vento
list. For now it still serves the function of letting the user know some
basic metadata about the files they're keeping in their inventory
2022-09-17 17:27:33 -03:00
Lux Aliaga 96588e7642
readme: Remove TODO list
I'll be using the issue tracker to work on project management from now
on.
2022-09-17 14:58:26 -03:00
Lux Aliaga 8034a25eb9
readme: Add man pages to the TODO list
I mean I need to make this a real POSIX program with full documentation,
right?
2022-09-17 06:01:58 -03:00
Lux Aliaga 98a6b8deca
readme: Added item to TODO list
This idea is to basically replicate the original concept more closely.
2022-09-17 06:00:12 -03:00
Lux Aliaga cb2b59e641
readme: Update TODO list
Just adding more work for me ig 💀
2022-09-17 05:56:42 -03:00
Lux Aliaga c2e276c6e2
src: Code cleanup
To demessify the project I've decided to clean the code a bit and make
it a little less painful to see. The main change here is that instead of
defining every single time the active and inactive directories it calls
it from a vector in common.rs, which is cleaner and allows for easily
implementing a config file to customize those values later on. Also,
version bump!
2022-09-17 05:49:52 -03:00
17 changed files with 3283 additions and 255 deletions

1283
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,42 @@
[package]
name = "vento"
version = "0.1.0"
version = "1.4.0-alpha"
edition = "2021"
readme = "README.md"
description = "A CLI inventory for your files"
authors = ["Lux Aliaga <lux@nixgoat.me>"]
repository = "https://git.sr.ht/~nixgoat/vento"
license = "GPL-3.0-or-later"
keywords = ["utility", "file-manager", "inventory"]
categories = ["command-line-utilities", "filesystem"]
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dirs = "4.0.0"
colored = "2.0.0"
fs_extra = "1.2.0"
dirs = "5.0"
colored = "2"
fs_extra = "1.3"
anyhow = "1.0"
size_format = "1.0.2"
config = "0.14"
xz2 = "0.1"
tar = "0.4"
clap = { version = "4.3.23", features = ["derive"] }
serde = "1.0"
rusqlite = { version = "0.31.0", features = ["bundled"] }
chrono = "0.4"
termion = "3.0.0"
[build-dependencies]
man = "0.3.0"
anyhow = "1.0.65"
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"

32
Makefile.toml Normal file
View file

@ -0,0 +1,32 @@
[tasks.install-binary]
command = "cargo"
args = ["install", "--path", "."]
[tasks.install-manpages]
condition = { platforms = ["linux"] }
script = [
"cp /tmp/vento-man/* $HOME/.local/share/man/man1/",
"rm -rf /tmp/vento-man"
]
[tasks.install]
dependencies = [
"install-binary",
"install-manpages"
]
[tasks.clean-binary]
command = "cargo"
args = ["clean"]
[tasks.clean-manpages]
condition = { platforms = ["linux"] }
script = [
"rm -rf /tmp/vento-man"
]
[tasks.clean]
dependencies = [
"clean-binary",
"clean-manpages"
]

View file

@ -1,25 +1,93 @@
# Vento
![Vento](https://git.sr.ht/~nixgoat/vento/blob/master/assets/logo.png "Vento")
[![Latest version](https://shields.io/crates/v/vento?color=red)](https://crates.io/crates/vento)
[![Downloads](https://shields.io/crates/d/vento)](https://crates.io/crates/vento)
[![Licensed under GPLv3](https://shields.io/crates/l/vento)](https://git.sr.ht/~nixgoat/vento/tree/master/item/LICENSE.md)
Vento is a utility which allows you to manage your files as if you're playing an old text adventure. It's made in Rust and originally inspired by [Chesapeake's Inventory](https://github.com/mothdotmonster/inventory).
## Install
[![asciicast](https://asciinema.org/a/524454.svg)](https://asciinema.org/a/524454)
Clone it using Git and build it with [Cargo](https://rustup.rs/)!
## Installation
### 1) Cargo (Recommended)
Make sure Rust is installed, along with `cargo`, Rust's package manager.
```
$ cargo install vento
```
### 2) Manually
Clone the repository using `git`.
```
$ git clone https://git.sr.ht/~nixgoat/vento && cd vento
```
Check out to the latest stable release.
```
$ git checkout v1.3
```
### 2.a) cargo-make
This install method additionally installs the manpages for Vento. Make sure Rust, `cargo` and `cargo-make` are installed.
```
$ cargo make install
```
### 2.b) Cargo
Make sure Rust is installed, along with `cargo`, Rust's package manager.
```
$ git clone https://codeberg.org/nixgoat/vento.git && cd vento
$ cargo install --path .
```
## Quickstart
## Quick Start
After installing, run `vento init`. This will create a `.vento` folder in your home directory which will store your inventories. After which, you can run `vento list` to display the files in your inventories, `vento take` to move a file into your active inventory and `vento drop` to drop a file out of it. If you're stuck, run `vento help`.
After installing, run:
## TODO:
```
$ vento -i
```
- [ ] Better implementation of `list`
- [ ] Code cleanup
This will create a `.vento` folder in your home directory, which will store your inventories. Some basic commands include:
```
// listing files in the currently active inventory
$ vento
// switching inventory slots
$ vento -c
// undoing last action
$ vento -u
// taking a file or directory
$ take <file|directory>
// dropping a file or directory
$ drop <file|directory> [destination]
```
For additional documentation, you can check the documentation for each command by running the following.
```
$ (command) -h
```
Or, if Vento was installed through `cargo-make`, check the manpages by running:
```
$ man (command)
```
## Credits
- [Chesapeake](https://moth.monster/) for the original concept
- [jo!](https://sr.ht/~j0lol/) for helping me with Rust concepts!

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

204
build.rs Normal file
View file

@ -0,0 +1,204 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use anyhow::Result;
use man::prelude::*;
use std::env;
use std::fs::{create_dir_all, File};
use std::io::Write;
struct Page {
content: String,
file: String,
}
fn main() -> Result<()> {
if cfg!(unix) {
let pages = [vento()?, take()?, drop()?, ventotoml()?];
let tempdir = env::temp_dir().join("vento-man");
create_dir_all(tempdir.clone())?;
for page in &pages {
let tempfile = tempdir.join(&page.file);
let mut file = File::create(tempfile).unwrap();
write!(&mut file, "{}", &page.content).unwrap();
}
}
Ok(())
}
fn vento() -> Result<Page> {
let content = Manual::new("vento")
.about("a CLI inventory for your files")
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
.description("List files and directories in the currently active inventory, the files in SLOT, the files in DIRECTORY or the files in DIRECTORY in SLOT.")
.flag(
Flag::new()
.short("-c")
.long("--switch")
.help("Switches inventory slots"),
)
.flag(
Flag::new()
.short("-u")
.long("--undo")
.help("Undoes actions by a certain amount of steps"),
)
.flag(
Flag::new()
.short("-r")
.long("--redo")
.help("Redoes actions by a certain amount of steps"),
)
.flag(
Flag::new()
.short("-v")
.long("--view")
.help("Shows log of actions"),
)
.flag(
Flag::new()
.short("-m")
.long("--migrate")
.help("Migrates history file to database"),
)
.flag(
Flag::new()
.short("-e")
.long("--export-inv")
.help("Exports an inventory"),
)
.flag(
Flag::new()
.short("-E")
.long("--export-dir")
.help("Exports the Vento directory"),
)
.flag(
Flag::new()
.short("-g")
.long("--import-inv")
.help("Imports an inventory archive"),
)
.flag(
Flag::new()
.short("-G")
.long("--import-dir")
.help("Imports a Vento directory archive"),
)
.flag(
Flag::new()
.short("-i")
.long("--init")
.help("Initializes Vento with all its respective directories"),
)
.flag(
Flag::new()
.short("-h")
.long("--help")
.help("Shows the help message"),
)
.option(
Opt::new("slot")
.short("-s")
.long("--slot")
.help("The slot to list"),
)
.arg(Arg::new("[DIRECTORY]"))
.custom(
Section::new("before starting")
.paragraph("Vento will first need to initialize the respective directories before usage. Do this by running vento -i.")
)
.render();
Ok(Page {
content,
file: String::from("vento.1"),
})
}
fn take() -> Result<Page> {
let content = Manual::new("take")
.about("a file grabber for Vento")
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
.description("Take FILE and put it in the inventory.")
.option(
Opt::new("slot")
.short("-s")
.long("--slot")
.help("The slot to put the file in"),
)
.arg(Arg::new("FILE"))
.render();
Ok(Page {
content,
file: String::from("take.1"),
})
}
fn drop() -> Result<Page> {
let content = Manual::new("drop")
.about("a file dropper for Vento")
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
.description("Take FILE off the inventory and drop it in DESTINATION.")
.option(
Opt::new("slot")
.short("-s")
.long("--slot")
.help("The slot to take the file from"),
)
.arg(Arg::new("FILE"))
.arg(Arg::new("[DESTINATION]"))
.render();
Ok(Page {
content,
file: String::from("drop.1"),
})
}
fn ventotoml() -> Result<Page> {
let content = Manual::new("vento.toml")
.about("configuration file for Vento")
.author(Author::new("Lux Aliaga").email("lux@nixgoat.me"))
.description("This is the configuration file for the vento(1), take(1) and drop(1) utilities. Its presence and all its directives are optional. Directives prefixed with \"name.directive\" indicate a separate section in config file, denoted by brackets.")
.custom (
Section::new("supported directives")
.paragraph("directory = \"PATH\": Changes the path in which Vento's inventories are saved in.")
.paragraph("display_emoji = (true | false): Sets whether emojis will be prefixed on messages or not.")
.paragraph("display_colors = (true | false): Sets whether messages will be colored.")
.paragraph("item.display_dir = (true | false): Sets whether item actions will show the paths involved in the operation.")
.paragraph("history.display_dir = (true | false): Sets whether history actions will show the paths involved in the operation.")
)
.custom (
Section::new("files")
.paragraph("Linux: $XDG_CONFIG_HOME/vento.toml")
.paragraph("macOS: $HOME/Library/Application Support/vento.toml")
.paragraph("Windows: {FOLDERID_RoamingAppData}\\\\vento.toml")
)
.render();
Ok(Page {
content,
file: String::from("vento.toml.1"),
})
}

133
src/archive.rs Normal file
View file

@ -0,0 +1,133 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2023 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::{
common,
message::{append_emoji, EmojiType},
};
use anyhow::Result;
use colored::Colorize;
use std::{fs::File, path::PathBuf};
use tar::Archive;
use xz2::read::XzDecoder;
use xz2::write::XzEncoder;
/// Exports an inventory slot into an xz tarball
pub fn export_inv(slot: &str, output: PathBuf, message: bool) -> Result<()> {
let slotdir: PathBuf = match slot {
"active" | "a" => common::env_config()?.active_dir,
"inactive" | "i" => common::env_config()?.inactive_dir,
_ => PathBuf::new(),
};
let archive = File::create(&output)?;
let enc = XzEncoder::new(archive, 9);
let mut tar = tar::Builder::new(enc);
tar.append_dir_all("", slotdir)?;
if message {
println!(
"{}{} {} {} {}",
append_emoji(EmojiType::Success)?,
"Exported".green(),
match slot {
"a" | "active" => "active".green(),
"i" | "inactive" => "inactive".blue(),
_ => slot.red(),
}
.bold(),
"slot into".green(),
&output.to_str().unwrap()
);
};
Ok(())
}
/// Exports the Vento directory into an xz tarball
pub fn export_dir(output: PathBuf, message: bool) -> Result<()> {
let dir: PathBuf = common::env_config()?.vento_dir;
let archive = File::create(&output)?;
let enc = XzEncoder::new(archive, 9);
let mut tar = tar::Builder::new(enc);
tar.append_dir_all("", dir)?;
if message {
println!(
"{}{} {}",
append_emoji(EmojiType::Success)?,
"Exported Vento directory into".green(),
&output.to_str().unwrap()
);
};
Ok(())
}
/// Imports an xz tarball into an inventory slot
pub fn import_inv(input: PathBuf, slot: &str, message: bool) -> Result<()> {
let slotdir: PathBuf = match slot {
"active" | "a" => common::env_config()?.active_dir,
"inactive" | "i" => common::env_config()?.inactive_dir,
_ => PathBuf::new(),
};
let tar_xz = File::open(&input)?;
let tar = XzDecoder::new(tar_xz);
let mut archive = Archive::new(tar);
archive.unpack(&slotdir)?;
if message {
println!(
"{}{} {} {} {} {}",
append_emoji(EmojiType::Success)?,
"Imported".green(),
&input.to_str().unwrap(),
"into".green(),
match slot {
"a" | "active" => "active".green(),
"i" | "inactive" => "inactive".blue(),
_ => slot.red(),
}
.bold(),
"slot".green()
);
};
Ok(())
}
/// Imports an xz tarball into the Vento directory
pub fn import_dir(input: PathBuf, message: bool) -> Result<()> {
let dir: PathBuf = common::env_config()?.vento_dir;
let tar_xz = File::open(&input)?;
let tar = XzDecoder::new(tar_xz);
let mut archive = Archive::new(tar);
archive.unpack(&dir)?;
if message {
println!(
"{}{} {} {}",
append_emoji(EmojiType::Success)?,
"Imported".green(),
&input.to_str().unwrap(),
"into Vento directory".green(),
);
};
Ok(())
}

50
src/bin/drop.rs Normal file
View file

@ -0,0 +1,50 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use vento::{common::get_current_dir, item};
#[derive(Parser)]
#[command(name = "Drop")]
#[command(about = "A file dropper for Vento", long_about = None)]
#[command(author, version)]
struct Cli {
/// Pick a slot to drop the file from
#[arg(short, long)]
slot: Option<String>,
/// File to drop from inventory
file: String,
/// Location to drop file onto
output: Option<PathBuf>,
}
fn main() -> Result<()> {
// Handles args in Drop
let cli = Cli::parse();
let unwrapped_slot = cli.slot.clone().unwrap_or(String::from("active"));
let slot = unwrapped_slot.as_str();
let out = cli.output.unwrap_or(get_current_dir()?);
item::drop(&cli.file, slot, out, true, cli.slot.is_some(), true)?;
Ok(())
}

45
src/bin/take.rs Normal file
View file

@ -0,0 +1,45 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use anyhow::Result;
use clap::Parser;
use vento::{common::override_color, item};
#[derive(Parser)]
#[command(name = "Take")]
#[command(about = "A file grabber for Vento", long_about = None)]
#[command(author, version)]
struct Cli {
/// Pick a slot to take the file into
#[arg(short, long)]
slot: Option<String>,
/// File to take
file: String,
}
fn main() -> Result<()> {
// Handles args in Vento
override_color()?;
let cli = Cli::parse();
let slot = cli.slot.clone().unwrap_or(String::from("active"));
item::take(&cli.file, &slot, true, cli.slot.is_some(), true)?;
Ok(())
}

149
src/bin/vento.rs Normal file
View file

@ -0,0 +1,149 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use vento::{
archive,
common::override_color,
history, inv,
message::{throw_error, ErrorType},
};
#[derive(Parser)]
#[command(name = "Vento")]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Pick slot to list
#[arg(short, long)]
slot: Option<String>,
/// Switch slots
#[arg(short = 'c', long)]
switch: bool,
/// Undo actions by a certain amount of steps
#[arg(short, long, value_name="STEPS", default_missing_value = "1", num_args = ..=1)]
undo: Option<usize>,
/// Redo actions by a certain amount of steps
#[arg(short, long, value_name="STEPS", default_missing_value = "1", num_args = ..=1)]
redo: Option<usize>,
/// View log of actions
#[arg(short = 'v', long, value_name="LENGTH", default_missing_value = "2", num_args = ..=1)]
view: Option<isize>,
/// Migrate history file to database
#[arg(short, long)]
migrate: bool,
/// Export an inventory
#[arg(short, long, value_names = &["SLOT", "ARCHIVE"], num_args = ..=2)]
export_inv: Option<Vec<String>>,
/// Export the Vento directory
#[arg(short = 'E', long, default_missing_value = "vento.tar.xz", value_name = "ARCHIVE", num_args = ..=1)]
export_dir: Option<PathBuf>,
/// Import an inventory archive
#[arg(short = 'g', long, num_args = 1..=2, value_names = &["ARCHIVE", "SLOT"])]
import_inv: Option<Vec<String>>,
/// Import a Vento directory archive
#[arg(short = 'G', long, value_name = "ARCHIVE")]
import_dir: Option<PathBuf>,
/// Initialize Vento
#[arg(short, long)]
init: bool,
directory: Option<String>,
}
fn main() -> Result<()> {
override_color()?;
let cli = Cli::parse();
let unwrapped_dir = cli.directory.unwrap_or(String::new());
let dir = unwrapped_dir.as_str();
if cli.switch {
inv::switch(true, true)?
} else if cli.init {
inv::init()?
} else if cli.undo.is_some() {
history::undo(cli.undo.unwrap_or(1))?
} else if cli.redo.is_some() {
history::redo(cli.redo.unwrap_or(1))?
} else if cli.view.is_some() {
history::view(cli.view.unwrap_or(2))?
} else if cli.migrate {
history::migrate()?;
} else if cli.export_inv.is_some() {
let unwrapped_export_inv = cli.export_inv.unwrap();
let export_inv_values = match unwrapped_export_inv.len() {
0 => vec![String::from("active"), String::from("active.tar.xz")],
_ => unwrapped_export_inv,
};
archive::export_inv(
match export_inv_values[0].as_str() {
"active" | "inactive" | "a" | "i" => export_inv_values[0].as_str(),
_ => "active",
},
PathBuf::from(match export_inv_values[0].as_str() {
"active" | "inactive" | "a" | "i" => export_inv_values[1].as_str(),
_ => export_inv_values[0].as_str(),
}),
true,
)?
} else if cli.export_dir.is_some() {
archive::export_dir(cli.export_dir.unwrap(), true)?
} else if cli.import_inv.is_some() {
let import_inv_values = &cli
.import_inv
.unwrap_or(vec![String::new(), String::from("active")]);
match import_inv_values[0].as_str() {
"" | "active" | "inactive" | "a" | "i" => throw_error(ErrorType::SpecifyFile)?,
_ => archive::import_inv(
PathBuf::from(&import_inv_values[0]),
match import_inv_values.len() {
2 => match import_inv_values[1].as_str() {
"active" | "inactive" | "a" | "i" => import_inv_values[1].as_str(),
_ => "active",
},
_ => "active",
},
true,
)?,
};
} else if cli.import_dir.is_some() {
archive::import_dir(cli.import_dir.unwrap(), true)?
} else {
inv::list(
cli.slot.clone().unwrap_or(String::from("active")).as_str(),
dir,
cli.slot.is_some(),
)?
}
Ok(())
}

View file

@ -17,20 +17,199 @@
*
*/
use std::process;
use crate::message::{throw_error, ErrorType};
use anyhow::Result;
use colored::control::set_override;
use config::Config;
use rusqlite::Connection;
use serde::Deserialize;
use std::env::current_dir;
use std::path::{Path, PathBuf};
use colored::Colorize;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub fn env_config() -> PathBuf { // Configures the directory for Vento
let emptypath = PathBuf::new();
pub struct Settings {
pub vento_dir: PathBuf,
pub active_dir: PathBuf,
pub inactive_dir: PathBuf,
}
#[derive(Debug)]
pub struct HistoryData {
pub id: i32,
pub path: Option<PathBuf>,
pub file: Option<String>,
pub slot: Option<String>,
pub action: Action,
pub time: i64,
pub current: i32,
}
pub struct DeserializedConfig {
pub directory: String,
pub display_dir: bool,
pub history_display_dir: bool,
pub display_emoji: bool,
pub display_colors: bool,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Item {
display_dir: bool,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct History {
display_dir: bool,
}
#[derive(Debug)]
pub enum Action {
Take,
Drop,
Switch,
}
/// Provides required variables for Vento
pub fn env_config() -> Result<Settings> {
let home = match dirs::home_dir() {
Option::Some(dir) => dir,
_ => PathBuf::new()
_ => PathBuf::new(),
};
if home == emptypath {
println!("{}", format!("Vento was unable to detect your home folder. Have you configured your environment correctly?").red());
process::exit(1);
if home == PathBuf::new() {
throw_error(ErrorType::NoHomeDirectory)?;
};
let custom_dir = Path::new(&parse_config()?.directory).to_path_buf();
let vento_dir: PathBuf = if custom_dir != PathBuf::new() {
Path::new(&custom_dir).to_path_buf()
} else {
return [home, Path::new(".vento").to_path_buf()].iter().collect();
[home, Path::new(".vento").to_path_buf()].iter().collect()
};
}
let active_dir = [&vento_dir, &Path::new("active").to_path_buf()]
.iter()
.collect();
let inactive_dir = [&vento_dir, &Path::new("inactive").to_path_buf()]
.iter()
.collect();
Ok(Settings {
vento_dir,
active_dir,
inactive_dir,
})
}
/// Handles reading the config file or variables for Vento.
pub fn parse_config() -> Result<DeserializedConfig> {
let mut directory = String::new();
let mut display_dir = true;
let mut history_display_dir = true;
let mut display_emoji = true;
let mut display_colors = true;
let mut config = match dirs::config_dir() {
Option::Some(dir) => dir,
_ => PathBuf::new(),
};
if config != PathBuf::new() {
config.push("vento.toml");
if config.is_file() {
let settings = Config::builder()
.add_source(config::File::with_name(
&config.as_path().display().to_string(),
))
.add_source(config::Environment::with_prefix("VENTO"))
.build()?;
directory = match settings.get_string("directory") {
Ok(value) => value,
Err(_) => String::new(),
};
display_dir = settings.get_bool("item.display_dir").unwrap_or(true);
history_display_dir = settings.get_bool("history.display_dir").unwrap_or(true);
display_emoji = settings.get_bool("display_emoji").unwrap_or(true);
display_colors = settings.get_bool("display_colors").unwrap_or(true);
}
};
Ok(DeserializedConfig {
directory,
display_dir,
history_display_dir,
display_emoji,
display_colors,
})
}
/// Writes an action into the history database
pub fn history(data: HistoryData) -> Result<()> {
let mut path = env_config()?.vento_dir;
path.push("history.db3");
let db = Connection::open(path)?;
// Create table if it doesn't exist.
db.execute(
"CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY,
path TEXT,
file TEXT,
slot TEXT,
action TEXT NOT NULL,
time INTEGER NOT NULL,
current INTEGER NOT NULL)",
(),
)?;
// Remove future actions
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
let actions = current.query_map([], |row| row.get(0))?;
let lastaction: i64 = actions.last().unwrap_or(Ok(0))?;
db.execute("DELETE FROM history WHERE id > ?1", [lastaction])?;
// Unset current actions
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
// Insert action into table
db.execute(
"INSERT INTO history (path, file, slot, action, time, current) VALUES (?1, ?2, ?3, ?4, ?5, 1)",
(
data.path.unwrap_or_default().to_str(),
data.file,
data.slot,
match data.action {
Action::Take => "take",
Action::Drop => "drop",
Action::Switch => "switch",
},
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::new(0, 0)).as_secs(),
),
)?;
Ok(())
}
/// Gets current directory for commands
pub fn get_current_dir() -> Result<PathBuf> {
let currentdir = match current_dir() {
Ok(dir) => dir,
Err(_) => PathBuf::new(),
};
if currentdir == PathBuf::new() {
throw_error(ErrorType::NoCurrentDirectory)?;
}
Ok(currentdir)
}
/// Sets color override if display_colors is disabled
pub fn override_color() -> Result<()> {
if !parse_config()?.display_colors {
set_override(false)
}
Ok(())
}

646
src/history.rs Normal file
View file

@ -0,0 +1,646 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::{
common::{self, env_config, parse_config, Action, HistoryData},
inv, item,
message::{append_emoji, throw_error, EmojiType, ErrorType},
};
use anyhow::Result;
use chrono::prelude::*;
use colored::Colorize;
use rusqlite::Connection;
use std::{
fs,
path::{Path, PathBuf},
};
/// Undoes actions made by Vento using the history database located on the Vento directory
pub fn undo(steps: usize) -> Result<()> {
let path: PathBuf = [
env_config()?.vento_dir,
Path::new("history.db3").to_path_buf(),
]
.iter()
.collect();
let db = Connection::open(path)?;
// Determine if step amount is greater than the position of the action
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
let actions = current.query_map([], |row| row.get(0))?;
let last_action: usize = actions.last().unwrap_or(Ok(0))?;
if last_action <= steps {
throw_error(ErrorType::InvalidStepsLength)?;
}
let final_dest = last_action - steps;
// Calculates how many actions need to be undone
let mut undo_queue_transaction = db.prepare(
"SELECT id, path, file, slot, action FROM history WHERE id > ?2 AND id <= ?1 ORDER BY id DESC",
)?;
let undo_queue = undo_queue_transaction.query_map([last_action, final_dest], |row| {
Ok(HistoryData {
id: row.get(0)?,
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
file: row.get(2)?,
slot: row.get(3)?,
action: match row.get::<_, String>(4)?.as_str() {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: 0,
current: 0,
})
})?;
// Undoes actions for each step
for raw_step in undo_queue {
let step = raw_step?;
match step.action {
Action::Take => {
item::drop(
&step.file.unwrap(),
&step.slot.unwrap(),
step.path.unwrap(),
false,
false,
false,
)?;
}
Action::Drop => {
let path: String = [
String::from(step.path.unwrap().to_str().unwrap()),
step.file.unwrap(),
]
.join("/");
item::take(&path, step.slot.unwrap().as_str(), false, false, false)?;
}
Action::Switch => inv::switch(false, false)?,
}
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
db.execute(
"UPDATE history SET current = 1 WHERE id = ?1",
[step.id - 1],
)?;
}
// Prepares to display details of the final position
let mut final_transaction = db.prepare("SELECT * FROM history WHERE current = 1")?;
let final_action_iter = final_transaction.query_map([], |row| {
Ok(HistoryData {
id: row.get(0)?,
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
file: row.get(2)?,
slot: row.get(3)?,
action: match row.get::<_, String>(4)?.as_str() {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: row.get(5)?,
current: row.get::<_, i32>(5)?,
})
})?;
let final_action = final_action_iter.last().unwrap()?;
// Formats the current action's timestamp to readable, local time
let timestamp = final_action.time;
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
let newdate = datetime.format("%Y-%m-%d, %H:%M:%S");
println!(
"{}{}{}{}{}{}",
append_emoji(EmojiType::Success)?,
"Rolled back to ".green(),
match final_action.action {
Action::Take => "Take",
Action::Drop => "Drop",
Action::Switch => "Switch",
}
.bold(),
" action, on ".green(),
newdate,
match final_action.action {
Action::Take => format!(
"{}{}{}{}{}{}{}",
" (".green(),
final_action.file.unwrap().bold(),
", ".green(),
match parse_config()?.history_display_dir {
true => format!(
"{} {} ",
"from".green(),
final_action.path.unwrap().to_str().unwrap(),
),
_ => String::new(),
},
"to ".green(),
match final_action.slot.clone().unwrap().as_str() {
"active" => final_action.slot.unwrap().green(),
"inactive" => final_action.slot.unwrap().blue(),
_ => final_action.slot.unwrap().red(),
}
.bold(),
" slot)".green(),
),
Action::Drop => format!(
"{}{}{}{}{}{}{}",
" (".green(),
final_action.file.unwrap().bold(),
", from ".green(),
match final_action.slot.clone().unwrap().as_str() {
"active" => final_action.slot.unwrap().green(),
"inactive" => final_action.slot.unwrap().blue(),
_ => final_action.slot.unwrap().red(),
}
.bold(),
" slot".green(),
match parse_config()?.history_display_dir {
true => format!(
" {} {}",
"to".green(),
final_action.path.unwrap().to_str().unwrap(),
),
false => String::new(),
},
")".green(),
),
_ => String::from(""),
}
);
Ok(())
}
/// Redoes actions made by Vento using the history database located on the Vento directory
pub fn redo(steps: usize) -> Result<()> {
let path: PathBuf = [
env_config()?.vento_dir,
Path::new("history.db3").to_path_buf(),
]
.iter()
.collect();
let db = Connection::open(path)?;
// Determine if step amount is greater than the position of the action
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
let actions = current.query_map([], |row| row.get(0))?;
let last_action: usize = actions.last().unwrap_or(Ok(0))?;
// Determine table size
let mut size_transaction = db.prepare("SELECT id FROM history")?;
let size_actions = size_transaction.query_map([], |row| row.get(0))?;
let size: usize = size_actions.last().unwrap_or(Ok(0))?;
if size - last_action < steps {
throw_error(ErrorType::InvalidStepsLength)?;
}
let final_dest = last_action + steps;
// Calculates how many actions need to be redone
let mut redo_queue_transaction = db.prepare(
"SELECT id, path, file, slot, action FROM history WHERE id > ?1 AND id <= ?2 ORDER BY id ASC",
)?;
let redo_queue = redo_queue_transaction.query_map([last_action, final_dest], |row| {
Ok(HistoryData {
id: row.get(0)?,
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
file: row.get(2)?,
slot: row.get(3)?,
action: match row.get::<_, String>(4)?.as_str() {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: 0,
current: 0,
})
})?;
// Redoes actions for each step
for raw_step in redo_queue {
let step = raw_step?;
match step.action {
Action::Take => {
let path: String = [
String::from(step.path.unwrap().to_str().unwrap()),
step.file.unwrap(),
]
.join("/");
item::take(&path, step.slot.unwrap().as_str(), false, false, false)?;
}
Action::Drop => {
item::drop(
&step.file.unwrap(),
&step.slot.unwrap(),
step.path.unwrap(),
false,
false,
false,
)?;
}
Action::Switch => inv::switch(false, false)?,
}
db.execute("UPDATE history SET current = 0 WHERE current = 1", ())?;
db.execute("UPDATE history SET current = 1 WHERE id = ?1", [step.id])?;
}
// Prepares to display details of the final position
let mut final_transaction = db.prepare("SELECT * FROM history WHERE current = 1")?;
let final_action_iter = final_transaction.query_map([], |row| {
Ok(HistoryData {
id: row.get(0)?,
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
file: row.get(2)?,
slot: row.get(3)?,
action: match row.get::<_, String>(4)?.as_str() {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: row.get(5)?,
current: row.get::<_, i32>(5)?,
})
})?;
let final_action = final_action_iter.last().unwrap()?;
// Formats the current action's timestamp to readable, local time
let timestamp = final_action.time;
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
let newdate = datetime.format("%Y-%m-%d, %H:%M:%S");
// Prints transaction result
println!(
"{}{}{}{}{}{}",
append_emoji(EmojiType::Success)?,
"Returned to ".green(),
match final_action.action {
Action::Take => "Take",
Action::Drop => "Drop",
Action::Switch => "Switch",
}
.bold(),
" action, on ".green(),
newdate,
match final_action.action {
Action::Take => format!(
"{}{}{}{}{}{}{}",
" (".green(),
final_action.file.unwrap().bold(),
", ".green(),
match parse_config()?.history_display_dir {
true => format!(
"{} {} ",
"from".green(),
final_action.path.unwrap().to_str().unwrap(),
),
_ => String::new(),
},
"to ".green(),
match final_action.slot.clone().unwrap().as_str() {
"active" => final_action.slot.unwrap().green(),
"inactive" => final_action.slot.unwrap().blue(),
_ => final_action.slot.unwrap().red(),
}
.bold(),
" slot)".green(),
),
Action::Drop => format!(
"{}{}{}{}{}{}{}",
" (".green(),
final_action.file.unwrap().bold(),
", from ".green(),
match final_action.slot.clone().unwrap().as_str() {
"active" => final_action.slot.unwrap().green(),
"inactive" => final_action.slot.unwrap().blue(),
_ => final_action.slot.unwrap().red(),
}
.bold(),
" slot".green(),
match parse_config()?.history_display_dir {
true => format!(
" {} {}",
"to".green(),
final_action.path.unwrap().to_str().unwrap(),
),
false => String::new(),
},
")".green(),
),
_ => String::from(""),
}
);
Ok(())
}
/// Displays n actions before and after the current action
pub fn view(length: isize) -> Result<()> {
let path: PathBuf = [
env_config()?.vento_dir,
Path::new("history.db3").to_path_buf(),
]
.iter()
.collect();
let db = Connection::open(path)?;
// Determine table size
let mut size_transaction = db.prepare("SELECT id FROM history")?;
let size_actions = size_transaction.query_map([], |row| row.get(0))?;
let size: isize = size_actions.last().unwrap_or(Ok(0))?;
let (x, _) = termion::terminal_size().unwrap();
// If there's no history, don't print the table
if size == 0 {
println!(
"{}{}",
append_emoji(EmojiType::Success)?,
"No data to show".green()
);
}
// Find last action
let mut current = db.prepare("SELECT id FROM history WHERE current = 1")?;
let actions = current.query_map([], |row| row.get(0))?;
let last_action: isize = actions.last().unwrap_or(Ok(0))?;
let mut forward: isize = last_action + length;
let mut backward: isize = last_action - length;
let total_range: isize = length * 2;
// Changes ranges in case they exceed the table margins
if forward >= size {
forward = size;
backward = size - total_range;
} else if backward < 1 {
backward = 1;
forward = total_range + 1;
}
// Read from table
let mut history_transaction =
db.prepare("SELECT * FROM history WHERE id >= ?1 AND id <= ?2")?;
let history = history_transaction.query_map([backward, forward], |row| {
Ok(HistoryData {
id: row.get(0)?,
path: Some(PathBuf::from(row.get::<_, String>(1)?)),
file: row.get(2)?,
slot: row.get(3)?,
action: match row.get::<_, String>(4)?.as_str() {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: row.get(5)?,
current: row.get(6)?,
})
})?;
// Terminal needs to be at least 83 columns wide
if x < 83 {
throw_error(ErrorType::SmallTerminal)?;
}
let mut space_left: usize = (x - 83).into();
// Append separators to ID
let mut id_separators = String::new();
if size.to_string().len() > 2 {
for _ in 0..size.to_string().len() - 2 {
id_separators.insert(id_separators.len(), '-')
}
space_left = space_left - size.to_string().len() + 2;
}
// Append separators to path column
let mut path_separators = String::new();
let mut file_separators = String::new();
// Calculate spaces left to add padding to the path and file separators
space_left /= 3;
for _ in 0..space_left * 2 {
path_separators.insert(path_separators.len(), '-')
}
for _ in 0..space_left {
file_separators.insert(file_separators.len(), '-')
}
let separator = format!(
"+----{}+---------------------+--------+------------------{}+----------{}+----------+---+",
id_separators, path_separators, file_separators
);
// Render the first column names
println!("{}", separator);
print!("| ");
if size.to_string().len() > 2 {
for _ in 0..size.to_string().len() - 2 {
print!(" ")
}
}
print!("ID | Date | Action | Path ");
for _ in 0..space_left * 2 {
print!(" ")
}
print!("| File ");
for _ in 0..space_left {
print!(" ")
}
println!("| Slot | C |\n{}", separator);
// Print the rows
for raw_step in history {
let step = raw_step?;
// Format timestamp on row
let timestamp = step.time;
let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0);
let datetime = TimeZone::from_utc_datetime(&Local, &naive.unwrap());
let fdate = datetime.format("%Y-%m-%d %H:%M:%S");
// Add spacing for ID column
let mut id_pad = String::new();
let id = step.id.to_string().len();
if size.to_string().len() >= 2 {
let id_pad_len = size.to_string().len();
for x in 0..id_pad_len - id {
id_pad.insert(x, ' ');
}
} else {
id_pad.insert(0, ' ');
}
// Add spacing to fit inside the file column
let file_len = match &step.file {
Some(x) => x.len(),
None => 0,
};
let mut file_pad = String::new();
let mut file = step.file.unwrap_or(String::from(""));
let file_column_len;
if file_len > space_left + 8 {
file_column_len = 0;
let mut reversed: String = file.chars().rev().collect();
for _ in 0..file_len - space_left - 5 {
reversed.pop();
}
file = reversed.chars().rev().collect();
for x in 0..3 {
file.insert(x, '.');
}
} else {
file_column_len = space_left + 8 - file_len
}
for x in 0..file_column_len {
file_pad.insert(x, ' ');
}
// Add spacing to fit inside the path column
let mut path_pad = String::new();
let mut path = step
.path
.unwrap_or(PathBuf::new())
.to_string_lossy()
.to_string();
let path_len = path.len();
let path_column_len;
if path_len > space_left * 2 + 16 {
path_column_len = 0;
let mut reversed: String = path.chars().rev().collect();
for _ in 0..path_len - space_left * 2 - 13 {
reversed.pop();
}
path = reversed.chars().rev().collect();
for x in 0..3 {
path.insert(x, '.');
}
} else {
path_column_len = space_left * 2 + 16 - path_len;
}
for _ in 0..path_column_len {
path_pad.insert(path_pad.len(), ' ');
}
// Add spacing on slot column
let mut slot = step.slot.unwrap_or(String::from(""));
if slot == "active" {
slot = String::from("active ");
} else if slot == "inactive" {
slot = String::from("inactive");
} else {
slot = String::from(" ")
}
println!(
"| {}{} | {} | {} | {}{} | {}{} | {} | {} |",
id_pad,
step.id,
fdate,
match step.action {
Action::Take => "Take ",
Action::Drop => "Drop ",
Action::Switch => "Switch",
},
path,
path_pad,
file,
file_pad,
slot,
match step.current {
0 => " ",
1 => "*",
_ => " ",
}
);
}
println!("{}", separator);
Ok(())
}
/// Migrate old "last" file into the history database
pub fn migrate() -> Result<()> {
// Get last file from previous location
let last_path: PathBuf = [env_config()?.vento_dir, Path::new("last").to_path_buf()]
.iter()
.collect();
if !last_path.is_file() {
throw_error(ErrorType::NoFileOrDir)?;
}
let last_file = fs::read_to_string(&last_path)?;
let mut contents = vec![];
for line in last_file.lines() {
contents.push(line);
}
if contents.len() != 4 {
throw_error(ErrorType::InvalidHistoryLength)?;
}
// Write contents of file into history database
common::history(HistoryData {
id: 0,
path: Some(Path::new(contents[0]).to_path_buf()),
file: Some(String::from(contents[1])),
slot: Some(String::from(contents[2])),
action: match contents[3] {
"take" => Action::Take,
"drop" => Action::Drop,
"switch" => Action::Switch,
_ => unreachable!(),
},
time: 0,
current: 1,
})?;
fs::remove_file(last_path)?;
println!(
"{}{}",
append_emoji(EmojiType::Success)?,
"Migrated history file to database".green()
);
Ok(())
}

View file

@ -17,68 +17,215 @@
*
*/
use std::{fs, process};
use std::path::{Path, PathBuf};
use std::io::{self, Write};
use super::{
common,
message::{append_emoji, throw_error, EmojiType, ErrorType},
};
use anyhow::{bail, Context, Result};
use colored::Colorize;
use super::common;
use size_format::SizeFormatterBinary;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::{fs, process};
pub fn init() { // Initializes Vento
let ventodir: PathBuf = common::env_config();
if ventodir.is_dir() { // Checks if Vento has already been initialized and prompts the user if they want to initialize it again
/// Initializes Vento by creating the respective directories it will use
pub fn init() -> Result<()> {
let ventodir = &common::env_config()?.vento_dir;
if ventodir.is_dir() {
// Checks if Vento has already been initialized and prompts the user if they want to initialize it again
let mut answer = String::new();
print!("⚠️ {} {}", format!("WARNING:").bold().red(), "Vento has already been initialized. Reinitializing will delete all files on the directory for Vento. Do you wish to proceed? (y/N) ");
print!("{}{} Vento has already been initialized. Reinitializing will delete all files on the directory for Vento. Do you wish to proceed? (y/N) ", append_emoji(EmojiType::Warning)?, "WARNING:".bold().red());
let _ = io::stdout().flush();
io::stdin()
.read_line(&mut answer)
.expect("❌ Failed to read input");
io::stdin().read_line(&mut answer)?;
match answer.as_str().trim() {
"y" | "Y" => {fs::remove_dir_all(&ventodir).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");},
"n" | "N" | _ => process::exit(0)
"y" | "Y" => fs::remove_dir_all(ventodir)?,
_ => process::exit(0),
};
};
create_slots(ventodir);
create_slots()?;
Ok(())
}
pub fn list(slot: &str) { // Lists files in inventory
let ventodir: PathBuf = common::env_config();
let slotdir: PathBuf = [ventodir.to_path_buf(), Path::new(slot).to_path_buf()].iter().collect();
/// Lists files in the provided slot and/or directory
pub fn list(slot: &str, dir: &str, display_slot: bool) -> Result<()> {
let ventodir = &common::env_config()?.vento_dir;
if slotdir.is_dir() { // Checks if inventory selected exists
println!("🗃️ {}", format!("Files in {} inventory:", match slot {
"active" => format!("{}", slot).bold(),
"inactive" | _ => format!("{}", slot).blue().bold()
}).green());
for file in fs::read_dir(&slotdir).unwrap() {
println!(" - {}", file.unwrap().path().file_name().unwrap().to_os_string().into_string().unwrap());
};
} else {
println!("{}", format!("Vento was unable to read that slot. Valid slots are {} and {}.", format!("active").green(), format!("inactive").blue()).red());
if !ventodir.is_dir() {
// Detects if Vento hasn't been initialized and bails if so
throw_error(ErrorType::NotInitialized)?;
}
let mut slotdir: PathBuf = match slot {
"active" | "a" => common::env_config()?.active_dir,
"inactive" | "i" => common::env_config()?.inactive_dir,
_ => PathBuf::new(),
};
if !dir.is_empty() {
// Detects if the directory argument is not empty, and if so appends the path provided to the slot directory variable
slotdir = [&slotdir, &Path::new(dir).to_path_buf()].iter().collect();
}
if dir.to_string().contains("..") {
// Basically preventing from listing anything out of bounds. ls and dir exist for that
throw_error(ErrorType::NoAccessParent)?;
}
if !slotdir.is_dir() {
// Detects if the consulted slot or directory exists
bail!(
"{}",
format!(
"No such slot or directory. Valid slots are {} and {}",
"active".green().bold(),
"inactive".blue().bold()
)
.red()
);
};
if fs::read_dir(&slotdir).unwrap().count() == 0 {
// Detects if the slot or directory has any contents
println!(
"{}{}",
append_emoji(EmojiType::Inventory)?,
format!(
"No files in {}{}",
if display_slot || !dir.is_empty() {
match slot {
"active" => slot.bold(),
_ => slot.blue().bold(),
}
} else {
"inventory".clear()
},
if !dir.is_empty() {
if cfg!(windows) {
format!("\\{}", dir)
} else {
format!("/{}", dir)
}
} else {
"".to_string()
}
)
.green()
);
} else {
println!(
"{}{}",
append_emoji(EmojiType::Inventory)?,
format!(
"Files in{}{} ({}):",
if display_slot || !dir.is_empty() {
format!(
" {}",
match slot {
"active" => slot.bold(),
_ => slot.blue().bold(),
},
)
} else {
String::new()
},
if !dir.is_empty() {
if cfg!(windows) {
format!("\\{}", dir)
} else {
format!("/{}", dir)
}
} else {
" inventory".to_string()
},
format!("{}", fs::read_dir(&slotdir).unwrap().count())
.white()
.bold()
)
.green()
);
for file in fs::read_dir(&slotdir).unwrap() {
let file = file.unwrap().path();
println!(
" - [{}] {}{}",
if file.clone().is_dir() {
"D".blue()
} else if file.clone().is_symlink() {
"S".yellow()
} else {
"F".green()
},
file.clone()
.file_name()
.unwrap()
.to_os_string()
.into_string()
.unwrap(),
if file.clone().is_file() {
format!(
" ({}B)",
SizeFormatterBinary::new(file.clone().metadata().unwrap().len())
)
} else {
String::new()
}
);
}
}
Ok(())
}
pub fn switch() { // Switches between inventory slots
let ventodir: PathBuf = common::env_config();
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
let temp: PathBuf = [ventodir.to_path_buf(), Path::new("temp").to_path_buf()].iter().collect();
let inactive: PathBuf = [ventodir.to_path_buf(), Path::new("inactive").to_path_buf()].iter().collect();
fs::rename(&active, &temp).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
fs::rename(&inactive, &active).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
fs::rename(&temp, &inactive).expect("❌ Vento was unable to switch slots. Try running vento init and try again");
/// Switches inevntory slots between each other, making the currently active inventory inactive and viceversa
pub fn switch(message: bool, save_history: bool) -> Result<()> {
let ventodir = &common::env_config()?.vento_dir;
let active = &common::env_config()?.active_dir;
let inactive = &common::env_config()?.inactive_dir;
let temp: PathBuf = [ventodir.to_path_buf(), Path::new("temp").to_path_buf()]
.iter()
.collect();
println!("🎉 {}", format!("Switched inventory slots!").green());
let rename_error = "Vento was unable to switch slots. Try running \"vento -i\" and try again";
fs::rename(active, &temp).context(rename_error)?;
fs::rename(inactive, active).context(rename_error)?;
fs::rename(&temp, inactive).context(rename_error)?;
if save_history {
common::history(common::HistoryData {
id: 0,
path: None,
file: None,
slot: None,
action: common::Action::Switch,
current: 1,
time: 0,
})?;
}
if message {
println!(
"{}{}",
append_emoji(EmojiType::Success)?,
"Switched inventory slots!".green()
);
}
Ok(())
}
// Used only on init. Creates all required directories
fn create_slots() -> Result<()> {
let active = &common::env_config()?.active_dir;
let inactive = &common::env_config()?.inactive_dir;
fn create_slots(dir: PathBuf) { // Used only on init. Creates all required directories.
let active: PathBuf = [dir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
let inactive: PathBuf = [dir.to_path_buf(), Path::new("inactive").to_path_buf()].iter().collect();
fs::create_dir_all(active)?;
fs::create_dir_all(inactive)?;
fs::create_dir_all(active).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");
fs::create_dir_all(inactive).expect("❌ Vento was unable to initalize. Do you have the correct permissions?");
println!("🎉 {}", format!("Vento has been succesfully initialized!").green());
println!(
"{}{}",
append_emoji(EmojiType::Celebrate)?,
"Vento has been succesfully initialized!".green()
);
Ok(())
}

View file

@ -17,50 +17,222 @@
*
*/
use std::fs;
use fs_extra::dir::{CopyOptions, move_dir};
use std::path::{Path, PathBuf};
use super::{
common::{env_config, history, parse_config, Action, HistoryData},
message::{append_emoji, throw_error, EmojiType, ErrorType},
};
use anyhow::{bail, Result};
use colored::Colorize;
use super::common;
use fs_extra::dir::{move_dir, CopyOptions};
use std::fs;
use std::path::{Path, PathBuf};
/// Takes a file or directory and stores it in an inventory slot
pub fn take(
file: &String,
slot: &str,
message: bool,
display_slot: bool,
save_history: bool,
) -> Result<()> {
let ventodir = &env_config()?.vento_dir;
if !ventodir.is_dir() {
// Detects if Vento hasn't been initialized and bails if so
throw_error(ErrorType::NotInitialized)?;
};
let slotdir: PathBuf = match slot {
"active" | "a" => env_config()?.active_dir,
"inactive" | "i" => env_config()?.inactive_dir,
_ => PathBuf::new(),
};
if !slotdir.is_dir() {
// Detects if the slot provided exists
bail!(
"{}",
format!(
"No such slot. Valid slots are {} and {}",
"active".green().bold(),
"inactive".blue().bold()
)
.red()
);
};
pub fn take(file: &String) { // Takes a file or directory
let ventodir = common::env_config();
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
let sourcepath: PathBuf = Path::new(&file).to_path_buf();
let destpath: PathBuf = [&active, &Path::new(file).to_path_buf()].iter().collect();
let mut sourcelocation: PathBuf = fs::canonicalize(&sourcepath)?;
sourcelocation.pop();
let filename = Path::new(&file).file_name().unwrap().to_str().unwrap();
let destpath: PathBuf = [&slotdir, &Path::new(&filename).to_path_buf()]
.iter()
.collect();
if Path::exists(&destpath) {
println!("{}", format!("A file with the same name already exists in your inventory!").red());
} else if sourcepath.is_file() | sourcepath.is_symlink() {
fs::copy(&file, &destpath).expect("❌ Vento was unable to copy the file.");
fs::remove_file(&file).expect("❌ Vento was unable to remove the file.");
// Checks if there's a file with the same name in the inventory.
throw_error(ErrorType::ExistsInventory)?;
}
if sourcepath.is_file() | sourcepath.is_symlink() {
// Checks the path's file type
fs::copy(file, &destpath)?;
fs::remove_file(file)?;
} else if sourcepath.is_dir() {
let options = CopyOptions::new();
move_dir(&file, &active, &options).expect("❌ Vento was unable to move the directory.");
move_dir(file, &slotdir, &options)?;
} else {
println!("{}", format!("No such file or directory.").red());
throw_error(ErrorType::NoFileOrDir)?;
}
if save_history {
history(HistoryData {
id: 0,
path: Some(sourcelocation.clone()),
file: Some(String::from(filename)),
slot: Some(String::from(slot)),
action: Action::Take,
current: 1,
time: 0,
})?;
}
if message {
println!(
"{}{} {}{}{}",
append_emoji(EmojiType::Success)?,
"Took".green(),
&filename.bold(),
match parse_config()?.display_dir {
true => format! {"{} {}",
" from".green(),
&sourcelocation.to_str().unwrap(),
},
_ => String::new(),
},
match display_slot {
true => format!(
"{} {} {}",
" to".green(),
match slot {
"active" => slot.green(),
"inactive" => slot.blue(),
_ => slot.red(),
}
.bold(),
"slot".green()
),
_ => String::new(),
},
);
}
Ok(())
}
pub fn drop(file: &String, dest: PathBuf) { // Drops a file or directory
let ventodir = common::env_config();
// I know I've declared the same variable multiple times through this project. I might move this to the common file and just call it from there every single time I need it, but that'll be for later.
let active: PathBuf = [ventodir.to_path_buf(), Path::new("active").to_path_buf()].iter().collect();
let sourcepath: PathBuf = [&active, &Path::new(file).to_path_buf()].iter().collect();
let destpath: PathBuf = [Path::new(&dest).to_path_buf(), Path::new(file).to_path_buf()].iter().collect();
/// Drops a file or directory and stores it in an inventory slot
pub fn drop(
file: &String,
slot: &str,
dest: PathBuf,
message: bool,
display_slot: bool,
save_history: bool,
) -> Result<()> {
// Drops a file or directory
let ventodir = &env_config()?.vento_dir;
if Path::exists(&destpath) { // HAHA YANDEREDEV MOMENT. This checks what method to use for the file/directory the user has picked
println!("{}", format!("A file with the same name already exists in the destination! Try renaming it or dropping this file somewhere else.").red());
} else if sourcepath.is_file() | sourcepath.is_symlink() {
fs::copy(&sourcepath, &destpath).expect("❌ Vento was unable to copy the file.");
fs::remove_file(&sourcepath).expect("❌ Vento was unable to remove the file.");
if !ventodir.is_dir() {
// Detects if Vento hasn't been initialized and bails if so
throw_error(ErrorType::NotInitialized)?;
};
let slotdir: PathBuf = match slot {
"active" | "a" => env_config()?.active_dir,
"inactive" | "i" => env_config()?.inactive_dir,
_ => PathBuf::new(),
};
if !slotdir.is_dir() {
// Detects if the slot provided exists
bail!(
"{}",
format!(
"No such slot. Valid slots are {} and {}",
"active".green().bold(),
"inactive".blue().bold()
)
.red()
);
};
let sourcepath: PathBuf = [&slotdir, &Path::new(file).to_path_buf()].iter().collect();
let mut destpath: PathBuf = [
Path::new(&dest).to_path_buf(),
Path::new(file).to_path_buf(),
]
.iter()
.collect();
if Path::exists(&destpath) {
// Checks if there's a file with the same name in the destination path.
throw_error(ErrorType::ExistsDestination)?;
}
if sourcepath.is_file() | sourcepath.is_symlink() {
// Checks the path's file type
fs::copy(&sourcepath, &destpath)?;
fs::remove_file(&sourcepath)?;
} else if sourcepath.is_dir() {
let destpath: PathBuf = Path::new(&dest).to_path_buf();
let options = CopyOptions::new();
move_dir(&sourcepath, &destpath, &options).expect("❌ Vento was unable to move the directory.");
move_dir(&sourcepath, destpath, &options)?;
} else {
println!("{}", format!("No such file or directory.").red());
throw_error(ErrorType::NoFileOrDir)?;
}
destpath.pop();
if save_history {
history(HistoryData {
id: 0,
path: Some(destpath.clone()),
file: Some(String::from(file)),
slot: Some(String::from(slot)),
action: Action::Drop,
current: 1,
time: 0,
})?;
}
if message {
println!(
"{}{} {}{}{}",
append_emoji(EmojiType::Success)?,
"Dropped".green(),
&file.bold(),
match display_slot {
true => format!(
"{} {} {}",
" from".green(),
match slot {
"active" => slot.green(),
"inactive" => slot.blue(),
_ => slot.red(),
}
.bold(),
"slot".green(),
),
false => String::new(),
},
match parse_config()?.display_dir {
true => format! {"{} {} ",
" into".green(),
&destpath.to_str().unwrap(),
},
_ => String::new(),
},
);
};
Ok(())
}

25
src/lib.rs Normal file
View file

@ -0,0 +1,25 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
pub mod archive;
pub mod common;
pub mod history;
pub mod inv;
pub mod item;
pub mod message;

View file

@ -1,91 +0,0 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2022 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use std::env;
use std::process;
use std::path::{Path};
use colored::Colorize;
mod inv;
mod item;
mod common;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() >= 2 { // If the vector for the arguments the command is taking is larger than 2, it most likely means the user has provided an argument
match args[1].as_str() {
"help" | "h" => help(),
"init" | "i" => inv::init(),
"list" | "l" => {
if args.len() == 3 { // If the user has provided a slot, it'll use it. Otherwise, it'll default to the active slot
inv::list(args[2].as_str());
} else {
inv::list("active");
};
},
"switch" | "s" => inv::switch(),
"take" | "t" => {
if args.len() == 3 { // Similar thing with list, but change it with a file and it will show an error instead of defaulting to anything
item::take(&args[2]);
} else {
println!("{}", format!("You need to specify a file.").red())
};
},
"drop" | "d" => {
if args.len() == 3 { // Tries to get the current directory if the user hasn't provided a "landing location"
item::drop(&args[2], match env::current_dir() {
Ok(dir) => dir,
Err(_) => {
println!("{}", format!("Vento was unable to detect your current directory. Have you configured your environment correctly?").red());
process::exit(1);
}
});
} else if args.len() == 4 {
item::drop(&args[2], Path::new(&args[3]).to_path_buf());
} else {
println!("{}", format!("You need to specify a file.").red())
};
},
_ => println!("❔ Command not found. Type \"vento help\" to see all commands available.")
}
} else { // If the user provides no commands, it'll fall back to the help guide
help();
}
}
fn help() { // A quick guide to move around in Vento
println!("{}, a CLI inventory for your files
© 2022 Lux Aliaga. Licensed under GPLv3
{}
- {}: Takes a file or directory and saves it in your inventory
- {}: Drops a file off of your inventory
- {}: Lists files in selected inventory
- {}: Switches slots
- {}: Initializes Vento
- {}: Displays this message",
format!("Vento").bold().blue(),
format!("Usage:").bold(),
format!("take | t <file | directory>").bold().green(),
format!("drop | d <file | directory> [destination]").bold().green(),
format!("list | l [slot]").bold().green(),
format!("switch | s").bold().green(),
format!("init | i").bold().green(),
format!("help | h").bold().green());
}

85
src/message.rs Normal file
View file

@ -0,0 +1,85 @@
/*
* Vento, a CLI inventory for your files.
* Copyright (C) 2023 Lux Aliaga
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use crate::common::parse_config;
use anyhow::{bail, Result};
use colored::Colorize;
pub enum ErrorType {
TooManyArgs,
SpecifySlot,
SpecifyFile,
NoCurrentDirectory,
NoHomeDirectory,
InvalidHistoryLength,
InvalidStepsLength,
SmallTerminal,
IllegalAction,
NotInitialized,
NoAccessParent,
ExistsInventory,
ExistsDestination,
NoFileOrDir,
}
pub enum EmojiType {
Celebrate,
Success,
Warning,
Inventory,
}
pub fn append_emoji(message: EmojiType) -> Result<String> {
let mut output: String = String::new();
if parse_config()?.display_emoji {
match message {
EmojiType::Celebrate => output = String::from("🎉 "),
EmojiType::Success => output = String::from(""),
EmojiType::Inventory => output = String::from("🗃️ "),
EmojiType::Warning => output = String::from("⚠️ "),
};
}
Ok(output)
}
/// Displays an error and exits
pub fn throw_error(error: ErrorType) -> Result<()> {
bail!(
"{}",
match error {
ErrorType::TooManyArgs => "Too many arguments",
ErrorType::SpecifyFile => "You need to specify a file",
ErrorType::SpecifySlot => "You need to specify a slot",
ErrorType::NoCurrentDirectory => "Vento was unable to detect your current directory. Have you configured your environment correctly?",
ErrorType::NoHomeDirectory => "Vento was unable to detect your home directory. Have you configured your environment correctly?",
ErrorType::InvalidHistoryLength => "Invalid history length",
ErrorType::InvalidStepsLength => "Invalid steps length",
ErrorType::SmallTerminal => "Your terminal needs to be at least 83 columns wide",
ErrorType::IllegalAction => "Illegal action",
ErrorType::NotInitialized => "Vento not initialized. Run \"vento -i\" to initialize Vento",
ErrorType::NoAccessParent => "Cannot access parent",
ErrorType::ExistsInventory => "A file with the same name already exists in your inventory!",
ErrorType::ExistsDestination => "A file with the same name already exists in the destination! Try renaming it or dropping this file somewhere else",
ErrorType::NoFileOrDir => "No such file or directory",
}
.red()
);
}