Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Table of Contents

Gpack Development Guide

This guide covers creating, testing, and publishing gpacks to the ggen marketplace.

Overview

Gpacks are versioned template collections that can be shared across the ggen community. They include:

  • Templates: .tmpl files with YAML frontmatter
  • Macros: Reusable template fragments (.tera files)
  • RDF Graphs: Semantic models and SPARQL queries
  • Tests: Golden tests for validation
  • Dependencies: Other gpacks this gpack depends on

Getting Started

Initialize New Gpack

# Create new gpack
ggen pack init

# This creates:
# ├── ggen.toml          # Gpack manifest
# ├── templates/          # Template directory
# ├── macros/            # Macro directory
# ├── graphs/            # RDF graphs
# ├── tests/             # Test directory
# └── README.md          # Documentation

Gpack Structure

my-gpack/
├── ggen.toml              # Gpack manifest
├── templates/             # Template files
│   └── cli/
│       └── subcommand/
│           ├── rust.tmpl
│           ├── graphs/         # Local RDF data
│           │   ├── cli.ttl
│           │   └── shapes/
│           │       └── cli.shacl.ttl
│           └── queries/        # Local SPARQL queries
│               └── commands.rq
├── macros/                # Reusable fragments
│   └── common.tera
├── tests/                 # Golden tests
│   └── test_hello.rs
├── README.md              # Documentation
└── .gitignore             # Git ignore file

Gpack Manifest (ggen.toml)

Basic Manifest

[gpack]
id = "io.ggen.rust.cli-subcommand"
name = "Rust CLI subcommand"
version = "0.1.0"
description = "Generate clap subcommands"
license = "MIT"
authors = ["Your Name <your.email@example.com>"]
repository = "https://github.com/your-org/your-gpack"
homepage = "https://github.com/your-org/your-gpack"
keywords = ["rust", "cli", "clap"]
ggen_compat = ">=0.2 <0.4"

[dependencies]
"io.ggen.macros.std" = "^0.2"

[templates]
entrypoints = ["cli/subcommand/rust.tmpl"]
includes   = ["macros/**/*.tera"]

[rdf]
base = "http://example.org/"
prefixes.ex = "http://example.org/"
files  = ["templates/**/graphs/*.ttl"]
inline = ["@prefix ex: <http://example.org/> . ex:Foo a ex:Type ."]

Manifest Fields

Required Fields

  • id: Unique identifier (reverse domain notation)
  • name: Human-readable name
  • version: Semantic version
  • description: Brief description
  • license: License identifier
  • ggen_compat: Required ggen version range

Optional Fields

  • authors: List of authors
  • repository: Source repository URL
  • homepage: Project homepage
  • keywords: Search keywords
  • readme: Path to README file
  • changelog: Path to changelog file

Template Development

Template Structure

---
to: "src/cmds/{{ name | snake_case }}.rs"
vars:
  name: "example"
  description: "Example command"
rdf:
  inline:
    - mediaType: text/turtle
      text: |
        @prefix cli: <urn:ggen:cli#> .
        [] a cli:Command ;
           cli:name "{{ name }}" ;
           cli:description "{{ description }}" .
sparql:
  vars:
    - name: slug
      query: |
        PREFIX cli: <urn:ggen:cli#>
        SELECT ?slug WHERE { ?c a cli:Command ; cli:name ?slug } LIMIT 1
determinism:
  seed: "{{ name }}"
---
// Generated by gpack: {{ gpack.id }}
// Template: {{ template.path }}

use clap::Parser;

#[derive(Parser)]
pub struct {{ name | pascal }}Args {
    /// {{ description }}
    #[arg(short, long)]
    pub verbose: bool,
}

pub fn {{ name | snake_case }}(args: {{ name | pascal }}Args) -> Result<(), Box<dyn std::error::Error>> {
    println!("{{ name | pascal }} command executed");
    if args.verbose {
        println!("Verbose mode enabled");
    }
    Ok(())
}

Template Best Practices

  1. Use semantic variable names: name, description, version
  2. Include RDF models: Define semantic structure
  3. Add SPARQL queries: Extract variables from graphs
  4. Include determinism: Use seeds for reproducibility
  5. Add comments: Document generated code
  6. Use filters: Apply transformations (| snake_case, | pascal)

Macro Development

Creating Macros

{#- Common CLI argument structure #}
{% macro cli_args(name, description) %}
#[derive(Parser)]
pub struct {{ name | pascal }}Args {
    /// {{ description }}
    #[arg(short, long)]
    pub verbose: bool,
}
{% endmacro %}

{#- Common error handling #}
{% macro error_handling() %}
-> Result<(), Box<dyn std::error::Error>> {
    // Error handling logic
    Ok(())
}
{% endmacro %}

Using Macros

---
to: "src/cmds/{{ name }}.rs"
---
{% import "macros/common.tera" as common %}

{{ common::cli_args(name, description) }}

pub fn {{ name }}(args: {{ name | pascal }}Args) {{ common::error_handling() }}

RDF Graph Development

Graph Structure

@prefix cli: <urn:ggen:cli#> .
@prefix ex: <http://example.org/> .
@base <http://example.org/> .

ex:Command a cli:Command ;
    cli:name "example" ;
    cli:description "Example command" ;
    cli:subcommands (
        ex:StatusCommand
        ex:ConfigCommand
    ) .

ex:StatusCommand a cli:Command ;
    cli:name "status" ;
    cli:description "Show status" .

ex:ConfigCommand a cli:Command ;
    cli:name "config" ;
    cli:description "Manage configuration" .

SPARQL Queries

PREFIX cli: <urn:ggen:cli#>
PREFIX ex: <http://example.org/>

# Extract command names
SELECT ?name WHERE {
    ?cmd a cli:Command ;
         cli:name ?name .
}

# Extract subcommands
SELECT ?parent ?child WHERE {
    ?parent a cli:Command ;
            cli:subcommands ?child .
    ?child a cli:Command .
}

Testing

Golden Tests

#![allow(unused)]
fn main() {
// tests/test_hello.rs
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hello_command() {
        // Test generated code compiles
        let args = HelloArgs { verbose: false };
        let result = hello(args);
        assert!(result.is_ok());
    }
}
}

Test Configuration

# ggen.toml
[tests]
golden = ["tests/*.rs"]
variables = [
    { name = "test1", description = "Test command 1" },
    { name = "test2", description = "Test command 2" }
]

Running Tests

# Run all tests
ggen pack test

# Run specific test
ggen pack test --test test_hello

# Run with verbose output
ggen pack test --verbose

Linting and Validation

Lint Gpack

# Lint gpack for publishing
ggen pack lint

# Lint specific template
ggen pack lint --template templates/cli/subcommand/rust.tmpl

# Lint with fixes
ggen pack lint --fix

Validation Checks

The linter checks for:

  • Manifest validity: Correct ggen.toml structure
  • Template syntax: Valid YAML frontmatter
  • RDF validity: Well-formed RDF graphs
  • SPARQL syntax: Valid SPARQL queries
  • Dependencies: Resolvable dependencies
  • Versioning: Semantic versioning compliance

Publishing

Prepare for Publishing

# Update version
# Edit ggen.toml:
# version = "0.2.0"

# Run tests
ggen pack test

# Lint gpack
ggen pack lint

# Generate changelog
ggen pack changelog

Publish to Registry

# Publish gpack
ggen pack publish

# Publish with specific version
ggen pack publish --version 0.2.0

# Publish with dry run
ggen pack publish --dry-run

Publishing Process

  1. Validation: Gpack is validated against schema
  2. Testing: Golden tests are run
  3. Linting: Code quality checks
  4. Registry Upload: Gpack is uploaded to registry
  5. Index Update: Registry index is updated
  6. Notification: Community is notified

Versioning

Semantic Versioning

Follow semantic versioning (semver):

  • Major (1.0.0 → 2.0.0): Breaking changes
  • Minor (1.0.0 → 1.1.0): New features
  • Patch (1.0.0 → 1.0.1): Bug fixes

Version Guidelines

  • 0.x.x: Development versions
  • 1.x.x: Stable versions
  • Pre-release: Use -alpha, -beta, -rc suffixes

Changelog

# Changelog

## [0.2.0] - 2024-01-15

### Added
- New CLI subcommand template
- Support for verbose flag
- Error handling macros

### Changed
- Updated RDF model structure
- Improved SPARQL queries

### Fixed
- Template variable resolution
- Macro import issues

## [0.1.0] - 2024-01-01

### Added
- Initial release
- Basic CLI subcommand template

Dependencies

Adding Dependencies

# ggen.toml
[dependencies]
"io.ggen.macros.std" = "^0.2"
"io.ggen.common.rdf" = "~0.1.0"
"io.ggen.rust.cli" = ">=0.1.0 <0.3.0"

Dependency Types

  • Caret (^): Compatible versions (^0.2.0 = >=0.2.0 <0.3.0)
  • Tilde (~): Patch-level changes (~0.1.0 = >=0.1.0 <0.2.0)
  • Exact: Specific version (=0.2.1)
  • Range: Version range (>=0.1.0 <0.3.0)

Dependency Resolution

# Check dependencies
ggen pack deps

# Update dependencies
ggen pack update

# Resolve conflicts
ggen pack resolve

Best Practices

Gpack Design

  1. Single Responsibility: One gpack, one purpose
  2. Consistent API: Use standard variable names
  3. Documentation: Include README and examples
  4. Testing: Comprehensive golden tests
  5. Versioning: Follow semver strictly

Template Quality

  1. Readability: Clear, well-commented code
  2. Maintainability: Modular, reusable templates
  3. Performance: Efficient SPARQL queries
  4. Security: Validate all inputs
  5. Accessibility: Follow language best practices

Community Guidelines

  1. Naming: Use descriptive, consistent names
  2. Licensing: Choose appropriate licenses
  3. Contributing: Welcome community contributions
  4. Support: Provide issue tracking
  5. Updates: Regular maintenance and updates

Troubleshooting

Common Issues

Template Not Found

# Check template path
ggen pack lint --template templates/cli/subcommand/rust.tmpl

# Verify entrypoints in manifest
cat ggen.toml | grep entrypoints

Dependency Conflicts

# Check dependency tree
ggen pack deps --tree

# Resolve conflicts
ggen pack resolve --force

RDF Validation Errors

# Validate RDF graphs
ggen pack lint --rdf-only

# Check SPARQL syntax
ggen pack lint --sparql-only

Test Failures

# Run tests with verbose output
ggen pack test --verbose

# Check test configuration
cat ggen.toml | grep -A 10 "\[tests\]"

Getting Help

  • Documentation: Check this guide and other docs
  • Community: Join ggen community forums
  • Issues: Report bugs and request features
  • Discussions: Ask questions and share ideas

Advanced Topics

Custom Filters

#![allow(unused)]
fn main() {
// Add custom Tera filters
use tera::{Filter, Value, Result};

pub fn custom_filter(value: &Value, _: &HashMap<String, Value>) -> Result<Value> {
    // Custom filter logic
    Ok(value.clone())
}
}

Plugin System

# ggen.toml
[plugins]
"io.ggen.plugin.custom" = "^0.1.0"

CI/CD Integration

# .github/workflows/publish.yml
name: Publish Gpack

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install ggen
        run: cargo install ggen
      - name: Test gpack
        run: ggen pack test
      - name: Lint gpack
        run: ggen pack lint
      - name: Publish gpack
        run: ggen pack publish
        env:
          GGEN_REGISTRY_TOKEN: ${{ secrets.GGEN_REGISTRY_TOKEN }}

This guide provides comprehensive coverage of gpack development, from initial creation to publishing and maintenance. Follow these practices to create high-quality, maintainable gpacks for the ggen community.