Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: better control over script usage #87

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
!test/golden/**/*.png
!examples/*.svg
/result
freeze
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Screenshots can be customized with `--flags` or [Configuration](#configuration)
> [!NOTE]
> You can view all freeze customization with `freeze --help`.

- [`-o`](#output), [`--output`](#output): Output location for .svg, .png, .jpg.
- [`-o`](#output), [`--output`](#output): Output location for .svg, .png, .jpg, use - for stdout.
- [`-c`](#configuration), [`--config`](#configuration): Base configuration file or template.
- [`-t`](#theme), [`--theme`](#theme): Theme to use for syntax highlighting.
- [`-l`](#language), [`--language`](#language): Language to apply to code
Expand Down Expand Up @@ -154,6 +154,12 @@ freeze main.go --output out.webp
freeze main.go --output out.{svg,png,webp}
```

Have `freeze` send image data to stdout by passing `-` for `--output`

```bash
freeze main.go --output -
```

### Font

Specify the font family, font size, and font line height of the output image.
Expand Down
3 changes: 2 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ type Config struct {
Language string `json:"language,omitempty" help:"Language of code file." short:"l" group:"Settings" placeholder:"go"`
Theme string `json:"theme" help:"Theme to use for syntax highlighting." short:"t" group:"Settings" placeholder:"charm"`

Output string `json:"output,omitempty" help:"Output location for {{.svg}}, {{.png}}, or {{.webp}}." short:"o" group:"Settings" default:"" placeholder:"freeze.svg"`
Output string `json:"output,omitempty" help:"Output location for {{.svg}}, {{.png}}, or {{.webp}}, use - for stdout." short:"o" group:"Settings" default:"" placeholder:"freeze.svg"`
Format string `json:"format,omitempty" help:"Output format for file, {{.svg}}, {{.png}} or {{.webp}}." short:"f" group:"Settings" default:"svg" placeholder:"svc"`
Execute string `json:"-" help:"Capture output of command execution." short:"x" group:"Settings" default:""`
ExecuteTimeout time.Duration `json:"-" help:"Execution timeout." group:"Settings" default:"10s" prefix:"execute." name:"timeout" hidden:""`

Expand Down
86 changes: 84 additions & 2 deletions freeze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -40,22 +41,103 @@ func TestFreeze(t *testing.T) {
}
}

// freeze is a wrapper around calling the binary itself
func freeze(input, output string, inputRead io.Reader, outputWrite io.Writer) error {
args := []string{}

if input != "" {
args = append(args, input)
}

if output != "" {
args = append(args, "-o", output)
}

cmd := exec.Command(binary, args...)

if inputRead != nil {
cmd.Stdin = inputRead
}

if outputWrite != nil {
cmd.Stdout = outputWrite
}

return cmd.Run()
}

func TestFreezeOutput(t *testing.T) {
output := "artichoke-test.svg"
defer os.Remove(output)

cmd := exec.Command(binary, "test/input/artichoke.hs", "-o", output)
err := cmd.Run()
if err := freeze("test/input/artichoke.hs", output, nil, nil); err != nil {
t.Fatal(err)
}

_, err := os.Stat(output)
if err != nil {
t.Fatal(err)
}
}

// read file from stdin
func TestFreezeOutputWhenInputStdin(t *testing.T) {
output := "tab.svg"
defer os.Remove(output)

inputMock, err := os.Open("test/input/tab.go")
if err != nil {
t.Fatal(err)
}

if err := freeze("-", output, inputMock, nil); err != nil {
t.Fatal(err)
}

_, err = os.Stat(output)
if err != nil {
t.Fatal(err)
}
}

// fall back to default filename
func TestFreezeOutputWhenNoOutputFilename(t *testing.T) {
defer os.Remove(defaultOutputFilename)

if err := freeze("test/input/tab.go", "", nil, nil); err != nil {
t.Fatal(err)
}

_, err := os.Stat(defaultOutputFilename)
if err != nil {
t.Fatal(err)
}
}

func TestFreezeOutputWhenOutputStdout(t *testing.T) {
stdoutMock := "free_stdout.svg"

f, err := os.OpenFile(stdoutMock, os.O_CREATE|os.O_WRONLY, 0750)
if err != nil {
t.Fatal(err)
}

defer os.Remove(stdoutMock)

if err := freeze("test/input/tab.go", "-", nil, f); err != nil {
t.Fatal(err)
}

i, err := os.Stat(stdoutMock)
if err != nil {
t.Fatal(err)
}

if i.Size() == 0 {
t.Fatal("nothing was writtent to stdout")
}
}

func TestFreezeHelp(t *testing.T) {
out := bytes.Buffer{}
cmd := exec.Command(binary)
Expand Down
107 changes: 65 additions & 42 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ var (
CommitSHA = ""
)

var istty = isatty.IsTerminal(os.Stdout.Fd())

func main() {
const shaLen = 7

Expand Down Expand Up @@ -386,65 +388,86 @@ func main() {
}
}

istty := isatty.IsTerminal(os.Stdout.Fd())
format, err := getFormat(&config)
if err != nil {
printErrorFatal("Invalid output format", err)
}

switch {
case strings.HasSuffix(config.Output, ".png"):
output := getOutputFilename(&config, format)

if format == "png" {
// use libsvg conversion.
svgConversionErr := libsvgConvert(doc, imageWidth, imageHeight, config.Output)
svgConversionErr := libsvgConvert(doc, imageWidth, imageHeight, output)
if svgConversionErr == nil {
printFilenameOutput(config.Output)
break
printFilenameOutput(output)
return
}

// could not convert with libsvg, try resvg
svgConversionErr = resvgConvert(doc, imageWidth, imageHeight, config.Output)
if svgConversionErr != nil {
printErrorFatal("Unable to convert SVG to PNG", svgConversionErr)
}
printFilenameOutput(config.Output)

default:
// output file specified.
if config.Output != "" {
err = doc.WriteToFile(config.Output)
if err != nil {
printErrorFatal("Unable to write output", err)
}
printFilenameOutput(config.Output)
return
}

// reading from stdin.
if config.Input == "" || config.Input == "-" {
if istty {
err = doc.WriteToFile(defaultOutputFilename)
printFilenameOutput(defaultOutputFilename)
} else {
_, err = doc.WriteTo(os.Stdout)
}
if err != nil {
printErrorFatal("Unable to write output", err)
}
return
}
printFilenameOutput(output)

// reading from file.
if istty {
config.Output = strings.TrimSuffix(filepath.Base(config.Input), filepath.Ext(config.Input)) + ".svg"
err = doc.WriteToFile(config.Output)
printFilenameOutput(config.Output)
} else {
_, err = doc.WriteTo(os.Stdout)
}
if err != nil {
printErrorFatal("Unable to write output", err)
}
return
}

err = doc.WriteToFile(output)
if err != nil {
printErrorFatal("Unable to write output", err)
}

printFilenameOutput(output)
}

var outputHeader = lipgloss.NewStyle().Foreground(lipgloss.Color("#F1F1F1")).Background(lipgloss.Color("#6C50FF")).Bold(true).Padding(0, 1).MarginRight(1).SetString("WROTE")

func printFilenameOutput(filename string) {
if !istty || filename != "-" {
return
}

fmt.Println(lipgloss.JoinHorizontal(lipgloss.Center, outputHeader.String(), filename))
}

func isAllowedFormat(format string) error {
if format == "svg" || format == "png" || format == "webp" {
return nil
}

return fmt.Errorf("%s is not a supported output file format, choose among png, svg and webp", format)
}

func getFormat(config *Config) (string, error) {
if config.Format != "" {
return config.Format, isAllowedFormat(config.Format)
}

ext := filepath.Ext(config.Output)

return ext, isAllowedFormat(ext)
}

func getOutputFilename(config *Config, format string) string {
// always write to file if specified
if config.Output != "" && config.Output != "-" {
return config.Output
}

if !istty || config.Output == "-" {
return os.Stdout.Name()
}

// no input filename AND no output filename
// no way to "deduce" the name
if config.Input == "" || config.Input == "-" {
return defaultOutputFilename
}

// no output filename, but some input filename
// now we need the format
// remove existing extension for the format of output
return strings.TrimSuffix(filepath.Base(config.Input), filepath.Ext(config.Input)) + "." + format
}