Skip to content

feat: add non-interactive template replacer#9

Merged
sammyjoyce merged 2 commits intomainfrom
feat/noninteractive-replacer
Oct 6, 2025
Merged

feat: add non-interactive template replacer#9
sammyjoyce merged 2 commits intomainfrom
feat/noninteractive-replacer

Conversation

@sammyjoyce
Copy link
Owner

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @sammyjoyce, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a fundamental shift in how project templates are handled, moving from an interactive setup process to a fully automated, non-interactive replacement mechanism. The core purpose is to streamline the instantiation of new projects from a template by programmatically injecting project-specific values and applying necessary string transformations, thereby enhancing efficiency and reducing manual intervention during project setup.

Highlights

  • Complete Rewrite of Replacer Script: The previous interactive setup.sh script has been entirely replaced by a new, non-interactive replacer.sh script. This new script is designed for automated template variable replacement, supporting command-line arguments for dry-runs and verbose output.
  • Enhanced Template Variable Processing: The replacer.sh script now includes sophisticated logic for handling template variables defined in template-vars.json. This includes functions for normalizing keys, rendering templates, and applying various string transformations like snake_case, kebab_case, and PascalCase to variable values.
  • Improved template-vars.json Structure and Replacements: The template-vars.json file has been significantly updated. Variable names are now standardized, the source field has been expanded to a sources array for greater flexibility, and a comprehensive set of special_replacements has been added for files like opencli.json and build.zig.zon, covering a wide range of project metadata and command examples.
  • Dry-Run and Verbose Modes: The new replacer.sh script introduces --dry-run functionality, allowing users to preview the changes that would be made without actually modifying any files. A verbose mode (-v) is also available for detailed logging during the replacement process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a non-interactive template replacer script, replacer.sh, which is a significant enhancement over the previous interactive setup. The script is well-structured, leveraging command-line arguments, external tools like jq and sd, and a JSON configuration file for variables. The accompanying changes to template-vars.json clean up the variable definitions and expand the replacement capabilities. My review focuses on improving the robustness and maintainability of the new shell script, with suggestions to fix a bug in the dry-run functionality, refine string transformations, and make file exclusion more configurable.


if (( dry_run )); then
(( verbose )) && printf '[dry-run] %s: "%s" -> "%s"\n' "$file" "$pattern" "$replacement"
sd --preview -F "$pattern" "$replacement" "$file" >/dev/null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The --dry-run feature is intended to show a preview of changes without writing them. The sd --preview command generates this preview, but its output is being redirected to /dev/null, which prevents the user from seeing it. This redirection defeats the purpose of the preview. Removing it will ensure the dry run feature works as expected.

Suggested change
sd --preview -F "$pattern" "$replacement" "$file" >/dev/null
sd --preview -F "$pattern" "$replacement" "$file"

text=$(printf '%s' "$text" | sed -E 's/([a-z0-9])([A-Z])/\1 \2/g')
text=$(printf '%s' "$text" | tr '[:upper:]' '[:lower:]')
text=$(printf '%s' "$text" | tr -cs '[:alnum:]' ' ')
printf '%s' "$text" | xargs || true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The || true appended to the xargs command is redundant. xargs exits with a status of 0 when it receives no input from stdin, so there's no error to suppress. Removing this will make the code slightly cleaner and rely on standard xargs behavior.

Suggested change
printf '%s' "$text" | xargs || true
printf '%s' "$text" | xargs


to_snake() {
local words="$(to_words "$1")"
[[ -z $words ]] && { printf '\n'; return; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When to_words returns an empty string, this line causes the function to output a newline character (\n). This can lead to unintended newlines being inserted during template replacement. The function should return an empty string for an empty input.

Suggested change
[[ -z $words ]] && { printf '\n'; return; }
[[ -z $words ]] && return


to_kebab() {
local words="$(to_words "$1")"
[[ -z $words ]] && { printf '\n'; return; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When to_words returns an empty string, this line causes the function to output a newline character (\n). This can lead to unintended newlines being inserted during template replacement. The function should return an empty string for an empty input.

Suggested change
[[ -z $words ]] && { printf '\n'; return; }
[[ -z $words ]] && return

error "'$1' is not installed. Please install it to continue."
to_pascal() {
local words="$(to_words "$1")"
[[ -z $words ]] && { printf '\n'; return; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When to_words returns an empty string, this line causes the function to output a newline character (\n). This can lead to unintended newlines being inserted during template replacement. The function should return an empty string for an empty input.

Suggested change
[[ -z $words ]] && { printf '\n'; return; }
[[ -z $words ]] && return

for rel in "${path_list[@]}"; do
[[ -z $rel ]] && continue
rel=${rel#./}
[[ $rel == .code/* ]] && continue

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line hardcodes an exclusion for the .code/ directory. To improve the script's maintainability and configurability, this exclusion should be defined in the template-vars.json file. Please remove this line and add .code/* to the exclude_patterns array in the JSON configuration.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines 1 to 36
{
"variables": {
"PROJECT_NA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationME": {
"placeholders": ["myapp", "cli_starter", "cli-starter", "yourproject"],
"description": "The name of the project/A modern CLI application",
"source": "repository_name",
"validation": "^[a-zA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z][a-zA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z0-9_-]*$"
"PROJECT_NAME": {
"placeholders": ["My CLI App", "CLI Starter"],
"description": "Human-readable project name shown in documentation, metadata, and headings.",
"sources": ["repository_name"],
"validation": "^[A-Za-z][A-Za-z0-9 _-]*$"
},
"PROJECT_NA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationME_SNA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationKE": {
"placeholders": ["cli_starter"],
"description": "Project name in snake_case format",
"source": "repository_name",
"PROJECT_NAME_SNAKE": {
"placeholders": ["my_cli_app", "cli_starter"],
"description": "Project name in snake_case for identifiers and configuration directories.",
"sources": ["repository_name"],
"transform": "snake_case",
"validation": "^[a-z][a-z0-9_]*$"
},
"PROJECT_NA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationME_KEBA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationB": {
"placeholders": ["cli-starter", "cli-starter-c23", "c23-cli-template"],
"description": "Project name in kebab-case format",
"source": "repository_name",
"PROJECT_NAME_KEBAB": {
"placeholders": ["myapp", "my-cli-app", "cli-starter"],
"description": "Project name in kebab-case for CLI commands, binaries, and URLs.",
"sources": ["repository_name"],
"transform": "kebab_case",
"validation": "^[a-z][a-z0-9-]*$"
},
"PROJECT_NA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationME_PA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationSCA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationL": {
"placeholders": ["CliStarter"],
"description": "Project name in PascalCase format",
"source": "repository_name",
"PROJECT_NAME_PASCAL": {
"placeholders": ["MyCliApp", "CliStarter"],
"description": "Project name in PascalCase for types, modules, and title casing.",
"sources": ["repository_name"],
"transform": "pascal_case",
"validation": "^[A A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z][a-zA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z0-9]*$"
"validation": "^[A-Z][A-Za-z0-9]*$"
},
"GITHUB_USERNA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI applicationME": {
"placeholders": ["yourusername", "sammyjoyce"],
"description": "GitHub username or organization",
"source": "repository_owner",
"validation": "^[a-zA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z0-9][a-zA A modern A modern CLI A modern CLI application A modern CLI application A modern CLI A modern CLI application A modern CLI application-Z0-9-]*$"
"PROJECT_DESCRIPTION": {
"placeholders": ["A modern C23 CLI application template."],
"description": "Short project description used in README content and package metadata.",
"sources": ["repository_description"],
"fallback": "A modern C23 CLI application template.",
"required": false
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep setup.sh in sync with template-vars schema

The variables JSON was refactored to live under a nested "variables" object (e.g. PROJECT_NAME at lines 1‑36), but .template/setup.sh still reads defaults with jq -r ".__PROJECT_NAME__" and similar paths. After this change those lookups return null, so running the interactive setup now pre-fills every prompt with null and, if the user accepts the defaults, replaces placeholders across the repo with the literal string null. The setup script needs to read from the new schema or be removed to avoid generating broken projects.

Useful? React with 👍 / 👎.

@sammyjoyce
Copy link
Owner Author

@codex fix

@chatgpt-codex-connector
Copy link

Summary

  • Refined the replacer’s word transformation helpers to avoid emitting stray newlines when values are empty and to surface sd --preview output during dry-run mode. .template/replacer.shL110-L303
  • Moved the .code directory exclusion into the template configuration for easier maintenance. .template/template-vars.jsonL83-L92
  • Updated the interactive setup script to pull defaults from the new variable schema, reuse field descriptions as prompts, and export CURRENT_YEAR automatically. .template/setup.shL56-L93

Testing

  • bash -n .template/replacer.sh
  • bash -n .template/setup.sh

View task →

@sammyjoyce sammyjoyce merged commit 115320b into main Oct 6, 2025
2 of 11 checks passed
@sammyjoyce sammyjoyce deleted the feat/noninteractive-replacer branch October 6, 2025 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant