Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# JetBrains/GoLand
/.idea
205 changes: 203 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,203 @@
# frosting
Enhance the operational tasks of your application with a little _frosting_

<h1 align="center">
<br>
<a href="http://github.com/cakehappens/frosting"><img src="./assets/cupcake.png" alt="playing card" width="200px" /></a>
<br>
Frosting
<br>
</h1>

<h4 align="center">Enhance the operational tasks of your application with a little <i>frosting</i> 🧁</h4>

<p align="center">
<a href="https://pkg.go.dev/github.com/cakehappens/frosting">
<img src="https://img.shields.io/badge/godoc-reference-5272B4.svg">
</a>
<!-- <a href="https://goreportcard.com/badge/github.com/cakehappens/frosting">
<img src="https://goreportcard.com/report/github.com/cakehappens/frosting">
</a> -->
<a href="https://saythanks.io/to/ghostsquad">
<img src="https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg">
</a>
<a href="buymeacoff.ee/50onA1pjc">
<img src="https://img.shields.io/badge/buymeacoffee-%24-orange">
</a>
</p>

<p align="center">
<a href="#introduction">Introduction</a> •
<a href="#install">Install</a> •
<a href="#how-to-use">How To Use</a> •
<a href="#credits">Credits</a> •
<a href="#support">Support</a> •
<a href="#related">Related</a> •
<a href="#license">License</a>
</p>

## 👋 Introduction

`frosting` is library that lets you quickly and easily create a CLI for your code repositories (like how a `Makefile` enables you to run `make build`). Inspired by [Make][make], [Mage][mage], [Task][taskfile] and others.

## 🎯 Features

| Feature | Frost | Make | Mage | Go-Task |
|----------------------------------|-------|------|------|---------|
| *File | Go | Make | Go | Yaml |
| Bash Support | 🧁 | 🐮 | 🧙 | 🐹 |
| Target-Specific Vars | 🧁 | 🐮 | 🧙 | 🐹 |
| Namespaces | 🧁 | 🐮 | | 🐹 |
| Imported Targets | 🧁 | 🐮 | 🧙 | 🐹‡ |
| Bash/Zsh Autocomplete | 🧁 | 🐮 | | 🐹 |
| Parallelism | 🧁 | | 🧙† | |
| No Custom DSL to Learn | 🧁 | | 🧙 | |
| Target Args | 🧁 | | | |
| Target Flags | 🧁 | | | |
| Target-Specific Help | 🧁 | | | |
| [Color Support][color] | 🧁 | | | |
| Doc Generation | 🧁 | | | |
| [Interative Terminal UI][tview] | 🧁 | | | |
| [go-prompt][gpt] Integration | 🧁 | | | |
| [Progress Bars][pb] | 🧁 | | | |
| [Spinners][spin] | 🧁 | | | |

† Yes, but with some limitations ([#273](https://github.com/magefile/mage/pull/273))

‡ Task support for imports is still experimental

## 💡 Philosophy

Mage puts it nicely in regards to make/bash:

> Makefiles are hard to read and hard to write. Mostly because makefiles are essentially fancy bash scripts with significant white space and additional make-related syntax. Go is superior to bash for any non-trivial task involving branching, looping, anything that’s not just straight line execution of commands.

Mage makes heavy use of code generation in order to create the resulting binary, and I found that to be a high barrier to entry for contributing to the project.

I've been itching to write my own CLI for awhile now, and I think I finally landed on a great use case.

## ⚡️ Quickstart

```go
package main

import (
"context"
"fmt"
"os"

"github.com/cakehappens/frosting"
)

func NewBuildIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient {
return frosting.MustNewIngredient(
name,
func(ctx context.Context, ing *frosting.Ingredient) error {
fmt.Println("Building...")
return nil
},
append([]frosting.IngredientOption{
frosting.WithHelpDescriptions("buildShort", "buildLong"),
}, options...)...,
)
}

func NewTestIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient {
return frosting.MustNewIngredient(
name,
func(ctx context.Context, ing *frosting.Ingredient) error {
fmt.Println("Testing...")
return nil
},
options...,
)
}

func main() {
f := frosting.New("frost")

{
build := NewBuildIngredient("build")

// test depends on build
test := NewTestIngredient(
"test",
frosting.WithDependencies(build),
)

f.Group(
"Basic Commands (Beginner):",
build,
test,
)
}

f.Execute(os.Args[1:]...)
}


```

```bash
go build -o frost
frost # prints help
frost build # runs build ingredient
frost build --help # prints build-target help
```

## 🎓 Docs

see [docs](docs) for more info, or look at the [godocs](https://pkg.go.dev/github.com/cakehappens/frosting)

## 👀 Examples

see [examples](examples)

## 🌟 Contribute

I'll definitely get some templates/guidelines setup soon...

## 🤗 Support

<a href="buymeacoff.ee/50onA1pjc">
<img src="https://img.shields.io/badge/buymeacoffee-%24-orange">
</a>

## 📖 Reading

- https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables
- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
- https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
- https://dave.cheney.net/tag/logging
- https://stackoverflow.com/questions/2214575/passing-arguments-to-make-run

## 💕 Related & Inspiration

- [Mage][mage]
- [Taskfile][taskfile]
- [Make][make]
- [Makem.sh][https://github.com/alphapapa/makem.sh]

## 📜 Credits

- [spf13/cobra][cobra]
- [spf13/viper][viper]
- [tivo/tview][tview]
- [theckman/yackspin][spin]
- [c-bata/go-prompt][gpt]
- [gosuri/uiprogress][pb]
- [fatih/color][color]
- Icons made by [Freepik](https://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)

## ⚖️ License

[Apache License, Version 2.0, http://www.apache.org/licenses/](LICENSE)

[make]: https://www.gnu.org/software/make/
[taskfile]: https://taskfile.dev/
[mage]: https://magefile.org/
[cobra]: https://github.com/spf13/cobra
[viper]: https://github.com/spf13/viper
[gpt]: https://github.com/c-bata/go-prompt
[spin]: https://github.com/theckman/yacspin
[pb]: https://github.com/gosuri/uiprogress
[color]: https://github.com/fatih/color
[tview]: https://github.com/rivo/tview
Binary file added assets/cupcake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package frosting

import "github.com/spf13/cobra"

type defaultCommandBuilder struct{}

func newSimpleCommandBuilder() *defaultCommandBuilder {
return &defaultCommandBuilder{}
}

type CommandBuilder interface {
Build(ingredient *Ingredient) (*cobra.Command, error)
}

func (c *defaultCommandBuilder) Build(ingredient *Ingredient) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: ingredient.name,
Short: ingredient.short,
Long: ingredient.long,
Example: ingredient.example,
Aliases: ingredient.aliases,
}

if ingredient.flagsFn != nil {
ingredient.flagsFn(cmd.Flags())
}

return cmd, nil
}
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Docs
64 changes: 64 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Architecture

## Quick Start

`frosting` is made up of `Ingredient`s (pun intended). An ingredient is analogous to a `target` in a make file.

### Library Code Example

📌 _Let the caller define the name of the target_

🏷️ _Ingredients are ** **globally unique by name** **, to allow different ingredients to have the same dependencies, but ensuring we don't call the dependent ingredient more than once._

```go
func NewBuildIngredient(name string, options ...frosting.IngredientOption) *frosting.Ingredient {
return frosting.MustNewIngredient(
name,
func(ctx context.Context, ing *frosting.Ingredient) error {
fmt.Println("Building...")
return nil
},
append([]frosting.IngredientOption{
frosting.WithHelpDescriptions("buildShort", "buildLong"),
}, options...)...,
)
}
```

Next, mix ingredients into frosting by adding them to a group. This provides a nice interface for getting help about related ingredients.
Ingredients are still ** **globally unique by name** ** regardless of group.

```
$ frost help
Add a little frosting to your operational tasks..

Basic Commands (Beginner):
build Build Things
test Test Things
```

```go
func main() {
f := frosting.New("frost")

{
build := NewBuildIngredient("build")

// test depends on build
test := NewTestIngredient(
"test",
frosting.WithDependencies(build),
)

f.Group(
"Basic Commands (Beginner):",
build,
test,
)
}

f.Execute(os.Args[1:]...)
}
```

👀 For more information, check out the [examples](../examples)!
7 changes: 7 additions & 0 deletions docs/internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Internals

Once ingredients are defined and "imported" into a frosting client (see [getting-started](./getting-started.md)),

```go

```
18 changes: 18 additions & 0 deletions examples/full/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM debian:buster-slim

RUN set -ex; \
apt-get update; \
apt-get install --no-install-recommends -y \
bash \
ca-certificates \
sudo \
locales \
procps \
; \
apt-get autoclean; \
rm -rf /var/lib/apt-lists/*;

SHELL [ "/bin/bash", "-Eexuo", "pipefail", "-c" ]

ENTRYPOINT [ "/bin/bash" ]
CMD [ "-c" "echo hello" ]
17 changes: 17 additions & 0 deletions examples/full/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Full Example

Lots of Bells and Whistles

## Imagine

Imagine that this is the _root_ of your project repo. It doesn't matter what language, node, python, go, etc.

The recommended approach is to put all of your frosting in a subfolder, as to not interfere if you are building a go application.

## Getting Started

Compile into a binary

```shell
go build -o fr ./frosting
```
22 changes: 22 additions & 0 deletions examples/minimal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# JetBrains/GoLand
/.idea

# Frosting Binary
/fr
/frost
17 changes: 17 additions & 0 deletions examples/minimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Minimal Example

The Newbie Tutorial

## Imagine

Imagine that this is the _root_ of your project repo. It doesn't matter what language, node, python, go, etc.

The recommended approach is to put frosting in a subfolder, as to not interfere if you are building a go application.

## Getting Started

Compile into a binary

```shell
go build -o frost ./frosting
```
Loading