Skip to content

Commit

Permalink
Introduce --clipboard on macOS
Browse files Browse the repository at this point in the history
Refactor scaffold to use `io.Writer` as its main target.

Refactor command code to allow for optional clipboard flag.

Introduce macOS specific code for copy to clipboard.
  • Loading branch information
HeavyWombat committed Jul 7, 2023
1 parent 4037b51 commit 88b9613
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 47 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ require (
github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.10.0 // indirect
golang.org/x/tools v0.11.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 5 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -96,8 +96,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
23 changes: 20 additions & 3 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import (
// version string will be injected by automation
var version string

// saveToClipboard function will be implemented by OS specific code
var saveToClipboard func(img.Scaffold) error

var rootCmd = &cobra.Command{
Use: fmt.Sprintf("%s [%s flags] [--] command [command flags] [command arguments] [...]", executableName(), executableName()),
Short: "Creates a screenshot of terminal command output",
Expand Down Expand Up @@ -118,8 +121,16 @@ window including all terminal colors and text decorations.
return err
}

filename, runErr := cmd.Flags().GetString("filename")
if filename == "" || runErr != nil {
// save image to clipboard
//
if toClipboard, err := cmd.Flags().GetBool("clipboard"); err == nil && toClipboard {
return saveToClipboard(scaffold)
}

// save image to file
//
filename, err := cmd.Flags().GetString("filename")
if filename == "" || err != nil {
fmt.Fprintf(os.Stderr, "failed to read filename from command-line, defaulting to out.png")
filename = "out.png"
}
Expand All @@ -128,7 +139,13 @@ window including all terminal colors and text decorations.
return fmt.Errorf("file extension %q of filename %q is not supported, only png is supported", extension, filename)
}

return scaffold.SavePNG(filename)
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}

defer file.Close()
return scaffold.Write(file)
},
}

Expand Down
75 changes: 75 additions & 0 deletions internal/cmd/root_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright © 2023 The Homeport Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
"bytes"
"encoding/hex"
"fmt"
"os"
"os/exec"

"github.com/homeport/termshot/internal/img"
)

const osascript = "/usr/bin/osascript"

// hasOsascript checks if /usr/bin/osascript exists and is executable
func hasOsascript() bool {
if fi, err := os.Stat(osascript); err == nil {
return fi.Mode()&0111 != 0
}

return false
}

func init() {
if hasOsascript() {
// register tool flag to enable clipboard option
rootCmd.Flags().BoolP("clipboard", "b", false, "copy termshot to clipboard, overrules filename option")

// register function to copy image into the clipboard
saveToClipboard = func(scaffold img.Scaffold) error {
var buf bytes.Buffer

if _, err := buf.WriteString("set the clipboard to «data PICT"); err != nil {
return err
}

if err := scaffold.Write(hex.NewEncoder(&buf)); err != nil {
return err
}

if _, err := buf.WriteString("»"); err != nil {
return err
}

cmd := exec.Command(osascript, "-e", buf.String())
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprint(os.Stderr, string(out))
return err
}

return nil
}
}
}
17 changes: 14 additions & 3 deletions internal/img/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
package img

import (
"image"
"image/color"
"image/png"
"io"
"math"
"strings"
Expand Down Expand Up @@ -156,7 +158,7 @@ func (s *Scaffold) measureContent() (width float64, height float64) {
return width, height
}

func (s *Scaffold) SavePNG(path string) error {
func (s *Scaffold) image() (image.Image, error) {
var f = func(value float64) float64 { return s.factor * value }

var (
Expand Down Expand Up @@ -196,7 +198,7 @@ func (s *Scaffold) SavePNG(path string) error {

shadow, err := stackblur.Process(bc.Image(), uint32(s.shadowRadius))
if err != nil {
return err
return nil, err
}

dc.DrawImage(shadow, 0, 0)
Expand Down Expand Up @@ -294,5 +296,14 @@ func (s *Scaffold) SavePNG(path string) error {
x += w
}

return dc.SavePNG(path)
return dc.Image(), nil
}

func (s *Scaffold) Write(w io.Writer) error {
image, err := s.image()
if err != nil {
return err
}

return png.Encode(w, image)
}
53 changes: 19 additions & 34 deletions internal/img/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ package img_test

import (
"bytes"
"os"
"io"
"strings"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -34,47 +34,32 @@ import (

var _ = Describe("Creating images", func() {
Context("Use scaffold to create PNG file", func() {
var withTempFile = func(f func(name string)) {
SetColorSettings(ON, ON)
defer SetColorSettings(AUTO, AUTO)
It("should write a PNG stream based on provided input", func() {
scaffold := NewImageCreator()

file, err := os.CreateTemp("", "termshot.png")
err := scaffold.AddContent(strings.NewReader("foobar"))
Expect(err).ToNot(HaveOccurred())
defer os.Remove(file.Name())

f(file.Name())
}

It("should create a PNG file based on provided input", func() {
withTempFile(func(name string) {
scaffold := NewImageCreator()

err := scaffold.AddContent(strings.NewReader("foobar"))
Expect(err).ToNot(HaveOccurred())

err = scaffold.SavePNG(name)
Expect(err).ToNot(HaveOccurred())
})
err = scaffold.Write(io.Discard)
Expect(err).ToNot(HaveOccurred())
})

It("should create a PNG file based on provided input with ANSI sequences", func() {
withTempFile(func(name string) {
var buf bytes.Buffer
_, _ = Fprintf(&buf, "Text with emphasis, like *bold*, _italic_, _*bold/italic*_ or ~underline~.\n\n")
_, _ = Fprintf(&buf, "Colors:\n")
_, _ = Fprintf(&buf, "\tRed{Red}\n")
_, _ = Fprintf(&buf, "\tGreen{Green}\n")
_, _ = Fprintf(&buf, "\tBlue{Blue}\n")
_, _ = Fprintf(&buf, "\tMintCream{MintCream}\n")
It("should write a PNG stream based on provided input with ANSI sequences", func() {
var buf bytes.Buffer
_, _ = Fprintf(&buf, "Text with emphasis, like *bold*, _italic_, _*bold/italic*_ or ~underline~.\n\n")
_, _ = Fprintf(&buf, "Colors:\n")
_, _ = Fprintf(&buf, "\tRed{Red}\n")
_, _ = Fprintf(&buf, "\tGreen{Green}\n")
_, _ = Fprintf(&buf, "\tBlue{Blue}\n")
_, _ = Fprintf(&buf, "\tMintCream{MintCream}\n")

scaffold := NewImageCreator()
scaffold := NewImageCreator()

err := scaffold.AddContent(&buf)
Expect(err).ToNot(HaveOccurred())
err := scaffold.AddContent(&buf)
Expect(err).ToNot(HaveOccurred())

err = scaffold.SavePNG(name)
Expect(err).ToNot(HaveOccurred())
})
err = scaffold.Write(io.Discard)
Expect(err).ToNot(HaveOccurred())
})
})
})

0 comments on commit 88b9613

Please sign in to comment.