feat: init
f9bc9c26
6 file(s) · +218 −0
| 1 | + | # To get started with Dependabot version updates, you'll need to specify which |
|
| 2 | + | # package ecosystems to update and where the package manifests are located. |
|
| 3 | + | # Please see the documentation for all configuration options: |
|
| 4 | + | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates |
|
| 5 | + | ||
| 6 | + | version: 2 |
|
| 7 | + | updates: |
|
| 8 | + | # Maintain dependencies for Cargo |
|
| 9 | + | - package-ecosystem: "cargo" |
|
| 10 | + | directory: "/" # Location of package manifests |
|
| 11 | + | schedule: |
|
| 12 | + | interval: "weekly" |
|
| 13 | + | # Maintain dependencies for GitHub Actions |
|
| 14 | + | - package-ecosystem: github-actions |
|
| 15 | + | directory: "/" |
|
| 16 | + | schedule: |
|
| 17 | + | interval: weekly |
| 1 | + | name: CI |
|
| 2 | + | ||
| 3 | + | on: |
|
| 4 | + | pull_request: |
|
| 5 | + | push: |
|
| 6 | + | branches: |
|
| 7 | + | - main |
|
| 8 | + | - master |
|
| 9 | + | - develop |
|
| 10 | + | ||
| 11 | + | env: |
|
| 12 | + | CARGO_TERM_COLOR: always |
|
| 13 | + | ||
| 14 | + | # ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel |
|
| 15 | + | # and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency |
|
| 16 | + | concurrency: |
|
| 17 | + | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} |
|
| 18 | + | cancel-in-progress: true |
|
| 19 | + | ||
| 20 | + | jobs: |
|
| 21 | + | fmt: |
|
| 22 | + | name: fmt |
|
| 23 | + | runs-on: ubuntu-latest |
|
| 24 | + | steps: |
|
| 25 | + | - name: Checkout |
|
| 26 | + | uses: actions/checkout@v4 |
|
| 27 | + | - name: Install Rust stable |
|
| 28 | + | uses: dtolnay/rust-toolchain@stable |
|
| 29 | + | with: |
|
| 30 | + | components: rustfmt |
|
| 31 | + | - name: check formatting |
|
| 32 | + | run: cargo fmt -- --check |
|
| 33 | + | - name: Cache Cargo dependencies |
|
| 34 | + | uses: Swatinem/rust-cache@v2 |
|
| 35 | + | clippy: |
|
| 36 | + | name: clippy |
|
| 37 | + | runs-on: ubuntu-latest |
|
| 38 | + | permissions: |
|
| 39 | + | contents: read |
|
| 40 | + | checks: write |
|
| 41 | + | steps: |
|
| 42 | + | - name: Checkout |
|
| 43 | + | uses: actions/checkout@v4 |
|
| 44 | + | - name: Install Rust stable |
|
| 45 | + | uses: dtolnay/rust-toolchain@stable |
|
| 46 | + | with: |
|
| 47 | + | components: clippy |
|
| 48 | + | - name: Run clippy action |
|
| 49 | + | uses: clechasseur/rs-clippy-check@v3 |
|
| 50 | + | - name: Cache Cargo dependencies |
|
| 51 | + | uses: Swatinem/rust-cache@v2 |
|
| 52 | + | doc: |
|
| 53 | + | # run docs generation on nightly rather than stable. This enables features like |
|
| 54 | + | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an |
|
| 55 | + | # API be documented as only available in some specific platforms. |
|
| 56 | + | name: doc |
|
| 57 | + | runs-on: ubuntu-latest |
|
| 58 | + | steps: |
|
| 59 | + | - uses: actions/checkout@v4 |
|
| 60 | + | - name: Install Rust nightly |
|
| 61 | + | uses: dtolnay/rust-toolchain@nightly |
|
| 62 | + | - name: Run cargo doc |
|
| 63 | + | run: cargo doc --no-deps --all-features |
|
| 64 | + | env: |
|
| 65 | + | RUSTDOCFLAGS: --cfg docsrs |
|
| 66 | + | test: |
|
| 67 | + | runs-on: ${{ matrix.os }} |
|
| 68 | + | name: test ${{ matrix.os }} |
|
| 69 | + | strategy: |
|
| 70 | + | fail-fast: false |
|
| 71 | + | matrix: |
|
| 72 | + | os: [macos-latest, windows-latest] |
|
| 73 | + | steps: |
|
| 74 | + | # if your project needs OpenSSL, uncomment this to fix Windows builds. |
|
| 75 | + | # it's commented out by default as the install command takes 5-10m. |
|
| 76 | + | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append |
|
| 77 | + | # if: runner.os == 'Windows' |
|
| 78 | + | # - run: vcpkg install openssl:x64-windows-static-md |
|
| 79 | + | # if: runner.os == 'Windows' |
|
| 80 | + | - uses: actions/checkout@v4 |
|
| 81 | + | - name: Install Rust |
|
| 82 | + | uses: dtolnay/rust-toolchain@stable |
|
| 83 | + | # enable this ci template to run regardless of whether the lockfile is checked in or not |
|
| 84 | + | - name: cargo generate-lockfile |
|
| 85 | + | if: hashFiles('Cargo.lock') == '' |
|
| 86 | + | run: cargo generate-lockfile |
|
| 87 | + | - name: cargo test --locked |
|
| 88 | + | run: cargo test --locked --all-features --all-targets |
|
| 89 | + | - name: Cache Cargo dependencies |
|
| 90 | + | uses: Swatinem/rust-cache@v2 |
| 1 | + | [package] |
|
| 2 | + | name = "bullet" |
|
| 3 | + | version = "0.1.0" |
|
| 4 | + | description = "Minimal RSS TUI" |
|
| 5 | + | authors = ["Steve <contact@stevedylan.dev>"] |
|
| 6 | + | license = "MIT" |
|
| 7 | + | edition = "2024" |
|
| 8 | + | ||
| 9 | + | [dependencies] |
|
| 10 | + | color-eyre = "0.6.3" |
|
| 11 | + | crossterm = "0.29.0" |
|
| 12 | + | feedparser-rs = "0.5.3" |
|
| 13 | + | ratatui = "0.30.0" |
|
| 14 | + | ||
| 15 | + | # Read the optimization guideline for more details: https://ratatui.rs/recipes/apps/release-your-app/#optimizations |
|
| 16 | + | [profile.release] |
|
| 17 | + | codegen-units = 1 |
|
| 18 | + | lto = true |
|
| 19 | + | opt-level = "s" |
|
| 20 | + | strip = true |
| 1 | + | The MIT License (MIT) |
|
| 2 | + | ||
| 3 | + | Copyright (c) Steve <contact@stevedylan.dev> |
|
| 4 | + | ||
| 5 | + | Permission is hereby granted, free of charge, to any person obtaining a copy |
|
| 6 | + | of this software and associated documentation files (the "Software"), to deal |
|
| 7 | + | in the Software without restriction, including without limitation the rights |
|
| 8 | + | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
| 9 | + | copies of the Software, and to permit persons to whom the Software is |
|
| 10 | + | furnished to do so, subject to the following conditions: |
|
| 11 | + | ||
| 12 | + | The above copyright notice and this permission notice shall be included in all |
|
| 13 | + | copies or substantial portions of the Software. |
|
| 14 | + | ||
| 15 | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
| 16 | + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
| 17 | + | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
| 18 | + | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
| 19 | + | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
| 20 | + | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
| 21 | + | SOFTWARE. |
| 1 | + | # bullet |
|
| 2 | + | ||
| 3 | + | This is a [Ratatui] app generated by the [Hello World template]. |
|
| 4 | + | ||
| 5 | + | [Ratatui]: https://ratatui.rs |
|
| 6 | + | [Hello World Template]: https://github.com/ratatui/templates/tree/main/hello-world |
|
| 7 | + | ||
| 8 | + | ## License |
|
| 9 | + | ||
| 10 | + | Copyright (c) Steve <contact@stevedylan.dev> |
|
| 11 | + | ||
| 12 | + | This project is licensed under the MIT license ([LICENSE] or <http://opensource.org/licenses/MIT>) |
|
| 13 | + | ||
| 14 | + | [LICENSE]: ./LICENSE |
| 1 | + | use feedparser_rs::{ParsedFeed, parse_url}; |
|
| 2 | + | use ratatui::{ |
|
| 3 | + | DefaultTerminal, Frame, |
|
| 4 | + | style::{Color, Modifier, Style}, |
|
| 5 | + | text::{Line, Span, Text}, |
|
| 6 | + | widgets::List, |
|
| 7 | + | widgets::ListItem, |
|
| 8 | + | }; |
|
| 9 | + | ||
| 10 | + | fn main() -> color_eyre::Result<()> { |
|
| 11 | + | color_eyre::install()?; |
|
| 12 | + | let feed = parse_url("https://feeds.stevedylan.dev/feed.xml", None, None, None)?; |
|
| 13 | + | ratatui::run(|t| app(t, &feed))?; |
|
| 14 | + | Ok(()) |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn app(terminal: &mut DefaultTerminal, feed: &ParsedFeed) -> std::io::Result<()> { |
|
| 18 | + | loop { |
|
| 19 | + | terminal.draw(|f| render(f, feed))?; |
|
| 20 | + | if crossterm::event::read()?.is_key_press() { |
|
| 21 | + | break Ok(()); |
|
| 22 | + | } |
|
| 23 | + | } |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | fn render(frame: &mut Frame, feed: &ParsedFeed) { |
|
| 27 | + | let dim = Style::new().fg(Color::DarkGray); |
|
| 28 | + | let author_style = Style::new() |
|
| 29 | + | .fg(Color::DarkGray) |
|
| 30 | + | .add_modifier(Modifier::ITALIC); |
|
| 31 | + | ||
| 32 | + | let items: Vec<ListItem> = feed |
|
| 33 | + | .entries |
|
| 34 | + | .iter() |
|
| 35 | + | .map(|e| { |
|
| 36 | + | let date = e |
|
| 37 | + | .published |
|
| 38 | + | .as_ref() |
|
| 39 | + | .map(|d| d.to_string()) |
|
| 40 | + | .unwrap_or_else(|| "-".into()); |
|
| 41 | + | let title = e.title.as_deref().unwrap_or("(untitled)"); |
|
| 42 | + | let author = e |
|
| 43 | + | .authors |
|
| 44 | + | .first() |
|
| 45 | + | .and_then(|a| a.name.as_deref()) |
|
| 46 | + | .unwrap_or("anon"); |
|
| 47 | + | ListItem::new(Text::from(vec![ |
|
| 48 | + | Line::from(Span::styled(date, dim)), |
|
| 49 | + | Line::from(title.to_string()), |
|
| 50 | + | Line::from(Span::styled(author.to_string(), author_style)), |
|
| 51 | + | Line::from(""), |
|
| 52 | + | ])) |
|
| 53 | + | }) |
|
| 54 | + | .collect(); |
|
| 55 | + | frame.render_widget(List::new(items), frame.area()); |
|
| 56 | + | } |