5c08eb8c 2023-02-18 Pavel Safronov | 1 | # README |
| 2 | |
| 3 | ## Name |
| 4 | |
| 5 | template |
| 6 | |
| 7 | ## Description |
| 8 | |
74911329 2023-04-10 Pavel Safronov | 9 | template is a command-line interface (CLI) tool that automates the creation of files and folders with pre-defined names and contents, so you can focus on your work and not on repetitive setup tasks. |
5c08eb8c 2023-02-18 Pavel Safronov | 10 | |
0621dedc 2026-01-15 Pavel Safronov | 11 | ## Example |
| 12 | |
| 13 | For this example, pretend that today is April 14, 2025. |
| 14 | |
| 15 | ```sh |
| 16 | $ template daily "Cool Project" |
| 17 | Profile: daily |
| 18 | Project name: Cool Project |
| 19 | Loaded config from /Users/pavel/.template.yaml |
| 20 | Loaded user config from /Users/pavel/.template.user.yaml, combining with global config |
| 21 | Creating file: /Users/pavel/DailyNotes/2025-04-04-Cool Project.md |
| 22 | Executing post script: sh -c code . "/Users/pavel/DailyNotes/2025-04-04-Cool Project.md" |
| 23 | Post script had no output |
| 24 | ``` |
| 25 | |
| 26 | ## Motivation |
| 27 | |
74911329 2023-04-10 Pavel Safronov | 28 | As developers and content creators, we often need to create files and folders with specific naming conventions, such as time-stamped file names, project-specific prefixes, and consistent suffixes. We also frequently need to populate these files with pre-defined contents, such as boilerplate code or templates for documentation. These tasks can be tedious and error-prone when done manually, especially when working on multiple projects or collaborating with others. |
| 29 | |
| 30 | template simplifies these tasks by providing a flexible and customizable way to create files and folders with consistent naming and content. With template, you can define your own naming patterns, file templates, and directory structures, or use pre-configured profiles that come with the tool. You can also pass in parameters at runtime to customize the output further, such as including a project name or arbitrary template data. |
| 31 | |
| 32 | template is built in Go and uses YAML and sprig templates for configuration. It supports creating both files and directories from predefined templates, as well as piped input for quick creation of files with custom content. With template, you can spend more time on your work and less time on setup and organization. |
| 33 | |
| 34 | ## Usage |
| 35 | |
| 36 | To use template, you can run the template command followed by a profile name, parameters, and options. For example: |
| 37 | |
| 38 | ```sh |
| 39 | template daily "Cool Project" |
| 40 | ``` |
| 41 | |
| 42 | This will create a new file in the format YYYY-MM-DD-cool_project.md in the ~/DailyNotes directory, with pre-defined contents. |
| 43 | |
| 44 | You can also define your own profiles in the ~/.template.yaml file, or specify file, directory, and script templates directly at runtime using the `-f`, `-d`, and `-s` options. |
5c08eb8c 2023-02-18 Pavel Safronov | 45 | |
| 46 | ### Sample tool config |
| 47 | |
| 48 | This is one of the profiles that comes with the tool. |
| 49 | |
| 50 | ```yaml |
| 51 | - name: daily |
| 52 | description: Creates a MD file in ~/DailyNotes folder in the format YYYY-MM-DD[-vX].md |
| 53 | isdefault: false |
| 54 | isdisabled: false |
| 55 | type: file |
| 56 | runpostscriptonfailure: true |
| 57 | postscripts: |
| 58 | - code . {{ if .FullPath }}"{{.FullPath}}"{{else}}(ls -t | head -n1){{ end }} |
| 59 | namepatterns: |
2105a1d7 2023-04-02 Pavel Safronov | 60 | - '{{$version := getVersion}}{{.YYYY_MM_DD}}{{if .ProjectName}}-{{.ProjectName | snakecase }}{{end}}{{if (gt $version 1)}}v{{$version}}{{end}}.md' |
5c08eb8c 2023-02-18 Pavel Safronov | 61 | targetdirectory: ~/DailyNotes |
| 62 | filetemplate: |- |
| 63 | # {{.FileNameWithoutExtension}} |
| 64 | |
2105a1d7 2023-04-02 Pavel Safronov | 65 | Today's notes for {{.YYYY_MM_DD}}. |
5c08eb8c 2023-02-18 Pavel Safronov | 66 | |
| 67 | ## Todo |
| 68 | |
| 69 | 1. Check email |
| 70 | 2. Check chat |
| 71 | 3. Check calendar |
| 72 | 4. Check yesterday's notes |
| 73 | 5. Have fun! |
| 74 | 6. ADD MORE ITEMS HERE |
| 75 | ``` |
| 76 | |
| 77 | Using the above configuration: |
| 78 | |
| 79 | ```sh |
| 80 | > $ template daily "Cool Thought" |
| 81 | Profile: daily |
| 82 | Project name: Cool Thought |
5e132930 2023-03-29 Pavel Safronov | 83 | Loaded config from /Users/pavel/.template.yaml |
| 84 | Loaded user config from /Users/pavel/.template.user.yaml, combining with global config |
| 85 | Creating file: /Users/pavel/DailyNotes/2022-11-20-cool_thought.md |
| 86 | Executing post script: sh -c code . "/Users/pavel/DailyNotes/2022-11-20-cool_thought.md" |
5c08eb8c 2023-02-18 Pavel Safronov | 87 | Post script had no output |
| 88 | ``` |
| 89 | |
| 90 | This will cause VSCode to open the `~\DailyNotes` folder as a workspace, and the newly-created file `2022-11-20-cool_thought.md` will be opened as well. |
| 91 | |
| 92 | The above example also uses the [`snakecase`](https://github.com/Masterminds/sprig/blob/master/docs/strings.md#user-content-snakecase) template function. |
| 93 | This is one of the [sprig template functions](https://github.com/Masterminds/sprig/blob/master/docs/index.md) that are supported by template. |
| 94 | |
| 95 | File-based profiles (`type: file`) can accept piped input, and this input will be piped to the created file. |
| 96 | In this instance, the profile's `FileTemplate` field is ignored. |
| 97 | Here's an example of piping the results of `ps` to a file with an auto-generated name: |
| 98 | |
| 99 | ```sh |
| 100 | > $ echo "$(ps)" | template log "ps-info" |
| 101 | Profile: log |
| 102 | Project name: ps-info |
5e132930 2023-03-29 Pavel Safronov | 103 | Loaded config from /Users/pavel/.template.yaml |
| 104 | Loaded user config from /Users/pavel/.template.user.yaml, combining with global config |
5c08eb8c 2023-02-18 Pavel Safronov | 105 | Creating file: 2022-12-10-00-05-21-ps-info.log |
| 106 | Executing post script: sh -c code . 2022-12-10-00-05-21-ps-info.log |
| 107 | Post script had no output |
| 108 | ``` |
| 109 | |
| 110 | Note that user profiles are combined with the default ones. |
| 111 | This is so you can run `template reset` to reset `~/.template.yaml`, but keep your local profiles/changes as-is. |
| 112 | |
8fc7c452 2023-03-29 Pavel Safronov | 113 | ### Using a file or folder template directly |
| 114 | |
| 115 | You can also specify a file or folder template during execution, without changing any configs. |
| 116 | |
| 117 | Creating a file: |
| 118 | |
| 119 | ```sh |
| 120 | template -f "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt" "project name" |
| 121 | ``` |
| 122 | |
| 123 | Creating a folder: |
| 124 | |
| 125 | ```sh |
| 126 | template -d "{{.Year}}-{{.Month}}-{{.Day}}-{{.ProjectName}}.txt" Test123 |
| 127 | ``` |
| 128 | |
fd0a4446 2023-04-03 Pavel Safronov | 129 | ### Using a script template directly |
| 130 | |
| 131 | You can also specify a template script: |
| 132 | |
| 133 | ```sh |
| 134 | template --script "echo 'Hello from {{.YYYY_MM_DD}}!'" |
| 135 | ``` |
| 136 | |
| 137 | will result in: |
| 138 | |
| 139 | ```txt |
| 140 | > template --script "echo 'Hello from {{.YYYY_MM_DD}}!'" |
| 141 | Project name: |
| 142 | Loaded config from C:\Users\pavel\.template.yaml |
| 143 | Loaded user config from C:\Users\pavel\.template.user.yaml, combining with global config |
| 144 | Executing post script: cmd /c echo 'Hello from 2023-04-02!' |
| 145 | Post script output: 'Hello from 2023-04-02!' |
| 146 | |
| 147 | ``` |
| 148 | |
5c08eb8c 2023-02-18 Pavel Safronov | 149 | ## Examples |
| 150 | |
| 151 | ### Use `daily` profile to create a new file |
| 152 | |
| 153 | ```sh |
| 154 | template daily |
| 155 | ``` |
| 156 | |
| 157 | ### Use `daily` profile to create a new file with a project name |
| 158 | |
| 159 | ```sh |
| 160 | template daily testing |
| 161 | ``` |
| 162 | |
| 163 | ### Use `daily` profile to create a new file with a project name that contains spaces |
| 164 | |
| 165 | ```sh |
| 166 | template daily "this is a test" |
| 167 | ``` |
| 168 | |
| 169 | ### Use `daily` profile to create a new file with contents piped in from `ps` |
| 170 | |
| 171 | ```sh |
| 172 | echo "$(ps)" | template daily |
| 173 | ``` |
| 174 | |
| 175 | ### Use `work` profile to create a folder with multiple files inside it |
| 176 | |
| 177 | ```sh |
| 178 | template work |
| 179 | ``` |
| 180 | |
| 181 | ### Use `work` profile and project name to create a folder with multiple files inside it |
| 182 | |
| 183 | ```sh |
| 184 | template work "fix-bug-123" |
| 185 | ``` |
| 186 | |
| 187 | ### Use `log` profile to create a `.log` from `stderr` and `stdout` from a `curl` command |
| 188 | |
| 189 | ```sh |
| 190 | curl -X GET "https://httpbin.org/delay/3" -H "accept: application/json" 2>&1 | template log |
| 191 | ``` |
| 192 | |
8fc7c452 2023-03-29 Pavel Safronov | 193 | ### Specify file pattern and project name |
| 194 | |
| 195 | ```sh |
fd0a4446 2023-04-03 Pavel Safronov | 196 | template -file "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.ProjectName}}.txt" test |
8fc7c452 2023-03-29 Pavel Safronov | 197 | ``` |
| 198 | |
| 199 | ### Specify file pattern, project name, and piped input |
| 200 | |
| 201 | ```sh |
fd0a4446 2023-04-03 Pavel Safronov | 202 | history | template -file "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.ProjectName}}.txt" "Hi all!" |
8fc7c452 2023-03-29 Pavel Safronov | 203 | ``` |
| 204 | |
| 205 | ### Specify folder pattern and project name |
| 206 | |
| 207 | ```sh |
fd0a4446 2023-04-03 Pavel Safronov | 208 | template -dir "{{.Year}}-{{.Month}}-{{.Day}}-{{.ProjectName}}" test123 |
8fc7c452 2023-03-29 Pavel Safronov | 209 | ``` |
| 210 | |
5c08eb8c 2023-02-18 Pavel Safronov | 211 | ## Requirements |
| 212 | |
| 213 | The application is a single executable, there are no special requirements. |
| 214 | |
| 215 | Building application requires [go](https://en.wikipedia.org/wiki/Go_(programming_language)). You can download the installer [here](https://go.dev/doc/install). |
| 216 | |
| 217 | The build is managed by [make](https://www.gnu.org/software/make/). |
| 218 | |
| 219 | The default profiles use [Visual Studio Code](https://code.visualstudio.com/) to open the generated files or folders. |
| 220 | |
| 221 | ## Installation with Homebrew |
| 222 | |
| 223 | ```sh |
| 224 | brew tap PavelSafronov/template https://gitlab.com/PavelSafronov/template.git |
| 225 | brew install template |
| 226 | template reset |
| 227 | ``` |
| 228 | |
| 229 | ### Uninstall with Homebrew |
| 230 | |
| 231 | ```sh |
| 232 | brew uninstall template |
| 233 | brew untap PavelSafronov/template |
| 234 | ``` |
| 235 | |
d3c36d20 2023-02-18 Pavel Safronov | 236 | ## Installation with Go |
| 237 | |
| 238 | If your Go installation is [properly configured](https://www.geeksforgeeks.org/how-to-install-a-package-with-go-get/), you can install `template` as a go module: |
| 239 | |
| 240 | ```sh |
| 241 | go install gitlab.com/PavelSafronov/template@latest |
| 242 | ``` |
| 243 | |
2105a1d7 2023-04-02 Pavel Safronov | 244 | ### Uninstall with go |
| 245 | |
| 246 | ```sh |
| 247 | go clean -i gitlab.com/PavelSafronov/template |
| 248 | ``` |
| 249 | |
| 250 | ## Manual installation |
5c08eb8c 2023-02-18 Pavel Safronov | 251 | |
| 252 | 1. Navigate to [Releases](https://gitlab.com/PavelSafronov/template/-/releases) |
| 253 | 2. Choose the latest release |
| 254 | 3. Download the archive file `template_VERSION_OS.tar.gz` |
| 255 | 4. From the archive file, extract `template` to a location that's in your PATH |
| 256 | |
| 257 | ## Installation with source |
| 258 | |
| 259 | Clone the repo and run these commands from the repo directory: |
| 260 | |
| 261 | ```sh |
| 262 | make install |
0621dedc 2026-01-15 Pavel Safronov | 263 | ``` |
| 264 | |
| 265 | If this is your first installation or you want to reset your config to the default, run: |
| 266 | |
| 267 | ```sh |
5c08eb8c 2023-02-18 Pavel Safronov | 268 | template reset |
| 269 | ``` |
| 270 | |
| 271 | ### Uninstall with source |
| 272 | |
| 273 | To uninstall, run this command from the repo directory: |
| 274 | |
| 275 | ```sh |
| 276 | make uninstall |
| 277 | ``` |
| 278 | |
| 279 | ## Running after installation |
| 280 | |
| 281 | - `template` - uses default profile "note" to create a date-stamped MD file in current folder |
| 282 | - `template blog` OR `template --profile blog` OR `template -p blog` - creates a date-stamped MD file in ~/Blog, opens in VSCode |
| 283 | - `template daily topic` OR `template --profile daily --name topic` OR `template -p daily -n topic` - creates a templated date-stamped MD file with `topic` in its name in ~/DailyNotes, opens in VSCode |
| 284 | - `template list` - lists all the found profiles |
| 285 | - `template get profile2` - lists detailed information about profile `profile2` |
05c30431 2026-01-16 Pavel Safronov | 286 | - `template init` - if global template config is not present, initializes the global template config with default profiles |
5c08eb8c 2023-02-18 Pavel Safronov | 287 | - `template reset` - resets the configuration to the default config |
| 288 | - *Note*: this will overwrite whatever config changes you may have made |
| 289 | - *Note*: the defaults may change from version to version |
| 290 | |
| 291 | ## Creating symbolic links |
| 292 | |
| 293 | template currently refers to paths under `~`. This is to ensure that it works on all systems. |
| 294 | |
| 295 | But I personally keep files in Dropbox, so I usually create symbolic links from (for example) `~/Blog` -> `~/Dropbox/Blog`. Here are the shell commands that I use, maybe they will be helpful for you. :) |
| 296 | |
bd9c08be 2023-08-29 Pavel Safronov | 297 | PowerShell commands to create symbolic links: |
| 298 | |
5c08eb8c 2023-02-18 Pavel Safronov | 299 | ```powershell |
bd9c08be 2023-08-29 Pavel Safronov | 300 | New-Item -ItemType Junction -Target (Resolve-Path "~\Dropbox\Blog") -Path "~\Blog\" |
| 301 | New-Item -ItemType Junction -Target (Resolve-Path "~\Dropbox\DailyNotes") -Path "~\DailyNotes\" |
| 302 | New-Item -ItemType Junction -Target (Resolve-Path "~\Dropbox\Notes") -Path "~\Notes\" |
| 303 | New-Item -ItemType Junction -Target (Resolve-Path "~\Dropbox\Therapy") -Path "~\Therapy\" |
| 304 | New-Item -ItemType Junction -Target (Resolve-Path "~\Dropbox\Work") -Path "~\Work\" |
5c08eb8c 2023-02-18 Pavel Safronov | 305 | ``` |
| 306 | |
bd9c08be 2023-08-29 Pavel Safronov | 307 | Bash commands to create symbolic links: |
| 308 | |
5c08eb8c 2023-02-18 Pavel Safronov | 309 | ```bash |
| 310 | ln -s ~/Dropbox/Blog ~/Blog |
| 311 | ln -s ~/Dropbox/DailyNotes ~/DailyNotes |
| 312 | ln -s ~/Dropbox/Notes ~/Notes |
| 313 | ln -s ~/Dropbox/Therapy ~/Therapy |
| 314 | ln -s ~/Dropbox/Work ~/Work |
| 315 | ``` |
| 316 | |
| 317 | ## Dev loop |
| 318 | |
| 319 | A simple way to get started is to load this directory in VSCode and debug the project using one of the prepared launch tasks. Use command `View: Show Run and Debug` to display the launch configs, then pick one and press "Start Debugging" button. |
| 320 | |
| 321 | The following command will build the package, test it, and then install it: |
| 322 | |
| 323 | ```sh |
| 324 | make install |
| 325 | ``` |
| 326 | |
8fc7c452 2023-03-29 Pavel Safronov | 327 | I use this approach, but that's because I use `template` multiple times a day, and find it helpful be using the latest version. |
5c08eb8c 2023-02-18 Pavel Safronov | 328 | |
| 329 | ## Support |
| 330 | |
| 331 | For anything, open an issue in this repo. |
| 332 | |
8fc7c452 2023-03-29 Pavel Safronov | 333 | ## Road map |
5c08eb8c 2023-02-18 Pavel Safronov | 334 | |
| 335 | I'm going to use this tool daily to figure out how it can be improved. |
| 336 | |
| 337 | While I am the only user, this app will stay at version `0.x.y`. |
| 338 | At this point, breaking changes may happen. |
| 339 | |
| 340 | I'll try to use feature branches, but main will sometimes be in a broken state. |
| 341 | Stable versions are tagged after I've had time to test it, so picking up those should be safe. |
| 342 | |
| 343 | Once there are enough other users and enough new feature requirements are added, we'll transition to version `1.x.y`. |
| 344 | |
| 345 | ### Basic features |
| 346 | |
| 347 | Here are the basic features of the CLI that I want to keep: |
| 348 | |
8fc7c452 2023-03-29 Pavel Safronov | 349 | 1. Cross-platform support |
5c08eb8c 2023-02-18 Pavel Safronov | 350 | 2. CLI is implemented in Go |
| 351 | 3. Fast (under 1s) basic operation of templating file and folder names |
| 352 | 1. Postscripts can take longer, as long as the "basic operations" finish quickly enough. |
| 353 | |
| 354 | ### KISS |
| 355 | |
| 356 | "Keep It Simple, Stupid" |
| 357 | |
| 358 | I want to keep this CLI simple. To that extent, I'm not interested in adding too many features. My main requirement right now is: `template blog topic` should do the same thing as `template --profile blog --name topic`. |
| 359 | |
2105a1d7 2023-04-02 Pavel Safronov | 360 | ### Makefile and VSCode |
5c08eb8c 2023-02-18 Pavel Safronov | 361 | |
| 362 | Good guidance is invaluable, IMO, so to this extent I am very bullish about adding and using descriptive Makefile and VS Code launch tasks. |
| 363 | |
| 364 | Makefile should be used for any build/test/release task. If there is a reason to run a task more than once *in the lifetime of a project*, add the task to the Makefile with a descriptive name. |
| 365 | I don't want to see complicated steps embedded in READMEs or (worse) in code. |
| 366 | If the task is important, one that a maintainer/developer/user *may* want to do multiple times, add it to Makefile. |
| 367 | |
| 368 | Similarly, if there is a good VSCode debug scenario, add a task to launch.json. |
| 369 | |
| 370 | ### Future Features |
| 371 | |
| 372 | Here is the future feature list: |
| 373 | |
| 374 | 1. Publish Chocolatey version of app, possibly using [this GoReleaser Chocolatey documentation](https://goreleaser.com/customization/chocolatey/) |
| 375 | 2. Support for passing in a list of template values for custom values in the template. Use might look like this: `template work form123 --id=456 --corpOffice=SEA`. The passed-in extra values will be placed somewhere inside the template. Some value keys will not be allowed, like "time". |
de118dc8 2023-04-20 Pavel Safronov | 376 | 3. Add `template gen` to create templates from existing files/folders |
| 377 | 4. Add `template check` to verify that a given file matches a specific template |
5c08eb8c 2023-02-18 Pavel Safronov | 378 | |
2105a1d7 2023-04-02 Pavel Safronov | 379 | ### Go module |
| 380 | |
| 381 | This project is mainly a CLI app written in Go, but it's also a Go module, so it can be used as a dependency. |
| 382 | I'm not entirely sure why anyone would want to use this module instead of the CLI, but it's there. |
| 383 | Please keep in mind that since my focus is on the CLI, CLI bugs and features will always be prioritized higher than module bugs and features. |
| 384 | |
5c08eb8c 2023-02-18 Pavel Safronov | 385 | ## Contributing |
| 386 | |
| 387 | Contributions are welcome, but unsolicited PRs are not. |
| 388 | If you want to add a particular feature, open an issue first and suggest it. |
| 389 | We'll discuss the feature and if I agree that it should be added, you can open a PR. |
| 390 | |
| 391 | I don't want to waste my time or your time. |
| 392 | |
| 393 | ## Authors and acknowledgment |
| 394 | |
| 395 | Your name will be listed here if you contribute. |
| 396 | |
| 397 | ## License |
| 398 | |
| 399 | MIT license. |
| 400 | |
| 401 | ## Project status |
| 402 | |
| 403 | This was a hackathon project to learn Go. |
| 404 | I'm going to be using this tool and upgrading it as I see fit. |