From bf5e4f9b99838cde9a80c7cb6a1164f0ce41e917 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 24 Jul 2018 20:23:48 -0400 Subject: [PATCH] V2.0 update ## Features ### Open * Now returns the activeDocument after opening the file. ### JSLayer() * Removed semicolon from output. ### Document * Added FullName() which returns path to the .psd file. * Changed Filename() to DumpFile(), as Filename was misleading. * Dump function now saves the file as well, to help reduce the frequency of de-syncs. * Dump function now saves json files alongside the .psds instead of in a separate data folder- encountered issues when using the package as a module in go 1.11beta2. * Added Save() ## Testing * Added TestDocument_Save * Added TestDocument_Dump ## Misc. * Renamed pkgpath to pkgPath, to better fit go's standards. ## Fixes * DoAction now runs correctly. --- README.md | 6 +- artlayer.go | 4 +- document.go | 47 +++++++++----- document_test.go | 106 ++++++++++++++++++++++++++++++++ examples_test.go | 2 +- layerset.go | 10 +-- ps.go | 28 +++++---- runner/runner.go | 5 +- runner/scripts/SetName.jsx | 5 ++ runner/scripts/action.vbs | 3 +- runner/scripts/dojs.vbs | 2 - runner/scripts/getActiveDoc.jsx | 2 +- textlayer.go | 5 +- 13 files changed, 171 insertions(+), 54 deletions(-) create mode 100644 document_test.go create mode 100644 runner/scripts/SetName.jsx diff --git a/README.md b/README.md index 1ac7c6c..d890164 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![logo](logo.png) - +# ps [![GoDoc](https://godoc.org/github.com/sbrow/ps?status.svg)](https://godoc.org/github.com/sbrow/ps) [![Build Status](https://travis-ci.org/sbrow/ps.svg?branch=master)](https://travis-ci.org/sbrow/ps) [![Coverage Status](https://coveralls.io/repos/github/sbrow/ps/badge.svg?branch=master)](https://coveralls.io/github/sbrow/ps?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/sbrow/ps)](https://goreportcard.com/report/github.com/sbrow/ps) `import "github.com/sbrow/ps"` @@ -39,12 +39,10 @@ $ go get -u github.com/sbrow/ps `sbrow:` (2) Make TextLayer a subclass of ArtLayer. -`sbrow:` Reduce cylcomatic complexity of ActiveDocument(). +`sbrow:` Reduce cyclomatic complexity of ActiveDocument(). `sbrow:` refactor Close to Document.Close -`sbrow:` get rid of the semicolon at the end of JSLayer. - ## Documentation For full Documentation please visit https://godoc.org/github.com/sbrow/ps - - - diff --git a/artlayer.go b/artlayer.go index 506bd3e..d088482 100644 --- a/artlayer.go +++ b/artlayer.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "log" - "strings" "github.com/sbrow/ps/runner" ) @@ -211,8 +210,7 @@ func (a *ArtLayer) SetVisible(b bool) error { case false: log.Printf("Hiding %s", a.name) } - js := fmt.Sprintf("%s.visible=%v;", - strings.TrimRight(JSLayer(a.Path()), ";"), b) + js := fmt.Sprintf("%s.visible=%v;", JSLayer(a.Path()), b) if byt, err := DoJS("compilejs.jsx", js); err != nil { log.Println(string(byt)) return err diff --git a/document.go b/document.go index d48cc5d..0c8503f 100644 --- a/document.go +++ b/document.go @@ -2,17 +2,19 @@ package ps import ( "encoding/json" + "fmt" "io/ioutil" "log" "os" + "os/user" "path/filepath" - "runtime" "strings" ) // Document represents a Photoshop document (PSD file). type Document struct { name string + fullName string height int width int artLayers []*ArtLayer @@ -23,6 +25,7 @@ type Document struct { // allows Documents to be saved to and loaded from JSON. type DocumentJSON struct { Name string + FullName string Height int Width int ArtLayers []*ArtLayer @@ -31,7 +34,7 @@ type DocumentJSON struct { // MarshalJSON returns the Document in JSON format. func (d *Document) MarshalJSON() ([]byte, error) { - return json.Marshal(&DocumentJSON{Name: d.name, Height: d.height, + return json.Marshal(&DocumentJSON{Name: d.name, FullName: d.fullName, Height: d.height, Width: d.width, ArtLayers: d.artLayers, LayerSets: d.layerSets}) } @@ -42,6 +45,7 @@ func (d *Document) UnmarshalJSON(b []byte) error { return err } d.name = tmp.Name + d.fullName = tmp.FullName d.height = tmp.Height d.width = tmp.Width d.artLayers = tmp.ArtLayers @@ -61,6 +65,11 @@ func (d *Document) Name() string { return d.name } +// FullName returns the absolute path to the current document file. +func (d *Document) FullName() string { + return d.fullName +} + // Parent returns the Group that contains d. func (d *Document) Parent() Group { return nil @@ -127,7 +136,7 @@ func ActiveDocument() (*Document, error) { } d.name = strings.TrimRight(string(byt), "\r\n") if Mode != Safe { - err = d.Restore(d.Filename()) + err = d.Restore(d.DumpFile()) switch { case os.IsNotExist(err): log.Println("Previous version not found.") @@ -165,7 +174,7 @@ func ActiveDocument() (*Document, error) { // Restore loads document data from a JSON file. func (d *Document) Restore(path string) error { if path == "" { - path = d.Filename() + path = d.DumpFile() } byt, err := ioutil.ReadFile(path) if err == nil { @@ -185,26 +194,23 @@ func (d *Document) Path() string { return "" } -// Filename returns the path to the json file for this document. -func (d *Document) Filename() string { - _, dir, _, ok := runtime.Caller(0) - if !ok { - log.Panic("No caller information") - } - err := os.Mkdir(filepath.Join(filepath.Dir(dir), "data"), 0700) +// DumpFile returns the path to the json file where +// this document's data gets dumped. See Document.Dump +func (d *Document) DumpFile() string { + usr, err := user.Current() if err != nil { log.Println(err) } - name := strings.TrimRight(d.name, "\r\n") - name = strings.TrimSuffix(name, ".psd") - return filepath.Join(filepath.Dir(dir), "data", name+".json") + path := filepath.Join(strings.Replace(d.fullName, "~", usr.HomeDir, 1)) + return strings.Replace(path, ".psd", ".json", 1) } // Dump saves the document to disk in JSON format. func (d *Document) Dump() { log.Println("Dumping to disk") - log.Println(d.Filename()) - f, err := os.Create(d.Filename()) + log.Println(d.DumpFile()) + defer d.Save() + f, err := os.Create(d.DumpFile()) if err != nil { log.Fatal(err) } @@ -238,3 +244,12 @@ func (d *Document) MustExist(name string) Layer { } return set } + +// Save saves the Document in place. +func (d *Document) Save() error { + js := fmt.Sprintf("var d=app.open(File('%s'));\nd.save();", d.FullName()) + if _, err := DoJS("compilejs", js); err != nil { + return err + } + return nil +} diff --git a/document_test.go b/document_test.go new file mode 100644 index 0000000..af1a793 --- /dev/null +++ b/document_test.go @@ -0,0 +1,106 @@ +package ps + +import ( + "log" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +// wd is the working directory +var wd string + +func init() { + _, file, _, ok := runtime.Caller(0) + if !ok { + log.Fatal("runtime.Caller(0) returned !ok") + } + wd = filepath.Dir(file) + + f, err := os.Create("test.log") + if err != nil { + log.Fatal(err) + } + log.SetOutput(f) +} + +func TestDocument_Dump(t *testing.T) { + // Must be true for test to be valid. + Mode = Normal + + tests := []struct { + name string + }{ + {"Test"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // Get rid of old Dump. + os.Remove(filepath.Join(wd, tt.name+".json")) + + // Generate a fresh Doc (loaded slowly). + want, err := Open(filepath.Join(wd, tt.name+".psd")) + if err != nil { + t.Fatal(err) + } + + // Dump the contents. + want.Dump() + // Grab a new version of the doc (loaded from json). + got, err := Open(filepath.Join(wd, tt.name+".psd")) + if err != nil { + t.Fatal(err) + } + got.layerSets[0].current = true + if !reflect.DeepEqual(got, want) { + t.Errorf("wanted: %+v\ngot: %+v", want, got) + } + }) + } +} + +func TestDocument_Save(t *testing.T) { + file := filepath.Join(wd, "Test.psd") + d, err := Open(file) + if err != nil { + t.Fatal(err) + } + + layerName := "Group 1" + lyr := d.LayerSet(layerName) + if lyr == nil { + t.Fatalf("LayerSet '%s' was not found", layerName) + } + + // Change a layer name. + _, err = DoJS(filepath.Join("SetName"), JSLayer(lyr.Path()), "Group 2") + if err != nil { + t.Error(err) + } + + defer func() { + if err := DoAction("DK", "Undo"); err != nil { + t.Error(err) + } + err := d.Save() + if err != nil { + t.Fatal(err) + } + }() + + err = d.Save() + if err != nil { + t.Fatal(err) + } + + d2, err := Open(file) + if err != nil { + t.Fatal(err) + } + if reflect.DeepEqual(d, d2) { + t.Errorf("wanted: %+v\ngot: %+v", d, d2) + } +} diff --git a/examples_test.go b/examples_test.go index 7eccf93..94b69d8 100644 --- a/examples_test.go +++ b/examples_test.go @@ -6,5 +6,5 @@ func ExampleJSLayer() { // The path of a layer inside a top level group. path := "Group 1/Layer 1" fmt.Println(JSLayer(path)) - // Output: app.activeDocument.layerSets.getByName('Group 1').artLayers.getByName('Layer 1'); + // Output: app.activeDocument.layerSets.getByName('Group 1').artLayers.getByName('Layer 1') } diff --git a/layerset.go b/layerset.go index 8628c6f..d04ddb1 100644 --- a/layerset.go +++ b/layerset.go @@ -201,8 +201,7 @@ func (l *LayerSet) SetVisible(b bool) error { if l.visible == b { return nil } - js := fmt.Sprintf("%s.visible=%v;", strings.TrimRight( - JSLayer(l.Path()), ";"), b) + js := fmt.Sprintf("%s.visible=%v;", JSLayer(l.Path()), b) if _, err := DoJS("compilejs.jsx", js); err != nil { return err } @@ -297,10 +296,3 @@ func (l *LayerSet) Refresh() error { l.current = true return nil } - -func (l *LayerSet) Set(ll *LayerSet) { - if ll == nil { - panic("AHHHHHH") - } - l = ll -} diff --git a/ps.go b/ps.go index ab3d2b5..93a8afd 100644 --- a/ps.go +++ b/ps.go @@ -16,11 +16,11 @@ import ( ) // The full path to this directory. -var pkgpath string +var pkgPath string func init() { _, file, _, _ := runtime.Caller(0) - pkgpath = filepath.Dir(file) + pkgPath = filepath.Dir(file) } // ApplyDataset fills out a template file with information @@ -40,9 +40,9 @@ func Close(save SaveOption) error { return err } -// DoAction runs the Photoshop Action with the given name from the Action Set "from". -func DoAction(action, from string) error { - _, err := runner.Run("action", action, from) +// DoAction runs the Photoshop Action with name 'action' from ActionSet 'set'. +func DoAction(set, action string) error { + _, err := runner.Run("action", set, action) return err } @@ -72,8 +72,10 @@ func DoJS(path string, args ...string) ([]byte, error) { args = append([]string{outpath.Name()}, args...) // If passed a script by name, assume it's in the default folder. - if filepath.Dir(path) == "." { - path = filepath.Join(pkgpath, "runner", "scripts", path) + scripts := filepath.Join(pkgPath, "runner", "scripts") + b, err := filepath.Match(scripts, path) + if !b || err != nil { + path = filepath.Join(scripts, path) } args = append([]string{path}, args...) @@ -99,8 +101,6 @@ func Init() error { // JSLayer "compiles" Javascript code to get an ArtLayer with the given path. // The output always ends with a semicolon, so if you want to access a specific // property of the layer, you'll have to trim the output before concatenating. -// -// TODO(sbrow): get rid of the semicolon at the end of JSLayer. func JSLayer(path string) string { pth := strings.Split(path, "/") js := "app.activeDocument" @@ -113,7 +113,7 @@ func JSLayer(path string) string { if pth[last] != "" { js += fmt.Sprintf(".artLayers.getByName('%s')", pth[last]) } - return js + ";" + return js } // JSLayerMerge gets the Javascript code to get the Layer or LayerSet with this path @@ -129,9 +129,11 @@ func JSLayerMerge(path string) string { // Open opens a Photoshop document with the specified path. // If Photoshop is not currently running, it is started before // opening the document. -func Open(path string) error { - _, err := runner.Run("open", path) - return err +func Open(path string) (*Document, error) { + if _, err := runner.Run("open", path); err != nil { + return nil, err + } + return ActiveDocument() } // Quit exits Photoshop, closing all open documents using the given save option. diff --git a/runner/runner.go b/runner/runner.go index daefdc2..c50aed2 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -51,7 +51,10 @@ func Run(name string, args ...string) ([]byte, error) { cmd := exec.Command(std.Cmd, parseArgs(name, args...)...) cmd.Stdout, cmd.Stderr = &out, &errs if err := cmd.Run(); err != nil || len(errs.Bytes()) != 0 { - return out.Bytes(), fmt.Errorf("err: \"%s %s\"\nargs: \"%s\"\nout: \"%s\"", err, errs.String(), args, out.String()) + return out.Bytes(), fmt.Errorf(`err: "%s" +errs.String(): "%s" +args: "%s" +out: "%s"`, err, errs.String(), args, out.String()) } return out.Bytes(), nil } diff --git a/runner/scripts/SetName.jsx b/runner/scripts/SetName.jsx new file mode 100644 index 0000000..0bf2083 --- /dev/null +++ b/runner/scripts/SetName.jsx @@ -0,0 +1,5 @@ +#include lib.js +var stdout=newFile(arguments[0]); +var obj=eval(arguments[1]); +obj.name=arguments[2]; +stdout.close(); \ No newline at end of file diff --git a/runner/scripts/action.vbs b/runner/scripts/action.vbs index 231e2ce..70cd5de 100644 --- a/runner/scripts/action.vbs +++ b/runner/scripts/action.vbs @@ -1,5 +1,6 @@ +' Runs an action with the given name (Argument 1) from the given set (Argument 0). set appRef = CreateObject("Photoshop.Application") -' No dialogs' +' No dialogs. dlgMode = 3 set desc = CreateObject( "Photoshop.ActionDescriptor" ) diff --git a/runner/scripts/dojs.vbs b/runner/scripts/dojs.vbs index 9e2c0b8..561832d 100644 --- a/runner/scripts/dojs.vbs +++ b/runner/scripts/dojs.vbs @@ -4,8 +4,6 @@ Set appRef = CreateObject("Photoshop.Application") if wScript.Arguments.Count = 0 then wScript.Echo "Missing parameters" else - ' wScript.Echo wScript.Arguments(0) - ' wScript.Echo wScript.Arguments(1) path = wScript.Arguments(0) args = wScript.Arguments(1) error = appRef.DoJavaScriptFile(path, Split(args, ",,")) diff --git a/runner/scripts/getActiveDoc.jsx b/runner/scripts/getActiveDoc.jsx index bd46c9d..9f8ff62 100644 --- a/runner/scripts/getActiveDoc.jsx +++ b/runner/scripts/getActiveDoc.jsx @@ -1,7 +1,7 @@ #include lib.js var stdout = newFile(arguments[0]); var doc = app.activeDocument; -stdout.writeln(('{"Name": "'+doc.name+'", "Height":'+doc.height+ +stdout.writeln(('{"Name": "'+doc.name+'", "FullName": "'+doc.fullName+'", "Height":'+doc.height+ ', "Width":'+doc.width+', "ArtLayers": [').replace(/ px/g, "")); stdout.writeln(layers(doc.artLayers)) diff --git a/textlayer.go b/textlayer.go index 2eed043..8c8d6db 100644 --- a/textlayer.go +++ b/textlayer.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "log" - "strings" ) // TextItem holds the text element of a TextLayer. @@ -62,7 +61,7 @@ func (t *TextItem) SetText(txt string) { return } var err error - lyr := strings.TrimRight(JSLayer(t.parent.Path()), ";") + lyr := JSLayer(t.parent.Path()) bndtext := "[[' + lyr.bounds[0] + ',' + lyr.bounds[1] + '],[' + lyr.bounds[2] + ',' + lyr.bounds[3] + ']]" js := fmt.Sprintf(`%s.textItem.contents='%s';var lyr = %[1]s;stdout.writeln(('%[3]s').replace(/ px/g, ''));`, lyr, txt, bndtext) @@ -87,7 +86,7 @@ func (t *TextItem) SetSize(s float64) { if t.size == s { return } - lyr := strings.TrimRight(JSLayer(t.parent.Path()), ";") + lyr := JSLayer(t.parent.Path()) js := fmt.Sprintf("%s.textItem.size=%f;", lyr, s) _, err := DoJS("compilejs.jsx", js) if err != nil {