4 Commits

Author SHA1 Message Date
149d308f9b Update version to v1.1.0 2026-03-23 17:35:55 -05:00
1e0e5c864c Add Pallet Lint and Pallet Fmt (#28)
closes #15
closes #16

Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/28
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 16:34:30 -06:00
c619621cf3 Implement Pallet Remove (#26)
closes #17

Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/26
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 16:06:36 -06:00
0334cd2fce Add Pallet Watch (#25)
closes #18

Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/25
Co-authored-by: godsfryingpan <sfrembling@gmail.com>
Co-committed-by: godsfryingpan <sfrembling@gmail.com>
2026-03-23 15:59:04 -06:00
8 changed files with 256 additions and 21 deletions

68
Cargo.lock generated
View File

@@ -84,6 +84,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bytes"
version = "1.11.1"
@@ -96,6 +105,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.6.0"
@@ -189,6 +204,17 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctrlc"
version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162"
dependencies = [
"dispatch2",
"nix",
"windows-sys 0.61.2",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -199,6 +225,18 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.11.0",
"block2",
"libc",
"objc2",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -340,6 +378,18 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "nix"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "notify"
version = "8.2.0"
@@ -367,6 +417,21 @@ dependencies = [
"bitflags 2.11.0",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
@@ -375,12 +440,13 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "pallet"
version = "1.0.6"
version = "1.1.0"
dependencies = [
"clap",
"clap_complete",
"clap_complete_nushell",
"colored",
"ctrlc",
"glob",
"notify",
"serde",

View File

@@ -1,6 +1,6 @@
[package]
name = "pallet"
version = "1.0.6"
version = "1.1.0"
edition = "2024"
description = "A project manager and build system for C inspired by Rust's Cargo"
@@ -9,6 +9,7 @@ clap = { version = "4.6.0", features = ["derive"] }
clap_complete = "4.6.0"
clap_complete_nushell = "4.6.0"
colored = "3.1.1"
ctrlc = "3.5.2"
glob = "0.3.3"
notify = "8.2.0"
serde = { version = "1.0.228", features = ["derive"] }

View File

@@ -1,12 +1,12 @@
# Maintainer: Shea Frembling <sfrembling@gmail.com>
pkgname=pallet
pkgver=1.0.6
pkgver=1.1.0
pkgrel=1
pkgdesc="A simple C project manager inspired by Cargo"
arch=('x86_64')
url=""
license=('MIT')
depends=('gcc' 'pkgconf')
depends=('gcc' 'pkgconf' 'clang')
makedepends=('rust' 'cargo')
source=()

View File

@@ -2,6 +2,7 @@ use std::{
env::set_current_dir,
path::{Path, PathBuf},
process::Command,
sync::{Arc, atomic::Ordering},
};
use clap::CommandFactory;
@@ -9,7 +10,9 @@ use colored::Colorize;
use glob::glob;
const MAIN_C: &str = include_str!("templates/main.c");
const GITIGNORE: &str = "target/\ncompile_commands.json\n";
const GITIGNORE: &str = include_str!("templates/.gitignore");
const CLANG_FORMAT: &str = include_str!("templates/.clang-format");
const CLANG_TIDY: &str = include_str!("templates/.clang-tidy");
#[derive(clap::Parser)]
#[clap(version, about)]
@@ -55,7 +58,7 @@ enum Subcommand {
},
/// List available build modes
List,
/// Add a new package to the project (throguh pkg-config)
/// Add a new package to the project (through pkg-config)
Add {
/// The package to be added
package: String,
@@ -68,6 +71,15 @@ enum Subcommand {
#[arg(long, short)]
args: Option<Vec<String>>,
},
/// Removes a package from the project (through pkg-config)
Remove {
/// The package to remove
package: String,
},
/// Format C source code using clang-format
Fmt,
/// Lint C source code using clang-tidy
Lint,
}
#[derive(clap::Subcommand)]
@@ -200,31 +212,92 @@ impl App {
std::process::exit(1);
}
}
Subcommand::Remove { package } => {
if let Err(e) = remove(&package) {
eprintln!("Error removing package {package} from project: {e}");
std::process::exit(1);
}
}
Subcommand::Fmt => {
if let Err(e) = fmt() {
eprintln!("Error formatting project: {e}");
std::process::exit(1);
}
}
Subcommand::Lint => {
if let Err(e) = lint() {
eprintln!("Error linting project: {e}");
std::process::exit(1);
}
}
}
}
}
fn remove(package: &str) -> std::io::Result<()> {
let mut conf = get_config().ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"no Pallet.toml found in local directory",
))?;
let deps = conf.dependencies.get_or_insert_with(Vec::new);
if let Some(index) = deps.iter().position(|dep| dep == package) {
deps.remove(index);
} else {
println!(
" {} package {package} not found in Pallet.toml, no change made",
"Warning".yellow().bold()
);
return Ok(());
}
std::fs::write(
"Pallet.toml",
toml::to_string_pretty(&conf).expect("valid TOML"),
)?;
println!(
" {} removed package {package}",
"Successfully".green().bold()
);
Ok(())
}
fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
use std::io::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
// save terminal state
let mut stdout = std::io::stdout();
// enter alternate screen — this is a separate terminal buffer,
// exiting it restores everything exactly as it was
// enter alternate screen
write!(stdout, "\x1b[?1049h")?;
stdout.flush()?;
// make sure we always restore the terminal, even on panic
let result = watch_inner(mode, args);
// set up ctrl+c handler BEFORE doing anything else
let running = Arc::new(AtomicBool::new(true));
let running_clone = running.clone();
ctrlc::set_handler(move || {
running_clone.store(false, Ordering::SeqCst);
})
.map_err(|e| std::io::Error::other(format!("{e}")))?;
// exit alternate screen
let result = watch_inner(mode, args, running);
// exit alternate screen — guaranteed to run now
write!(stdout, "\x1b[?1049l")?;
stdout.flush()?;
result
}
fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
fn watch_inner(
mode: &Option<String>,
args: Option<Vec<String>>,
running: Arc<std::sync::atomic::AtomicBool>,
) -> std::io::Result<()> {
use notify::{Event, RecursiveMode, Watcher, recommended_watcher};
use std::io::Write;
use std::sync::mpsc;
@@ -294,8 +367,8 @@ fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Res
.checked_sub(Duration::from_secs(1))
.unwrap_or(Instant::now());
loop {
match rx.recv() {
while running.load(Ordering::SeqCst) {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(Ok(event)) => {
let is_relevant = matches!(
event.kind,
@@ -332,10 +405,8 @@ fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Res
}
}
Ok(Err(e)) => eprintln!(" {} watch error: {e}", "Error".red().bold()),
Err(e) => {
eprintln!(" {} channel error: {e}", "Error".red().bold());
break;
}
Err(mpsc::RecvTimeoutError::Timeout) => continue, // check running flag and loop
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
}
@@ -489,6 +560,8 @@ fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
std::fs::write("src/main.c", MAIN_C)?;
std::fs::write(".gitignore", GITIGNORE)?;
std::fs::write(".clang-tidy", CLANG_TIDY)?;
std::fs::write(".clang-format", CLANG_FORMAT)?;
let lossy = pathdir.to_string_lossy();
@@ -739,3 +812,71 @@ fn clean() -> std::io::Result<()> {
Ok(())
}
fn fmt() -> std::io::Result<()> {
let source_files: Vec<PathBuf> = glob("src/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.chain(
glob("src/*.h")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?,
)
.filter_map(|e| e.ok())
.collect();
let status = Command::new("clang-format")
.arg("-i")
.args(&source_files)
.status()
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"clang-format not found — try installing it (e.g. sudo pacman -S clang)",
)
})?;
if !status.success() {
return Err(std::io::Error::other("clang-format failed"));
}
println!(" {} all source files", "Formatted".green().bold());
Ok(())
}
fn lint() -> std::io::Result<()> {
gen_compile_commands(&None)?;
let source_files: Vec<PathBuf> = glob("src/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.filter_map(|e| e.ok())
.collect();
let mut any_warnings = false;
for src in &source_files {
println!(" {} {}", "Linting".green().bold(), src.display());
let status = Command::new("clang-tidy")
.arg(src)
.arg("--use-color")
.status()
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"clang-tidy not found — try installing it (e.g. sudo pacman -S clang)",
)
})?;
if !status.success() {
any_warnings = true;
}
}
if any_warnings {
println!("\n {} lint warnings found", "Warning".yellow().bold());
} else {
println!("\n {} no issues found", "Finished".green().bold());
}
Ok(())
}

View File

@@ -0,0 +1,23 @@
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
BreakBeforeBraces: Attach
BraceWrapping:
AfterFunction: false
AfterControlStatement: false
SpaceAfterCStyleCast: false
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveDeclarations: Consecutive
PointerAlignment: Right
SortIncludes: CaseSensitive
IncludeBlocks: Regroup

View File

@@ -0,0 +1,2 @@
Checks: "clang-diagnostic-*,clang-analyzer-*"
WarningsAsErrors: ""

2
src/templates/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target/
compile_commands.json

View File

@@ -1,7 +1,7 @@
#include <stdio.h>
int main() {
printf("Hello, world!\n");
printf("Hello, world!\n");
return 0;
return 0;
}