Compare commits

...

28 commits
v0.0.1 ... main

Author SHA1 Message Date
d74625e2b3
docs: fix readme demo gif 2025-03-09 21:03:06 +00:00
d200bcb9b5
feat: build using forgejo actions
All checks were successful
Release / build (push) Successful in 3m48s
2025-03-08 16:59:37 +00:00
dependabot[bot]
a9d6c1b327
Bump ring from 0.17.8 to 0.17.13 in the cargo group (#8)
Bumps the cargo group with 1 update: [ring](https://github.com/briansmith/ring).


Updates `ring` from 0.17.8 to 0.17.13
- [Changelog](https://github.com/briansmith/ring/blob/main/RELEASES.md)
- [Commits](https://github.com/briansmith/ring/commits)

---
updated-dependencies:
- dependency-name: ring
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-08 15:07:27 +00:00
dependabot[bot]
631470324c
Bump the dev-dependencies group across 1 directory with 4 updates (#7)
Bumps the dev-dependencies group with 4 updates in the / directory: [chrono](https://github.com/chronotope/chrono), [clap](https://github.com/clap-rs/clap), [serde](https://github.com/serde-rs/serde) and [anyhow](https://github.com/dtolnay/anyhow).


Updates `chrono` from 0.4.39 to 0.4.40
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.39...v0.4.40)

Updates `clap` from 4.5.29 to 4.5.31
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.29...v4.5.31)

Updates `serde` from 1.0.217 to 1.0.218
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.217...v1.0.218)

Updates `anyhow` from 1.0.95 to 1.0.97
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.95...1.0.97)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 13:27:10 +00:00
dependabot[bot]
e091e03d5a
Bump the dev-dependencies group with 2 updates (#5)
Bumps the dev-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [rss](https://github.com/rust-syndication/rss).


Updates `clap` from 4.5.28 to 4.5.29
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.28...clap_complete-v4.5.29)

Updates `rss` from 2.0.11 to 2.0.12
- [Release notes](https://github.com/rust-syndication/rss/releases)
- [Changelog](https://github.com/rust-syndication/rss/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-syndication/rss/compare/2.0.11...2.0.12)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: rss
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 12:44:14 +00:00
dependabot[bot]
14b6923e60
Bump the dev-dependencies group with 2 updates (#4)
Bumps the dev-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [toml](https://github.com/toml-rs/toml).


Updates `clap` from 4.5.27 to 4.5.28
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.27...clap_complete-v4.5.28)

Updates `toml` from 0.8.19 to 0.8.20
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.19...toml-v0.8.20)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 14:35:19 +00:00
1a1c5d24f6
test build in pull request 2025-02-10 14:31:12 +00:00
dependabot[bot]
adaa032f69
Bump openssl from 0.10.68 to 0.10.70 in the cargo group (#3)
Bumps the cargo group with 1 update: [openssl](https://github.com/sfackler/rust-openssl).


Updates `openssl` from 0.10.68 to 0.10.70
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.68...openssl-v0.10.70)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 22:34:38 +00:00
dependabot[bot]
64f06c65f9
Bump the dev-dependencies group with 2 updates (#2)
Bumps the dev-dependencies group with 2 updates: [clap](https://github.com/clap-rs/clap) and [indicatif](https://github.com/console-rs/indicatif).


Updates `clap` from 4.5.26 to 4.5.27
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.26...clap_complete-v4.5.27)

Updates `indicatif` from 0.17.9 to 0.17.11
- [Release notes](https://github.com/console-rs/indicatif/releases)
- [Commits](https://github.com/console-rs/indicatif/compare/0.17.9...0.17.11)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: indicatif
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 12:13:32 +00:00
04dc5e28eb
reorder description trim chars 2025-01-31 19:04:29 +00:00
1788524a70
bump version to v0.0.3 2025-01-30 22:27:43 +00:00
04ba3e64be
remove html tags from description content 2025-01-30 22:19:24 +00:00
535ace44e3
add updated demo.vhs file 2025-01-17 22:49:24 +00:00
e87a8d453c
update demo gif in readme 2025-01-17 22:48:46 +00:00
a6787d0688
main return more specific type 2025-01-16 21:21:51 +00:00
8517ac3ac7
add charmbracelet/vhs demo tape 2025-01-14 21:31:15 +00:00
3254268b78
replace demo gif with charmbracelet/vhs 2025-01-14 21:28:46 +00:00
5f1229ace1
update demo gif in readme
update demo gif in readme for v0.0.2 release
2025-01-14 20:39:52 +00:00
dfff01acf6
remove terminal-link dependency 2025-01-14 19:16:17 +00:00
cb4d395997
readd custom style to pb 2025-01-14 17:04:14 +00:00
c0ecec680f
add back list length as pb max 2025-01-14 15:32:30 +00:00
14c99e6380
all but main function out of main 2025-01-14 15:00:12 +00:00
22b9cf046c
add nixos install instructions 2025-01-14 12:48:25 +00:00
28d59a4c7a
separate modules into different files 2025-01-14 12:32:48 +00:00
5a9338f64d
remove colored and emojis dependencies 2025-01-14 00:28:35 +00:00
409c1d4787
add flake.nix (#1)
example taken from github:jnsahaj/lumen
2025-01-13 19:56:09 +00:00
4625e5f6e6
update readme contents to match personal site 2025-01-13 18:38:21 +00:00
733de0ebad
add demo gif to readme 2025-01-13 17:36:23 +00:00
14 changed files with 403 additions and 283 deletions

View file

@ -1,4 +1,4 @@
name: Rust
name: Release
on:
push:
@ -19,10 +19,10 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build release
run: cargo build --release
- uses: https://github.com/dtolnay/rust-toolchain@stable
- run: cargo build --release
- name: Upload release assets
uses: softprops/action-gh-release@v2
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: ${{ env.BIN_NAME }}

1
.github/CODEOWNERS vendored
View file

@ -1 +0,0 @@
* @troylusty

View file

@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
groups:
dev-dependencies:
patterns:
- "*"

135
Cargo.lock generated
View file

@ -81,6 +81,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "atom_syndication"
version = "0.12.6"
@ -147,9 +153,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cc"
version = "1.2.5"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
@ -162,23 +168,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.39"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets",
"windows-link",
]
[[package]]
name = "clap"
version = "4.5.26"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [
"clap_builder",
"clap_derive",
@ -186,9 +192,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.26"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [
"anstream",
"anstyle",
@ -198,9 +204,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.24"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck",
"proc-macro2",
@ -220,15 +226,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "console"
version = "0.15.10"
@ -344,15 +341,6 @@ dependencies = [
"syn",
]
[[package]]
name = "emojis"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e1f1df1f181f2539bac8bf027d31ca5ffbf9e559e3f2d09413b9107b5c02f4"
dependencies = [
"phf",
]
[[package]]
name = "encode_unicode"
version = "1.0.0"
@ -849,9 +837,9 @@ dependencies = [
[[package]]
name = "indicatif"
version = "0.17.9"
version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
dependencies = [
"console",
"number_prefix",
@ -999,9 +987,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "openssl"
version = "0.10.68"
version = "0.10.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6"
dependencies = [
"bitflags",
"cfg-if",
@ -1031,9 +1019,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.104"
version = "0.9.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc"
dependencies = [
"cc",
"libc",
@ -1043,18 +1031,16 @@ dependencies = [
[[package]]
name = "packard"
version = "0.0.1"
version = "0.0.4"
dependencies = [
"anyhow",
"chrono",
"clap",
"colored",
"emojis",
"futures",
"indicatif",
"reqwest",
"rss",
"serde",
"terminal-link",
"tokio",
"toml",
"xdg",
@ -1066,24 +1052,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -1182,24 +1150,23 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.8"
version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rss"
version = "2.0.11"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531af70fce504d369cf42ac0a9645f5a62a8ea9265de71cfa25087e9f6080c7c"
checksum = "b2107738f003660f0a91f56fd3e3bd3ab5d918b2ddaf1e1ec2136fb1c46f71bf"
dependencies = [
"atom_syndication",
"derive_builder",
@ -1305,18 +1272,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
@ -1362,12 +1329,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
@ -1393,12 +1354,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -1483,12 +1438,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "terminal-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "253bcead4f3aa96243b0f8fa46f9010e87ca23bd5d0c723d474ff1d2417bbdf8"
[[package]]
name = "tinystr"
version = "0.7.6"
@ -1561,9 +1510,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.19"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
@ -1582,9 +1531,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.22"
version = "0.22.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
dependencies = [
"indexmap",
"serde",
@ -1809,6 +1758,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]]
name = "windows-registry"
version = "0.2.0"
@ -1923,9 +1878,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.24"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
dependencies = [
"memchr",
]

View file

@ -1,24 +1,22 @@
[package]
name = "packard"
version = "0.0.1"
version = "0.0.4"
edition = "2021"
description = "A terminal based feed checker."
authors = ["Troy Lusty <hello@troylusty.com>"]
[dependencies]
chrono = "0.4.39"
clap = { version = "4.5.26", features = ["derive"] }
chrono = "0.4.40"
clap = { version = "4.5.31", features = ["derive"] }
reqwest = "0.12.12"
rss = "2.0.11"
serde = { version = "1.0.217", features = ["derive"] }
rss = "2.0.12"
serde = { version = "1.0.218", features = ["derive"] }
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread"] }
toml = "0.8.19"
toml = "0.8.20"
xdg = "2.5.2"
futures = "0.3.31"
indicatif = "0.17.9"
colored = "3.0.0"
terminal-link = "0.1.0"
emojis = "0.6.4"
indicatif = "0.17.11"
anyhow = "1.0.97"
[profile.dev]
opt-level = 0

View file

@ -1,10 +1,30 @@
<div align="center">
<h1>️📰 Packard</h1>
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/troylusty/packard">
<img alt="Release" src="https://img.shields.io/gitea/v/release/troy/packard?gitea_url=https%3A%2F%2Fcode.troylusty.com">
<h5>Packard is a simple RSS aggregator meant to allow you to take a quick glance at what's occurring in topics you care about.</h5>
</div>
![Demo](https://github.com/user-attachments/assets/5ce5e6a5-5e2f-44cf-8fb1-6c3862685f6e)
![Demo](demo/demo.gif)
This is my first attempt at making something with Rust so that I may learn alongside creating something that I personally find useful.
## Installation
On NixOS you can install Packard by including it as an input in flake.nix, then adding it to your system packages.
```nix
inputs = {
packard.url = "git+https://code.troylusty.com/troy/packard";
};
...
environment.systemPackages = {
inputs.packard.packages."${pkgs.system}".default
};
```
Alternatively, the [latest release](https://github.com/troylusty/packard/releases/latest) binary is available.
## Configuration
@ -48,8 +68,4 @@ news = [
After running Packard with your configured settings, the parsed results can be opened in your default browser however your terminal allows for opening URLs. For example the keybind for this with [Foot](https://codeberg.org/dnkl/foot#urls) is `ctrl` + `shift` + `o`.
Currently no keyboard interaction is implemented. To get around this you can pipe the output of Packard into a tool like `less` like so: `packard -c 12 -l news -s 3 | less`. Be aware, this will remove any text formatting that has been applied.
## Notes
For anyone who may or may not look at this, I am very new to Rust with this being my first project making use of it. So expect mistakes.
Currently no keyboard interaction is implemented. To get around this you can pipe the output of Packard into a tool like `less` like so: `packard -c 12 -l news -s 3 | less`.

BIN
demo/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

19
demo/demo.tape Normal file
View file

@ -0,0 +1,19 @@
Output demo.gif
Require packard
Set Shell "zsh"
Set FontSize 32
Set Width 1200
Set Height 600
Type@250ms "packard" Sleep 500ms
Hide Type " -c 48 -l news" Show Sleep 500ms
Type@200ms " | less" Sleep 500ms Enter
Sleep 3s
PageDown@50ms 1
Sleep 1s
PageDown@50ms 2
Sleep 1s
PageUp@50ms 3
Sleep 3s

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1736701207,
"narHash": "sha256-jG/+MvjVY7SlTakzZ2fJ5dC3V1PrKKrUEOEE30jrOKA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ed4a395ea001367c1f13d34b1e01aa10290f67d6",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

41
flake.nix Normal file
View file

@ -0,0 +1,41 @@
{
description = "Packard is a simple RSS aggregator meant to allow you to take a quick glance at what's occurring in topics you care about.";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
self,
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {inherit system;};
in {
packages = {
packard = let
manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
in
pkgs.rustPlatform.buildRustPackage {
pname = manifest.name;
version = manifest.version;
cargoLock.lockFile = ./Cargo.lock;
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [pkgs.pkg-config];
buildInputs = [pkgs.openssl];
};
default = self.packages.${system}.packard;
};
})
// {
overlays.default = final: prev: {
inherit (self.packages.${final.system}) packard;
};
};
}

87
src/config.rs Normal file
View file

@ -0,0 +1,87 @@
use clap::Parser;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use xdg::BaseDirectories;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[arg(short, long)]
pub verbose: bool,
#[arg(short, long)]
pub count: Option<u8>,
#[arg(short = 'l', long)]
pub selected_list: Option<String>,
#[arg(short, long)]
pub skip_amount: Option<u8>,
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub count: Option<u8>,
pub skip_amount: Option<u8>,
pub selected_list: Option<String>,
pub lists: HashMap<String, Vec<String>>,
}
pub fn parse_cli() -> Cli {
let args = Cli::parse();
args
}
pub fn collate_values(args: Cli, config: &Config) -> (u8, u8, String) {
if args.verbose {
println!("{:?}", args);
println!("Selected list: {:?}", config.selected_list);
println!("Items: {:?}", config.lists);
}
let count: u8 = if args.count.is_some() {
args.count.expect("Count flag wrong?")
} else if config.count.is_some() {
config.count.expect("Unable to use count value from config")
} else {
8
};
let skip_amount: u8 = if args.skip_amount.is_some() {
args.skip_amount.expect("Skip amount flag wrong?")
} else if config.skip_amount.is_some() {
config
.skip_amount
.expect("Unable to use skip amount value from config")
} else {
0
};
let list: String = if args.selected_list.is_some() {
args.selected_list
.clone()
.expect("Error getting selected list from flag")
} else if config.selected_list.is_some() {
config
.selected_list
.clone()
.expect("Need to specify a selected list")
} else {
panic!("Need to set selected list")
};
(count, skip_amount, list)
}
pub fn validate_config() -> Config {
let xdg_dirs = BaseDirectories::new().expect("Failed to get XDG directories");
let config_path = xdg_dirs
.place_config_file("packard/config.toml")
.expect("Failed to determine config file path");
if !config_path.exists() {
eprintln!("Configuration file not found at {:?}", config_path);
}
let config_content = fs::read_to_string(&config_path).expect("Failed to read config file");
let config: Config = toml::de::from_str(&config_content).expect("Failed to parse TOML");
config
}

68
src/data.rs Normal file
View file

@ -0,0 +1,68 @@
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use futures::future::join_all;
use indicatif::ProgressBar;
use reqwest::get;
use rss::Channel;
#[derive(Debug)]
pub struct FeedItem {
pub title: String,
pub description: String,
pub link: String,
pub pub_date: DateTime<Utc>,
}
async fn fetch_rss(url: &str, pb: &ProgressBar) -> Result<Channel> {
let response = get(url)
.await
.context("Failed to send request")?
.text()
.await
.context("Failed to read response text")?;
let channel = Channel::read_from(response.as_bytes()).context("Failed to parse RSS feed")?;
pb.inc(1);
pb.set_message(format!("Processing: {}", channel.title));
Ok(channel)
}
fn parse_feed(channel: &Channel) -> Vec<FeedItem> {
channel
.items()
.iter()
.map(|item| FeedItem {
title: item.title().unwrap_or("No title").to_string(),
description: item.description().unwrap_or("No description").to_string(),
link: item.link().unwrap_or("No link").to_string(),
pub_date: item
.pub_date()
.and_then(|date_str| DateTime::parse_from_rfc2822(date_str).ok())
.map(|dt| dt.with_timezone(&Utc))
.unwrap_or_else(|| Utc::now()),
})
.collect()
}
pub async fn run_tasks(
sources: Vec<String>,
count: u8,
skip: u8,
pb: &ProgressBar,
) -> Vec<FeedItem> {
let fetch_futures: Vec<_> = sources.iter().map(|url| fetch_rss(url, &pb)).collect();
let channels = join_all(fetch_futures).await;
let mut all_items = Vec::new();
for channel in channels.into_iter().filter_map(Result::ok) {
let feed_items = parse_feed(&channel);
all_items.extend(feed_items);
}
all_items.sort_by(|a, b| b.pub_date.cmp(&a.pub_date));
all_items.truncate((count + skip).into());
let removed_items = all_items.split_off(skip.into());
removed_items
}

View file

@ -1,174 +1,34 @@
use chrono::{DateTime, Utc};
use clap::Parser;
use colored::Colorize;
use emojis;
use futures::future::join_all;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::get;
use rss::Channel;
use serde::Deserialize;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use terminal_link::Link;
use tokio;
use toml;
use xdg::BaseDirectories;
use indicatif::ProgressStyle;
use tokio::io;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
count: Option<u8>,
#[arg(short = 'l', long)]
selected_list: Option<String>,
#[arg(short, long)]
skip_amount: Option<u8>,
}
#[derive(Debug, Deserialize)]
struct Config {
count: Option<u8>,
skip_amount: Option<u8>,
selected_list: Option<String>,
lists: HashMap<String, Vec<String>>,
}
#[derive(Debug)]
struct FeedItem {
title: String,
description: String,
link: String,
pub_date: DateTime<Utc>,
}
async fn fetch_rss(url: &str, pb: &ProgressBar) -> Result<Channel, Box<dyn Error>> {
let response = get(url).await?.text().await?;
let channel = Channel::read_from(response.as_bytes())?;
pb.inc(1);
pb.set_message(format!("Processing: {}", channel.title));
Ok(channel)
}
fn parse_feed(channel: &Channel) -> Vec<FeedItem> {
channel
.items()
.iter()
.map(|item| FeedItem {
title: item.title().unwrap_or("No title").to_string(),
description: item.description().unwrap_or("No description").to_string(),
link: item.link().unwrap_or("No link").to_string(),
pub_date: item
.pub_date()
.and_then(|date_str| DateTime::parse_from_rfc2822(date_str).ok())
.map(|dt| dt.with_timezone(&Utc))
.unwrap_or_else(|| Utc::now()),
})
.collect()
}
fn validate_config() -> Config {
let xdg_dirs = BaseDirectories::new().expect("Failed to get XDG directories");
let config_path = xdg_dirs
.place_config_file("packard/config.toml")
.expect("Failed to determine config file path");
if !config_path.exists() {
eprintln!("Configuration file not found at {:?}", config_path);
}
let config_content = fs::read_to_string(&config_path).expect("Failed to read config file");
let config: Config = toml::de::from_str(&config_content).expect("Failed to parse TOML");
config
}
fn trim_chars(input: &str) -> String {
let trimmed: String = input.chars().take(256).collect();
if trimmed.len() < input.len() {
format!("{}...", trimmed)
} else {
trimmed
}
}
async fn run_tasks(sources: Vec<String>, count: u8, skip: u8, pb: &ProgressBar) -> Vec<FeedItem> {
let fetch_futures: Vec<_> = sources.iter().map(|url| fetch_rss(url, &pb)).collect();
let channels = join_all(fetch_futures).await;
let mut all_items = Vec::new();
for channel in channels.into_iter().filter_map(Result::ok) {
let feed_items = parse_feed(&channel);
all_items.extend(feed_items);
}
all_items.sort_by(|a, b| b.pub_date.cmp(&a.pub_date));
all_items.truncate((count + skip).into());
let removed_items = all_items.split_off(skip.into());
removed_items
}
mod config;
mod data;
mod utils;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let config = validate_config();
let args = Cli::parse();
if args.verbose {
println!("{:?}", args);
println!("Selected list: {:?}", config.selected_list);
println!("Items: {:?}", config.lists);
}
let count: u8 = if args.count.is_some() {
args.count.expect("Count flag wrong?")
} else if config.count.is_some() {
config.count.expect("Unable to use count value from config")
} else {
8
};
let skip_amount: u8 = if args.skip_amount.is_some() {
args.skip_amount.expect("Skip amount flag wrong?")
} else if config.skip_amount.is_some() {
config
.skip_amount
.expect("Unable to use skip amount value from config")
} else {
0
};
let list: String = if args.selected_list.is_some() {
args.selected_list
.expect("Error getting selected list from flag")
} else if config.selected_list.is_some() {
config
.selected_list
.expect("Need to specify a selected list")
} else {
panic!("Need to set selected list")
};
async fn main() -> Result<(), io::Error> {
let config = config::validate_config();
let args = config::parse_cli();
let (count, skip_amount, list) = config::collate_values(args, &config);
if let Some(values) = config.lists.get(&list) {
let pb = indicatif::ProgressBar::new(12);
pb.set_style(
ProgressStyle::with_template("[{elapsed}] {bar:40.green/black} {msg}").unwrap(),
let pb = indicatif::ProgressBar::new(
values
.len()
.try_into()
.expect("Could not convert list length"),
);
let all_items = run_tasks(values.to_vec(), count, skip_amount, &pb).await;
pb.set_style(ProgressStyle::with_template("{wide_bar} {percent}% {msg}").unwrap());
let all_items = data::run_tasks(values.to_vec(), count, skip_amount, &pb).await;
pb.finish_and_clear();
for item in all_items {
println!(
"{} {}\n{}\n{}\n",
emojis::get_by_shortcode("newspaper").unwrap(),
Link::new(&item.title, &item.link),
trim_chars(&item.description).dimmed().italic(),
item.pub_date.to_string().dimmed().bold()
"\x1b[1m>\x1b[0m \x1b[1;32m\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\\x1b[0m\n\x1b[3m\x1b[2m{}\x1b[0m\n\x1b[2m{}\x1b[0m\n",
item.link,
item.title,
utils::trim_chars(&utils::remove_html_tags(&item.description)),
item.pub_date.to_string()
);
}
} else {

26
src/utils.rs Normal file
View file

@ -0,0 +1,26 @@
pub fn trim_chars(input: &str) -> String {
let trimmed: String = input.chars().take(256).collect();
if trimmed.len() < input.len() {
format!("{}...", trimmed)
} else {
trimmed
}
}
pub fn remove_html_tags(input: &str) -> String {
let mut result = String::new();
let mut in_tag = false;
for c in input.chars() {
if c == '<' {
in_tag = true;
} else if c == '>' {
in_tag = false;
} else if !in_tag {
result.push(c);
}
}
result
}