8 Commits

Author SHA1 Message Date
9cfcd14e0a add config keys (#31)
Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/31
Co-authored-by: Shea Frembling <sfrembling@gmail.com>
Co-committed-by: Shea Frembling <sfrembling@gmail.com>
2026-03-23 20:49:32 -06:00
130525a868 Increment version 2026-03-23 21:10:28 -05:00
a6734c45ab Fix bug with nested / moduled c files not compiling (#30)
closes #29

Reviewed-on: http://192.168.1.227:3000/sfrembling/pallet/pulls/30
Co-authored-by: Shea Frembling <sfrembling@gmail.com>
Co-committed-by: Shea Frembling <sfrembling@gmail.com>
2026-03-23 20:09:19 -06:00
b14662a666 Update README.md 2026-03-23 17:03:58 -06:00
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
10 changed files with 317 additions and 23 deletions

68
Cargo.lock generated
View File

@@ -84,6 +84,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
@@ -96,6 +105,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.0" version = "4.6.0"
@@ -189,6 +204,17 @@ dependencies = [
"typenum", "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]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -199,6 +225,18 @@ dependencies = [
"crypto-common", "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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -340,6 +378,18 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "notify" name = "notify"
version = "8.2.0" version = "8.2.0"
@@ -367,6 +417,21 @@ dependencies = [
"bitflags 2.11.0", "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]] [[package]]
name = "once_cell_polyfill" name = "once_cell_polyfill"
version = "1.70.2" version = "1.70.2"
@@ -375,12 +440,13 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]] [[package]]
name = "pallet" name = "pallet"
version = "1.0.6" version = "1.1.1"
dependencies = [ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
"clap_complete_nushell", "clap_complete_nushell",
"colored", "colored",
"ctrlc",
"glob", "glob",
"notify", "notify",
"serde", "serde",

View File

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

View File

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

View File

@@ -72,3 +72,11 @@ args = [
"-O3", "-O3",
] ]
``` ```
## Creating Changelogs
Use the following command to create a changelog:
```sh
git log --oneline (git describe --tags --abbrev=0)..HEAD | sed 's/^/- /' | xclip -selection clipboard
```

View File

@@ -2,6 +2,7 @@ use std::{
env::set_current_dir, env::set_current_dir,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
sync::{Arc, atomic::Ordering},
}; };
use clap::CommandFactory; use clap::CommandFactory;
@@ -9,7 +10,9 @@ use colored::Colorize;
use glob::glob; use glob::glob;
const MAIN_C: &str = include_str!("templates/main.c"); 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)] #[derive(clap::Parser)]
#[clap(version, about)] #[clap(version, about)]
@@ -55,7 +58,7 @@ enum Subcommand {
}, },
/// List available build modes /// List available build modes
List, List,
/// Add a new package to the project (throguh pkg-config) /// Add a new package to the project (through pkg-config)
Add { Add {
/// The package to be added /// The package to be added
package: String, package: String,
@@ -68,6 +71,15 @@ enum Subcommand {
#[arg(long, short)] #[arg(long, short)]
args: Option<Vec<String>>, 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)] #[derive(clap::Subcommand)]
@@ -83,6 +95,8 @@ enum UtilSubcommand {
/// The build mode to generate for /// The build mode to generate for
mode: Option<String>, mode: Option<String>,
}, },
/// Generate the config keys for Pallet.toml
GenConfigKeys,
} }
#[derive(Clone, clap::ValueEnum)] #[derive(Clone, clap::ValueEnum)]
@@ -181,6 +195,15 @@ impl App {
std::process::exit(1); std::process::exit(1);
} }
} }
UtilSubcommand::GenConfigKeys => {
println!(
"{} a property with a '?' indicates that the property is optional",
"Hint".yellow().bold()
);
for (id, desc, dt) in crate::config::Config::get_config_syntax() {
println!(" - {} ({}): {desc}", id.green().bold(), dt.blue().bold());
}
}
}, },
Subcommand::List => { Subcommand::List => {
if let Err(e) = list() { if let Err(e) = list() {
@@ -200,31 +223,92 @@ impl App {
std::process::exit(1); 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<()> { fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
use std::io::Write; use std::io::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
// save terminal state
let mut stdout = std::io::stdout(); 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")?; write!(stdout, "\x1b[?1049h")?;
stdout.flush()?; stdout.flush()?;
// make sure we always restore the terminal, even on panic // set up ctrl+c handler BEFORE doing anything else
let result = watch_inner(mode, args); 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")?; write!(stdout, "\x1b[?1049l")?;
stdout.flush()?; stdout.flush()?;
result 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 notify::{Event, RecursiveMode, Watcher, recommended_watcher};
use std::io::Write; use std::io::Write;
use std::sync::mpsc; use std::sync::mpsc;
@@ -294,8 +378,8 @@ fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Res
.checked_sub(Duration::from_secs(1)) .checked_sub(Duration::from_secs(1))
.unwrap_or(Instant::now()); .unwrap_or(Instant::now());
loop { while running.load(Ordering::SeqCst) {
match rx.recv() { match rx.recv_timeout(Duration::from_millis(100)) {
Ok(Ok(event)) => { Ok(Ok(event)) => {
let is_relevant = matches!( let is_relevant = matches!(
event.kind, event.kind,
@@ -332,10 +416,8 @@ fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Res
} }
} }
Ok(Err(e)) => eprintln!(" {} watch error: {e}", "Error".red().bold()), Ok(Err(e)) => eprintln!(" {} watch error: {e}", "Error".red().bold()),
Err(e) => { Err(mpsc::RecvTimeoutError::Timeout) => continue, // check running flag and loop
eprintln!(" {} channel error: {e}", "Error".red().bold()); Err(mpsc::RecvTimeoutError::Disconnected) => break,
break;
}
} }
} }
@@ -411,7 +493,7 @@ fn gen_compile_commands(mode: &Option<String>) -> std::io::Result<()> {
let cwd = std::env::current_dir()?; let cwd = std::env::current_dir()?;
let obj_dir = format!("target/{}/obj", build_config.name); let obj_dir = format!("target/{}/obj", build_config.name);
let source_files: Vec<PathBuf> = glob("src/*.c") let source_files: Vec<PathBuf> = glob("src/**/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))? .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.collect(); .collect();
@@ -489,6 +571,8 @@ fn create_project<P: AsRef<Path>>(directory: P) -> std::io::Result<()> {
std::fs::write("src/main.c", MAIN_C)?; std::fs::write("src/main.c", MAIN_C)?;
std::fs::write(".gitignore", GITIGNORE)?; 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(); let lossy = pathdir.to_string_lossy();
@@ -592,7 +676,7 @@ fn build(mode: &Option<String>, force_recompile: bool) -> std::io::Result<()> {
.collect(); .collect();
} }
let source_files: Vec<PathBuf> = glob("src/*.c") let source_files: Vec<PathBuf> = glob("src/**/*.c")
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))? .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, format!("{e}")))?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.collect(); .collect();
@@ -739,3 +823,71 @@ fn clean() -> std::io::Result<()> {
Ok(()) 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

@@ -35,6 +35,46 @@ impl Config {
self.build.iter().find(|bc| bc.name == self.default_build) self.build.iter().find(|bc| bc.name == self.default_build)
} }
} }
pub fn get_config_syntax() -> Vec<(String, String, String)> {
vec![
(
"compiler?",
"the compiler used by Pallet (defeault=gcc)",
"string",
),
("name", "the name of your application", "string"),
(
"default_build",
"the name of the build to use by default when running or building",
"string",
),
(
"description?",
"a brief description of your application",
"string",
),
("version?", "the version of your application", "string"),
(
"authors?",
"the author or authors of your application",
"string[]",
),
(
"build",
"a build config that can be used when compiling",
"{name: string, args: string[]}[]",
),
(
"dependencies",
"libraries (such as zlib) used by your application that require pkg-config to be compiled correclty",
"string[]"
)
]
.into_iter()
.map(|(a, b, c)| (a.to_owned(), b.to_owned(), c.to_owned()))
.collect()
}
} }
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]

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