Add configdir and unify file loading/saving
* Create a configuration directory to store the user's local levels and doodads. On Linux this is at ~/.config/doodle * Unify the file loading and saving functions: you can type into the console "edit example" and it will open `example.level` from your levels folder or else `example.doodad` from the doodads folder, in the appropriate mode. * You can further specify the file extension: `edit example.doodad` and it will load it from the doodads folder only. * Any slash characters in a file name are taken literally as a relative or absolute path. * The UI Save/Load buttons now share the same code path as the console commands, so the `save` command always saves as a Doodad when the EditorScene is in Doodad Mode.
This commit is contained in:
parent
a7fd3aa1ca
commit
cfe26cb964
|
@ -176,8 +176,8 @@ Lesser important UI features that can come at any later time:
|
|||
|
||||
## Doodad Editor
|
||||
|
||||
* [ ] The Edit Mode should support creating drawings for Doodads.
|
||||
* [ ] It should know whether you're drawing a Map or a Doodad as some
|
||||
* [x] The Edit Mode should support creating drawings for Doodads.
|
||||
* [x] It should know whether you're drawing a Map or a Doodad as some
|
||||
behaviors may need to be different between the two.
|
||||
* [ ] Compress the coordinates down toward `(0,0)` when saving a Doodad,
|
||||
by finding the toppest, leftest point and making that `(0,0)` and adjusting
|
||||
|
|
|
@ -43,7 +43,7 @@ func main() {
|
|||
app.SetupEngine()
|
||||
if filename != "" {
|
||||
if edit {
|
||||
app.EditDrawing(filename)
|
||||
app.EditFile(filename)
|
||||
} else {
|
||||
app.PlayLevel(filename)
|
||||
}
|
||||
|
|
16
commands.go
16
commands.go
|
@ -4,6 +4,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.kirsle.net/apps/doodle/enum"
|
||||
)
|
||||
|
||||
// Command is a parsed shell command.
|
||||
|
@ -112,11 +114,17 @@ func (c Command) Save(d *Doodle) error {
|
|||
} else if scene.filename != "" {
|
||||
filename = scene.filename
|
||||
} else {
|
||||
return errors.New("usage: save <filename.json>")
|
||||
return errors.New("usage: save <filename>")
|
||||
}
|
||||
|
||||
d.shell.Write("Saving to file: " + filename)
|
||||
switch scene.DrawingType {
|
||||
case enum.LevelDrawing:
|
||||
d.shell.Write("Saving Level: " + filename)
|
||||
scene.SaveLevel(filename)
|
||||
case enum.DoodadDrawing:
|
||||
d.shell.Write("Saving Doodad: " + filename)
|
||||
scene.SaveDoodad(filename)
|
||||
}
|
||||
} else {
|
||||
return errors.New("save: only available in Edit Mode")
|
||||
}
|
||||
|
@ -131,8 +139,8 @@ func (c Command) Edit(d *Doodle) error {
|
|||
}
|
||||
|
||||
filename := c.Args[0]
|
||||
d.shell.Write("Editing level: " + filename)
|
||||
d.EditDrawing(filename)
|
||||
d.shell.Write("Editing file: " + filename)
|
||||
d.EditFile(filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
136
config.go
136
config.go
|
@ -1,6 +1,15 @@
|
|||
package doodle
|
||||
|
||||
import "git.kirsle.net/apps/doodle/render"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"github.com/kirsle/configdir"
|
||||
)
|
||||
|
||||
// Configuration constants.
|
||||
var (
|
||||
|
@ -10,3 +19,128 @@ var (
|
|||
DebugTextStroke = render.Grey
|
||||
DebugTextShadow = render.Black
|
||||
)
|
||||
|
||||
// Profile Directory settings.
|
||||
var (
|
||||
ConfigDirectoryName = "doodle"
|
||||
ProfileDirectory string
|
||||
LevelDirectory string
|
||||
DoodadDirectory string
|
||||
|
||||
// Regexp to match simple filenames for maps and doodads.
|
||||
reSimpleFilename = regexp.MustCompile(`^([A-Za-z0-9-_.,+ '"\[\](){}]+)$`)
|
||||
)
|
||||
|
||||
// File extensions
|
||||
const (
|
||||
extLevel = ".level"
|
||||
extDoodad = ".doodad"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ProfileDirectory = configdir.LocalConfig(ConfigDirectoryName)
|
||||
LevelDirectory = configdir.LocalConfig(ConfigDirectoryName, "levels")
|
||||
DoodadDirectory = configdir.LocalConfig(ConfigDirectoryName, "doodads")
|
||||
configdir.MakePath(LevelDirectory, DoodadDirectory)
|
||||
}
|
||||
|
||||
// LevelPath will turn a "simple" filename into an absolute path in the user's
|
||||
// local levels folder. If the filename already contains slashes, it is returned
|
||||
// as-is as an absolute or relative path.
|
||||
func LevelPath(filename string) string {
|
||||
return resolvePath(LevelDirectory, filename, extLevel)
|
||||
}
|
||||
|
||||
// DoodadPath is like LevelPath but for Doodad files.
|
||||
func DoodadPath(filename string) string {
|
||||
return resolvePath(DoodadDirectory, filename, extDoodad)
|
||||
}
|
||||
|
||||
// resolvePath is the inner logic for LevelPath and DoodadPath.
|
||||
func resolvePath(directory, filename, extension string) string {
|
||||
if strings.Contains(filename, "/") {
|
||||
return filename
|
||||
}
|
||||
|
||||
// Attach the file extension?
|
||||
if strings.ToLower(filepath.Ext(filename)) != extension {
|
||||
filename += extension
|
||||
}
|
||||
|
||||
return filepath.Join(directory, filename)
|
||||
}
|
||||
|
||||
/*
|
||||
EditFile opens a drawing file (Level or Doodad) in the EditorScene.
|
||||
|
||||
The filename can be one of the following:
|
||||
|
||||
- A simple filename with no path separators in it and/or no file extension.
|
||||
- An absolute path beginning with "/"
|
||||
- A relative path beginning with "./"
|
||||
|
||||
If the filename has an extension (`.level` or `.doodad`), that will disambiguate
|
||||
how to find the file and which mode to start the EditorMode in. Otherwise, the
|
||||
"levels" folder is checked first and the "doodads" folder second.
|
||||
*/
|
||||
func (d *Doodle) EditFile(filename string) error {
|
||||
var absPath string
|
||||
|
||||
// Is it a simple filename?
|
||||
if m := reSimpleFilename.FindStringSubmatch(filename); len(m) > 0 {
|
||||
log.Debug("EditFile: simple filename %s", filename)
|
||||
extension := strings.ToLower(filepath.Ext(filename))
|
||||
if foundFilename := d.ResolvePath(filename, extension, false); foundFilename != "" {
|
||||
log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename)
|
||||
absPath = foundFilename
|
||||
} else {
|
||||
return fmt.Errorf("EditFile: %s: no level or doodad found", filename)
|
||||
}
|
||||
} else {
|
||||
log.Debug("Not a simple: %s %+v", filename, reSimpleFilename)
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
log.Debug("EditFile: verified path %s exists", filename)
|
||||
absPath = filename
|
||||
}
|
||||
}
|
||||
|
||||
d.EditDrawing(absPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolvePath takes an ambiguous simple filename and searches for a Level or
|
||||
// Doodad that matches. Returns a blank string if no files found.
|
||||
//
|
||||
// Pass a true value for `one` if you are intending to create the file. It will
|
||||
// only test one filepath and return the first one, regardless if the file
|
||||
// existed. So the filename should have a ".level" or ".doodad" extension and
|
||||
// then this path will resolve the ProfileDirectory of the file.
|
||||
func (d *Doodle) ResolvePath(filename, extension string, one bool) string {
|
||||
// If the filename exists outright, return it.
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
return filename
|
||||
}
|
||||
|
||||
var paths []string
|
||||
if extension == extLevel {
|
||||
paths = append(paths, filepath.Join(LevelDirectory, filename))
|
||||
} else if extension == extDoodad {
|
||||
paths = append(paths, filepath.Join(DoodadDirectory, filename))
|
||||
} else {
|
||||
paths = append(paths,
|
||||
filepath.Join(LevelDirectory, filename+".level"),
|
||||
filepath.Join(DoodadDirectory, filename+".doodad"),
|
||||
)
|
||||
}
|
||||
|
||||
for _, test := range paths {
|
||||
log.Debug("findFilename: try to find '%s' as %s", filename, test)
|
||||
if _, err := os.Stat(test); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return test
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/balance"
|
||||
"git.kirsle.net/apps/doodle/doodads"
|
||||
|
@ -167,6 +168,10 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
|||
return errors.New("SaveLevel: current drawing is not a Level type")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(filename, extLevel) {
|
||||
filename += extLevel
|
||||
}
|
||||
|
||||
s.filename = filename
|
||||
|
||||
m := s.Level
|
||||
|
@ -185,6 +190,9 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
|||
return fmt.Errorf("SaveLevel error: %s", err)
|
||||
}
|
||||
|
||||
// Save it to their profile directory.
|
||||
filename = LevelPath(filename)
|
||||
log.Info("Write Level: %s", filename)
|
||||
err = ioutil.WriteFile(filename, json, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create map file error: %s", err)
|
||||
|
@ -215,6 +223,10 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
|||
return errors.New("SaveDoodad: current drawing is not a Doodad type")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(filename, extDoodad) {
|
||||
filename += extDoodad
|
||||
}
|
||||
|
||||
s.filename = filename
|
||||
d := s.Doodad
|
||||
if d.Title == "" {
|
||||
|
@ -228,7 +240,10 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
|||
d.Palette = s.drawing.Palette
|
||||
d.Layers[0].Chunker = s.drawing.Chunker()
|
||||
|
||||
err := d.WriteJSON(s.filename)
|
||||
// Save it to their profile directory.
|
||||
filename = DoodadPath(filename)
|
||||
log.Info("Write Doodad: %s", filename)
|
||||
err := d.WriteJSON(filename)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user