cabin new and cabin init

cabin new and cabin init generate a minimal single-package project layout: a cabin.toml, a src/ tree, a public header tree (library scaffolds only), and a .gitignore. Both commands share the same scaffold logic, so the generated bytes match exactly for the same kind / name pair.

  • cabin new <path> creates a new directory at <path> and fills it with the scaffold. <path> must not already exist.
  • cabin init initializes the current directory. The directory may already contain user files; the scaffold never overwrites them.

The default scaffold kind is a binary package (an executable target). Pass --lib to generate a library package (a library target) instead. --bin and --lib are mutually exclusive; passing both produces a clear error and no filesystem mutation.

Binary scaffold (default)

<dest>/
  cabin.toml
  src/
    main.cc
  .gitignore

The manifest declares a single executable target named after the package:

[package]
name = "hello"
version = "0.1.0"

[target.hello]
type = "executable"
sources = ["src/main.cc"]

src/main.cc is a minimal int main that prints a greeting and returns zero. An existing src/main.cc is preserved unchanged so re-running cabin init over a partially scaffolded project never clobbers user code.

Library scaffold (--lib)

<dest>/
  cabin.toml
  include/
    <package>/
      <package>.hpp
  src/
    <package>.cc
  .gitignore

The manifest declares a single library target with a public include/ directory:

[package]
name = "greeter"
version = "0.1.0"

[target.greeter]
type = "library"
sources = ["src/greeter.cc"]
include_dirs = ["include"]

The generated header (include/<package>/<package>.hpp) and the implementation source (src/<package>.cc) define a minimal add(int, int) function inside a namespace derived from the package name. Hyphens in the package name are mapped to underscores when forming the C++ namespace identifier (so hello-world becomes namespace hello_world); the on-disk file paths keep the original package name.

Existing header / source files at those paths are preserved unchanged.

.gitignore

Both cabin new and cabin init create a .gitignore at the destination when one does not already exist. An existing .gitignore is preserved verbatim — Cabin never appends to it or rewrites it.

The generated file ignores Cabin's default build-output directories:

/build/
/dist/

cabin.lock is intentionally not ignored. Cabin recommends committing the lockfile so collaborators and CI converge on the same resolution; see lockfile.md.

Package name

When --name is omitted, the package name is derived from the final path component (cabin new path/to/hello-world produces hello-world). If that component is empty, contains characters outside the allowed alphabet (ASCII letters, digits, _, -), or starts with a ., Cabin falls back to the literal name cabin-package.

Pass --name <name> to override the derived name. Names containing whitespace or unsupported characters are rejected with a clear error.

Errors

cabin new and cabin init produce actionable diagnostics for:

  • --bin used together with --lib;
  • an invalid package name (empty, whitespace, or characters outside the allowed alphabet);
  • a destination that already exists (for cabin new — the message suggests cabin init for that case);
  • a parent directory that does not exist (for cabin new);
  • a pre-existing cabin.toml (rejected; nothing is overwritten).

When validation fails, cabin new removes the destination directory it just created so a partially scaffolded layout does not survive a validation error.