A TUI feed reader for RSS, Atom, and (aspirationally) Podcasts

Overview

moccasin

A TUI feed reader for RSS, Atom, and (eventually) Podcasts. VIM keybindings. Ranger-inspired interface. Configurable.

Crates.io (version) CI status

GitHub stars GitHub forks GitHub watchers Follow on GitHub

tabs TUI in action

Installation

cargo install moccasin

NetBSD

If you are on NetBSD, a pre-compiled binary is available from the official repositories. To install it, simply run:

pkgin install moccasin

Or, if you prefer to build from source:

cd /usr/pkgsrc/news/moccasin
make install

Usage

Since "moccasin" is hard to spell and has too many letters, the executable is just called mcsn.

mcsn [OPTIONS]

Options

Command line arguments will override any values set in your config file for that session.

Short Long Args Description
-c --config <PATH> Set a custom config file
-s --color-scheme <COLOR_SCHEME> Set a color scheme, either built-in or a path to a custom theme file
-i --interval <INTERVAL> Set a custom refresh rate in seconds
-t --timeout <TIMEOUT> Set a custom request timeout in seconds
-n --no-cache Do not cache feeds in local file-backed database
-h --help Print help
-V --version Print version

Config

On first boot, Moccasin will create both a database and a config file in your default config directory, which varies by platform:

Platform Value Example
Linux $HOME/.config/moccasin/ /home/alice/.config/moccasin/
macOS $HOME/Library/Application Support/com.rektsoft.moccasin/ /Users/Alice/Library/Application Support/com.rektsoft.moccasin/
Windows {FOLDERID_LocalAppData}\rektsoft\moccasin\config C:\Users\Alice\AppData\Local\rektsoft\moccasin\config

The moccasin.toml file in this directory can be edited to customize app behavior, add feeds in bulk, change the color scheme, etc. Most of these properties can be changed from within the application as well, which will write to this file. Configuration options are as follows:

moccasin.toml

Table Field Type Default Description
[sources] Table
feeds Array [] URLs of Atom/RSS feeds you wish to see in-app.
[preferences] Table
color_scheme Enum | Table "default" Either a built-in color scheme name, one of "default" | "borland" | "darcula" | "focus" | "jungle" | "matrix" | "redshift" | "wyse", or a table of values described below.
sort_feeds Enum "a-z" Order in which to list feeds, one of "a-z" | "z-a" | "newest" | "oldest" | "unread" | "custom"
cache_feeds Boolean true Whether or not to write feeds to a local database for faster startup and access. When false, the app will use an in-memory database.
refresh_interval Integer 3600 How often to refetch feeds, in seconds.
refresh_timeout Integer 5 How long to wait for each feed before aborting, in seconds.

Color Schemes

To create a custom color scheme, the color_scheme field can be declared as a table in which the keys are interface elements and the values are either a built-in ANSI color (which will inherit from your terminal emulator), a HEX color, or in InlineTable with fg and bg properties of the same type.

[preferences.color_scheme]
base = { fg = "white", bg = "#000080" }
status = { fg = "gray", bg = "#000080" }
border = "gray"
selection_active = { fg = "#000080", bg = "#fefd72" }
scrollbar = { fg = "white", bg = "gray" }

The built-in color names are

  • "white"
  • "black"
  • "red"
  • "green"
  • "yellow"
  • "blue"
  • "magenta"
  • "cyan"
  • "gray"
  • "lightred"
  • "lightgreen"
  • "lightyellow"
  • "lightblue"
  • "lightmagenta"
  • "lightcyan"
  • "lightblack" | "darkgray"

The styleable properties are all optional, inheriting sensible defaults. Available properties are as follows:

Field Default Description
base terminal default Base foreground and background colors
overlay base Modal overlays
status base The top menu bar and bottom status bar colors
selection ~base Selected list item
selection_active selection Selected list item of active panel
border border_active* Border and titles around panels
border_active base Border and title of active panel
scrollbar base Thumb (fg) and track (bg) of scrollbars

* NOTE: it is important to define border when the style it inherits (either base or border_active) is defined as a hex color, otherwise it will be difficult to know which panel is currently active.

Keybinds

The application uses VIM-style keybinds, but arrow keys can also be used for navigation. At the moment, the app has a NORMAL mode and a COMMAND mode. In future, you should also be able to tag and group feeds and items in GROUP mode.

NORMAL mode

Keys Description
j/k Focus next/previous item
h/l Focus previous/next panel
Enter Select current item
Esc Deselect current item/mode
Tab Cycle tabs
b/f/t View Browse/Favorites/Tags tab
r Refresh all feeds
o Open current feed/item in browser
: Enter COMMAND mode
, Open config file
? Show keybinds

COMMAND mode

Command Args Description
:a, :add <URL> Add a feed
:d, :delete [URL] Delete feed for URL, or current feed if not supplied. Removes this entry from config file and cache.
:s, :search <TEXT> Search for a feed, item, or text content

License

MIT © Tobias Fried

You might also like...
Terminal UI for leetcode. Lets you browse questions through different topics. View, solve, run and submit questions from TUI.
Terminal UI for leetcode. Lets you browse questions through different topics. View, solve, run and submit questions from TUI.

Leetcode TUI Use Leetcode in your terminal. Why this TUI: My motivation for creating leetcode-tui stemmed from my preference for tools that are lightw

A fast, simple TUI for interacting with systemd services and their logs
A fast, simple TUI for interacting with systemd services and their logs

systemctl-tui A fast, simple TUI for interacting with systemd services and their logs. systemctl-tui can quickly browse service status and logs, and s

Rust TUI client for steamcmd
Rust TUI client for steamcmd

Steam TUI About Just a simple TUI client for steamcmd. Allows for the graphical launching, updating, and downloading of steam games through a simple t

Another TUI based system monitor, this time in Rust!
Another TUI based system monitor, this time in Rust!

Another TUI based system monitor, this time in Rust!

A user-friendly TUI client for Matrix written in Rust!

Konoha A user-friendly TUI client for Matrix written in Rust! Notice: The client is currently not usable and is only hosted on GitHub for version cont

TUI image viewer
TUI image viewer

Picterm TUI image viewer install $ cargo install picterm or $ git clone https://github.com/ksk001100/picterm $ cd picterm $ cargo install --path . usa

A cli prepared with TUI that facilitates your operations.
A cli prepared with TUI that facilitates your operations.

⚠️ For linux only ⚠️ Helper CLI A cli prepared with TUI that facilitates your operations. Click me to learn more about the theme system. If you just w

A tui to test regexes on the rust regex crate

regex-tui Structure src/ ├── app.rs - holds the states and renders the widgets ├── event.rs - handles the terminal events (key press, mouse cl

Parse hex colors to tui::style::Color

Color - Tui Parse hex colors to tui rgb colors #c3f111 - Color::Rgb(195,241,17) Note that the indexed colors are NOT HEX #142 - Color::Indexed(142)

Comments
  • Add NetBSD installation

    Add NetBSD installation

    Hi,

    I've just packaged moccasin for NetBSD and merged the package into the main branch. Commit log, for reference: http://mail-index.netbsd.org/pkgsrc-changes/2023/09/03/msg281899.html

    Please consider adding install instructions to the README.md

    I got a few warnings building with Rust-1.71.1, leaving those here.

       Compiling moccasin v0.1.2 (/usr/pkgsrc/wip/moccasin/work/moccasin-0.1.2)
    warning: unused import: `SortOrder`
     --> src/repo/storage.rs:1:29
      |
    1 | use crate::config::{Config, SortOrder};
      |                             ^^^^^^^^^
      |
      = note: `#[warn(unused_imports)]` on by default
    
    warning: unused import: `Item`
     --> src/repo/storage.rs:2:25
      |
    2 | use crate::feed::{Feed, Item};
      |                         ^^^^
    
    warning: unused variable: `red`
       --> src/config/theme.rs:133:13
        |
    133 |         let red = make_color("#850908");
        |             ^^^ help: if this is intentional, prefix it with an underscore: `_red`
        |
        = note: `#[warn(unused_variables)]` on by default
    
    warning: unused variable: `fragment`
      --> src/ui/detail.rs:31:17
       |
    31 |     fn from_dom(fragment: Dom) -> Self {
       |                 ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_fragment`
    
    warning: enum `StorageRequest` is never used
      --> src/repo/repo.rs:23:6
       |
    23 | enum StorageRequest {
       |      ^^^^^^^^^^^^^^
       |
       = note: `#[warn(dead_code)]` on by default
    
    warning: function `centered_rect_sized` is never used
       --> src/ui/mod.rs:217:4
        |
    217 | fn centered_rect_sized(width: u16, height: u16, r: Rect) -> Rect {
        |    ^^^^^^^^^^^^^^^^^^^
    
    warning: function `get_line_offset` is never used
     --> src/ui/detail.rs:7:4
      |
    7 | fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
      |    ^^^^^^^^^^^^^^^
    
    warning: struct `HTML` is never constructed
      --> src/ui/detail.rs:15:8
       |
    15 | struct HTML<'a> {
       |        ^^^^
    
    warning: associated items `from_dom`, `block`, `style`, `wrap`, `scroll`, and `alignment` are never used
      --> src/ui/detail.rs:31:8
       |
    30 | impl<'a> HTML<'a> {
       | ----------------- associated items in this implementation
    31 |     fn from_dom(fragment: Dom) -> Self {
       |        ^^^^^^^^
    ...
    45 |     pub fn block(mut self, block: Block<'a>) -> HTML<'a> {
       |            ^^^^^
    ...
    50 |     pub fn style(mut self, style: Style) -> HTML<'a> {
       |            ^^^^^
    ...
    55 |     pub fn wrap(mut self, wrap: Wrap) -> HTML<'a> {
       |            ^^^^
    ...
    60 |     pub fn scroll(mut self, offset: (u16, u16)) -> HTML<'a> {
       |            ^^^^^^
    ...
    65 |     pub fn alignment(mut self, alignment: Alignment) -> HTML<'a> {
       |            ^^^^^^^^^
    
    warning: unused `Result` that must be used
       --> src/app.rs:543:17
        |
    543 |                 self.config.add_feed_url(&url);
        |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
        = note: `#[warn(unused_must_use)]` on by default
    help: use `let _ = ...` to ignore the resulting value
        |
    543 |                 let _ = self.config.add_feed_url(&url);
        |                 +++++++
    
    warning: unused `Result` that must be used
       --> src/app.rs:550:21
        |
    550 |                     self.config.remove_feed_url(&url);
        |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    550 |                     let _ = self.config.remove_feed_url(&url);
        |                     +++++++
    
    warning: unused `Result` that must be used
       --> src/app.rs:551:21
        |
    551 |                     self.repo.remove_feed_url(&url);
        |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    551 |                     let _ = self.repo.remove_feed_url(&url);
        |                     +++++++
    
    warning: unused `Result` that must be used
      --> src/repo/repo.rs:77:17
       |
    77 |                 tx.send(RepositoryEvent::Refresh);
       |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
       |
    77 |                 let _ = tx.send(RepositoryEvent::Refresh);
       |                 +++++++
    
    warning: unused `Result` that must be used
      --> src/repo/repo.rs:99:21
       |
    99 |                     self.storage.write_all(&feeds);
       |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
       |
    99 |                     let _ = self.storage.write_all(&feeds);
       |                     +++++++
    
    warning: unused `Result` that must be used
       --> src/repo/repo.rs:100:21
        |
    100 |                     self.app_tx.send(RepositoryEvent::RetrievedAll(feeds));
        |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    100 |                     let _ = self.app_tx.send(RepositoryEvent::RetrievedAll(feeds));
        |                     +++++++
    
    warning: unused `Result` that must be used
       --> src/repo/repo.rs:104:21
        |
    104 |                     self.storage.write_one(&feed);
        |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    104 |                     let _ = self.storage.write_one(&feed);
        |                     +++++++
    
    warning: unused `Result` that must be used
       --> src/repo/repo.rs:105:21
        |
    105 |                     self.app_tx.send(RepositoryEvent::RetrievedOne(feed));
        |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    105 |                     let _ = self.app_tx.send(RepositoryEvent::RetrievedOne(feed));
        |                     +++++++
    
    warning: unused `Result` that must be used
       --> src/repo/repo.rs:126:13
        |
    126 |             app_tx.send(RepositoryEvent::Aborted);
        |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |
        = note: this `Result` may be an `Err` variant, which should be handled
    help: use `let _ = ...` to ignore the resulting value
        |
    126 |             let _ = app_tx.send(RepositoryEvent::Aborted);
        |             +++++++
    
    warning: `moccasin` (lib) generated 18 warnings (run `cargo fix --lib -p moccasin` to apply 4 suggestions)
        Finished release [optimized] target(s) in 7m 27s
    

    Thanks!

    opened by 0323pin 3
Owner
Tobias Fried
Full-Stack Web Engineer, Photographer, and Kombucha Brewer
Tobias Fried
A template for bootstrapping a Rust TUI application with tui-rs & crossterm

rust-tui-template A template for bootstrapping a Rust TUI application with tui-rs & crossterm. tui-rs The library is based on the principle of immedia

Orhun Parmaksız 72 Dec 31, 2022
Rust low-level minimalist APNG writer and PNG reader with just a few dependencies with all possible formats coverage (including HDR).

project Wiki https://github.com/js29a/micro_png/wiki at glance use micro_png::*; fn main() { // load an image let image = read_png("tmp/test.

jacek SQ6KBQ 8 Aug 30, 2023
Configurable, extensible, interactive line reader

linefeed linefeed is a configurable, concurrent, extensible, interactive input reader for Unix terminals and Windows console. API Documentation linefe

Murarth 176 Jan 3, 2023
A blazingly fast rust-based bionic reader for blazingly fast reading within a terminal console 🦀

This Rust-based CLI tool reads text and returns it back in bionic reading format for blazingly fast loading and even faster reading! Bionic reading is

Ismet Handzic 5 Aug 5, 2023
AniTUI is a CLI (and in the future a TUI) app for searching and wathching anime in MPV.

AniTUI is a CLI (and in the future a TUI) app for searching and wathching anime in MPV. This is a Rust rewrite (quite literally a rewrite) of Pystardu

null 7 Oct 31, 2022
A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf.

xplr A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf. [Quickstart] [Features] [Plugins] [Documentation] [Upgrade Guide] [

Arijit Basu 2.6k Jan 1, 2023
TUI for crate management like lazydocker and lazynpm

TUI for crate management like lazydocker and lazynpm. Shouldve named it as lazycargo but lazycrates sounded good at that time.

Aquib 9 Dec 8, 2022
✨ sleek typing tui with visualized results and historical logging

thokr ✨ sleek typing tui with visualized results and historical logging Usage For detailed usage run thokr -h. thokr 0.4.1 sleek typing tui with visua

colby thomas 440 Dec 30, 2022
A user-friendly TUI for secure file transfers, with arrow-key and VIM-style navigation

gsftp SFTP with an interactive text-based user interface (TUI). Transfer files through an encrypted connection with a visual interface, so you can see

Ben Jiron 3 Jul 7, 2022
Simple yet powerful multi-line text editor widget for tui-rs and ratatui

tui-textarea tui-textarea is a simple yet powerful text editor widget like <textarea> in HTML for tui-rs and ratatui. Multi-line text editor can be ea

Linda_pp 126 Jul 12, 2023