1 Commits

Author SHA1 Message Date
dependabot[bot]
074b80f6c6 Bump actions/upload-artifact from 3 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-09 03:20:35 +00:00
9 changed files with 30 additions and 242 deletions

View File

@@ -36,7 +36,7 @@ jobs:
run: cargo bench --no-fail-fast
- name: Upload benchmark results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: benchmark-results
path: target/criterion/

View File

@@ -149,7 +149,7 @@ jobs:
run: cargo build --release --target ${{ matrix.target }} --verbose
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: piglet-${{ matrix.target }}
path: target/${{ matrix.target }}/release/piglet

View File

@@ -5,174 +5,37 @@ on:
tags:
- 'v*.*.*'
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
create-release:
name: Create Release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get version from tag
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: false
prerelease: false
generate_release_notes: false
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
# Piglet v${{ steps.get_version.outputs.version }}
🐷 Animated and colorful figlet wrapper written in Rust
## Features
- 20+ motion effects (fade, slide, scale, typewriter, wave, rainbow, etc.)
- 18+ easing functions (linear, ease-in/out, quad, cubic, elastic, bounce, etc.)
- Full color support with gradients and palettes
- CSS gradient syntax support
- Cross-platform (Linux, macOS)
## Changes in this Release
See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details.
## Installation
### Linux (x86_64)
```bash
curl -L https://github.com/${{ github.repository }}/releases/download/v${{ steps.get_version.outputs.version }}/piglet-x86_64-unknown-linux-gnu -o piglet
chmod +x piglet
sudo mv piglet /usr/local/bin/
```
### Linux (musl)
```bash
curl -L https://github.com/${{ github.repository }}/releases/download/v${{ steps.get_version.outputs.version }}/piglet-x86_64-unknown-linux-musl -o piglet
chmod +x piglet
sudo mv piglet /usr/local/bin/
```
### macOS (Intel)
```bash
curl -L https://github.com/${{ github.repository }}/releases/download/v${{ steps.get_version.outputs.version }}/piglet-x86_64-apple-darwin -o piglet
chmod +x piglet
sudo mv piglet /usr/local/bin/
```
### macOS (Apple Silicon)
```bash
curl -L https://github.com/${{ github.repository }}/releases/download/v${{ steps.get_version.outputs.version }}/piglet-aarch64-apple-darwin -o piglet
chmod +x piglet
sudo mv piglet /usr/local/bin/
```
### Via Cargo
```bash
cargo install --git https://github.com/${{ github.repository }} --tag v${{ steps.get_version.outputs.version }}
```
## Usage Examples
```bash
# Simple gradient
piglet "Hello" -g "linear-gradient(90deg, red, blue)"
# Typewriter effect
piglet "World" -m typewriter -i ease-out
# Wave with rainbow colors
piglet "Cool!" -p "hotpink,cyan,gold" -m wave
```
See the [README](https://github.com/${{ github.repository }}/blob/v${{ steps.get_version.outputs.version }}/README.md) for full documentation.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-release:
name: Build Release (${{ matrix.target }})
needs: create-release
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
- os: macos-latest
target: x86_64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools (Linux musl)
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-${{ matrix.target }}-cargo-build-release-${{ hashFiles('**/Cargo.lock') }}
- name: Build release binary
run: cargo build --release --target ${{ matrix.target }} --verbose
- name: Strip binary (Linux)
if: matrix.os == 'ubuntu-latest'
run: strip target/${{ matrix.target }}/release/piglet
- name: Strip binary (macOS)
if: matrix.os == 'macos-latest'
run: strip target/${{ matrix.target }}/release/piglet
- name: Rename binary
run: |
cp target/${{ matrix.target }}/release/piglet piglet-${{ matrix.target }}
- name: Upload release binary
uses: softprops/action-gh-release@v1
with:
files: piglet-${{ matrix.target }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-crate:
name: Publish to crates.io
needs: build-release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_TOKEN }}
continue-on-error: true
### Linux (x86_64)

View File

@@ -21,8 +21,8 @@ palette = "0.7"
# Terminal manipulation
crossterm = "0.27"
# Async runtime (for timing and signal handling)
tokio = { version = "1.35", features = ["time", "rt-multi-thread", "macros", "signal", "sync"] }
# Async runtime (for timing)
tokio = { version = "1.35", features = ["time", "rt-multi-thread", "macros"] }
# Process execution
which = "5.0"

View File

@@ -43,7 +43,7 @@ impl AnimationEngine {
self
}
pub async fn run(&self, terminal: &mut TerminalManager) -> Result<bool> {
pub async fn run(&self, terminal: &mut TerminalManager) -> Result<()> {
let renderer = renderer::Renderer::new(
&self.ascii_art,
self.duration_ms,

View File

@@ -2,12 +2,6 @@ use crate::animation::{easing::EasingFunction, effects::Effect, timeline::Timeli
use crate::color::{apply, ColorEngine};
use crate::utils::{ansi, ascii::AsciiArt, terminal::TerminalManager};
use anyhow::Result;
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::Duration;
use tokio::time::sleep;
pub struct Renderer<'a> {
@@ -36,52 +30,17 @@ impl<'a> Renderer<'a> {
}
}
pub async fn render(&self, terminal: &mut TerminalManager) -> Result<bool> {
pub async fn render(&self, terminal: &mut TerminalManager) -> Result<()> {
let mut timeline = Timeline::new(self.timeline.duration_ms(), self.timeline.fps());
timeline.start();
// Spawn background thread to listen for exit keys
let should_exit = Arc::new(AtomicBool::new(false));
let should_exit_clone = should_exit.clone();
std::thread::spawn(move || loop {
if let Ok(true) = event::poll(Duration::from_millis(100)) {
if let Ok(Event::Key(key)) = event::read() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => {
should_exit_clone.store(true, Ordering::Relaxed);
break;
}
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
should_exit_clone.store(true, Ordering::Relaxed);
break;
}
_ => {}
}
}
}
if should_exit_clone.load(Ordering::Relaxed) {
break;
}
});
loop {
// Check for exit FIRST
if should_exit.load(Ordering::Relaxed) {
return Ok(true); // User requested exit
}
let frame_start = std::time::Instant::now();
// Calculate progress with easing
let linear_progress = timeline.progress();
let eased_progress = self.easing.ease(linear_progress);
// Check again before rendering
if should_exit.load(Ordering::Relaxed) {
return Ok(true); // User requested exit
}
// Apply effect
let effect_result = self.effect.apply(self.ascii_art, eased_progress);
@@ -92,11 +51,6 @@ impl<'a> Renderer<'a> {
effect_result.text.clone()
};
// Check before terminal operations
if should_exit.load(Ordering::Relaxed) {
return Ok(true); // User requested exit
}
// Render to terminal
terminal.clear()?;
terminal.refresh_size()?;
@@ -108,11 +62,7 @@ impl<'a> Renderer<'a> {
let (width, height) = terminal.get_size();
let lines: Vec<&str> = colored_text.lines().collect();
let text_height = lines.len() as i32;
let text_width = lines
.iter()
.map(|l| ansi::visual_width(l))
.max()
.unwrap_or(0) as i32;
let text_width = lines.iter().map(|l| ansi::visual_width(l)).max().unwrap_or(0) as i32;
let base_x = (width as i32 - text_width) / 2;
let base_y = (height as i32 - text_height) / 2;
@@ -128,14 +78,9 @@ impl<'a> Renderer<'a> {
}
}
// Check if user wants to exit
if should_exit.load(Ordering::Relaxed) {
return Ok(true); // User requested exit
}
// Check if animation is complete before advancing
if timeline.is_complete() {
return Ok(false); // Animation completed naturally
break;
}
// Advance to next frame and wait
@@ -144,21 +89,11 @@ impl<'a> Renderer<'a> {
let elapsed = frame_start.elapsed();
if elapsed < frame_duration {
let sleep_duration = frame_duration - elapsed;
// Break sleep into small chunks to check should_exit frequently
let chunk_duration = Duration::from_millis(5);
let mut remaining = sleep_duration;
while remaining > Duration::ZERO {
if should_exit.load(Ordering::Relaxed) {
return Ok(true); // User requested exit during sleep
}
let sleep_time = remaining.min(chunk_duration);
sleep(sleep_time).await;
remaining = remaining.saturating_sub(sleep_time);
}
sleep(frame_duration - elapsed).await;
}
}
Ok(())
}
fn apply_colors(&self, text: &str, progress: f64) -> String {

View File

@@ -61,14 +61,8 @@ async fn run_piglet(args: PigletCli) -> Result<()> {
// Run animation
loop {
let user_exited = animation_engine.run(&mut terminal).await?;
animation_engine.run(&mut terminal).await?;
// If user pressed exit key, stop looping
if user_exited {
break;
}
// If not looping, stop after one animation
if !args.loop_animation {
break;
}

View File

@@ -8,7 +8,7 @@ pub fn strip_ansi(text: &str) -> String {
// Skip ANSI escape sequence
if chars.peek() == Some(&'[') {
chars.next(); // consume '['
// Skip until we hit a letter (the command character)
// Skip until we hit a letter (the command character)
while let Some(&c) = chars.peek() {
chars.next();
if c.is_ascii_alphabetic() {

View File

@@ -69,11 +69,7 @@ impl TerminalManager {
pub fn print_centered(&self, text: &str) -> Result<()> {
let lines: Vec<&str> = text.lines().collect();
let max_width = lines
.iter()
.map(|l| ansi::visual_width(l))
.max()
.unwrap_or(0) as u16;
let max_width = lines.iter().map(|l| ansi::visual_width(l)).max().unwrap_or(0) as u16;
let height = lines.len() as u16;
let start_x = (self.width.saturating_sub(max_width)) / 2;