Vendor go-xdg

A few other changes
This commit is contained in:
Brian Buller 2016-02-09 08:29:56 -06:00
parent e13fa94318
commit fea0b581e4
25 changed files with 422 additions and 115 deletions

View File

@ -2,30 +2,24 @@
package userConfig package userConfig
import ( import (
"bufio"
"errors" "errors"
"fmt"
"os" "os"
"strings" "strings"
"launchpad.net/go-xdg" "gogs.bullercodeworks.com/brian/user-config/ext/go-xdg"
) )
// Config is a stuct for managing the config // Config is a stuct for managing the config
type Config struct { type Config struct {
name string name string
generalConfig *ConfigFile generalConfig *GeneralConfig
// ConfigFiles are files that have key/value pairs
ConfigFiles []ConfigFile
// RawFiles are other files (dbs, etc.)
RawFiles map[string]string
} }
// NewConfig generates a Config struct // NewConfig generates a Config struct
func NewConfig(name string) (*Config, error) { func NewConfig(name string) (*Config, error) {
c := &Config{name: name} c := &Config{name: name}
if err := c.Load(); err != nil { if err := c.Load(); err != nil {
return nil, err return c, err
} }
return c, nil return c, nil
} }
@ -40,15 +34,14 @@ func (c *Config) Get(k string) string {
return c.generalConfig.Get(k) return c.generalConfig.Get(k)
} }
// GetConfigPath just returns the config path
func (c *Config) GetConfigPath() string {
return c.generalConfig.Path
}
// Load loads config files into the config // Load loads config files into the config
func (c *Config) Load() error { func (c *Config) Load() error {
var err error var err error
// Clear the data, we're reloading
c.ConfigFiles = c.ConfigFiles[:0]
c.RawFiles = make(map[string]string)
var conf generalConfig
c.data = conf
if strings.TrimSpace(c.name) == "" { if strings.TrimSpace(c.name) == "" {
return errors.New("Invalid Config Name: " + c.name) return errors.New("Invalid Config Name: " + c.name)
} }
@ -61,10 +54,10 @@ func (c *Config) Load() error {
return err return err
} }
// We always have a <name>.conf file // We always have a <name>.conf file
cfgPath = cfgPath + "/" + c.name + ".conf" //cfgPath = cfgPath + "/" + c.name + ".conf"
} }
// Load general config // Load general config
if c.generalConfig, err = NewConfigFile(c.name, cfgPath, c.data); err != nil { if c.generalConfig, err = NewGeneralConfig(c.name, cfgPath); err != nil {
return err return err
} }
@ -73,38 +66,41 @@ func (c *Config) Load() error {
// Save writes the config to file(s) // Save writes the config to file(s)
func (c *Config) Save() error { func (c *Config) Save() error {
var err error if c.generalConfig == nil {
c.generalConfig.Save() return errors.New("Bad setup.")
var cfgPath string
var configLines []string
//configLines = append(configLines, "server="+client.ServerAddr)
//configLines = append(configLines, "key="+client.ServerKey)
cfgPath = os.Getenv("HOME")
if cfgPath != "" {
cfgPath = cfgPath + "/.config"
if err := c.verifyOrCreateDirectory(cfgPath); err != nil {
return err
}
cfgPath = cfgPath + "/" + c.name
} }
if cfgPath != "" { return c.generalConfig.Save()
file, err := os.Create(cfgPath) /*
if err != nil { var cfgPath string
// Couldn't load config even though one was specified var configLines []string
return err //configLines = append(configLines, "server="+client.ServerAddr)
//configLines = append(configLines, "key="+client.ServerKey)
cfgPath = os.Getenv("HOME")
if cfgPath != "" {
cfgPath = cfgPath + "/.config"
if err := c.verifyOrCreateDirectory(cfgPath); err != nil {
return err
}
cfgPath = cfgPath + "/" + c.name
} }
defer file.Close() if cfgPath != "" {
file, err := os.Create(cfgPath)
if err != nil {
// Couldn't load config even though one was specified
return err
}
defer file.Close()
w := bufio.NewWriter(file) w := bufio.NewWriter(file)
for _, line := range configLines { for _, line := range configLines {
fmt.Fprintln(w, line) fmt.Fprintln(w, line)
}
if err = w.Flush(); err != nil {
return err
}
} }
if err = w.Flush(); err != nil { return nil
return err */
}
}
return nil
} }
// verifyOrCreateDirectory is a helper function for building an // verifyOrCreateDirectory is a helper function for building an

View File

@ -2,111 +2,81 @@
package userConfig package userConfig
import ( import (
"bufio"
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
type generalConfig struct { // GeneralConfig is the basic config structure
// All configs make with package userConfig will have this file
type GeneralConfig struct {
Name string `toml:"-"`
Path string `toml:"-"`
ConfigFiles []string `toml:"additional_config"` ConfigFiles []string `toml:"additional_config"`
RawFiles []string `toml:"raw_files"` RawFiles []string `toml:"raw_files"`
Values map[string]string `toml:"general"` Values map[string]string `toml:"general"`
} }
// ConfigFile is a file that has key/value pairs in the config // NewGeneralConfig generates a General Config struct
type ConfigFile struct { func NewGeneralConfig(name, path string) (*GeneralConfig, error) {
Name string gf := &GeneralConfig{Name: name, Path: path}
Path string gf.ConfigFiles = []string{}
data interface{} gf.RawFiles = []string{}
} gf.Values = make(map[string]string)
// NewConfigFile generates a Config struct if err := gf.Load(); err != nil {
func NewConfigFile(name, path string, dt interface{}) (*ConfigFile, error) { return gf, err
cf := &ConfigFile{Name: name, Path: path, data: dt}
if err := cf.Load(); err != nil {
return nil, err
} }
return cf, nil return gf, nil
}
// Set sets a key/value pair in cf, if unable to save, revert to old value
// (and return the error)
func (cf *ConfigFile) Set(k, v string) error {
oldVal := cf.values[k]
cf.values[k] = v
if err := cf.Save(); err != nil {
cf.values[k] = oldVal
}
return err
}
// Get gets a key/value pair from cf
func (cf *ConfigFile) Get(k string) string {
return cf.values[k]
} }
// Load loads config files into the config // Load loads config files into the config
func (cf *ConfigFile) Load() error { func (gf *GeneralConfig) Load() error {
if strings.TrimSpace(cf.Name) == "" && strings.TrimSpace(cf.Path) { if strings.TrimSpace(gf.Name) == "" || strings.TrimSpace(gf.Path) == "" {
return errors.New("Invalid ConfigFile Name: " + cf.Path + "/" + cf.Name) return errors.New("Invalid ConfigFile Name: " + gf.Path + "/" + gf.Name)
} }
// Config files end with .conf // Config files end with .conf
cfgPath := cf.Path + "/" + cf.Name + ".conf" cfgPath := gf.Path + "/" + gf.Name + ".conf"
tomlData, err := ioutil.ReadFile(cfgPath) tomlData, err := ioutil.ReadFile(cfgPath)
if err != nil { if err != nil {
return err return err
} }
if _, err := toml.Decode(tomlData, &cf.data); err != nil { if _, err := toml.Decode(string(tomlData), &gf); err != nil {
return err return err
} }
return nil return nil
} }
// Save writes the config to file(s) // Save writes the config to file(s)
func (cf *ConfigFile) Save() error { func (gf *GeneralConfig) Save() error {
if strings.TrimSpace(cf.Name) == "" && strings.TrimSpace(cf.Path) {
return errors.New("Invalid ConfigFile Name: " + cf.Path + "/" + cf.Name + ".conf")
}
filePath := cf.path + "/" + cf.Name + ".conf"
var err error
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
enc := toml.NewEncoder(buf).Encode(data) cfgPath := gf.Path + "/" + gf.Name + ".conf"
err = ioutil.WriteFile(filePath, buf, 0644) fmt.Println("Writing Config File: " + cfgPath)
if err != nil { if err := toml.NewEncoder(buf).Encode(gf); err != nil {
return err return err
} }
fmt.Println("Writing Config File: " + buf.String())
return ioutil.WriteFile(cfgPath, buf.Bytes(), 0644)
}
cfgPath = os.Getenv("HOME") // Set sets a key/value pair in gf, if unable to save, revert to old value
if cfgPath != "" { // (and return the error)
cfgPath = cfgPath + "/.config" func (gf *GeneralConfig) Set(k, v string) error {
if err := c.verifyOrCreateDirectory(cfgPath); err != nil { oldVal := gf.Values[k]
return err gf.Values[k] = v
} if err := gf.Save(); err != nil {
cfgPath = cfgPath + "/" + c.name gf.Values[k] = oldVal
} return err
if cfgPath != "" {
file, err := os.Create(cfgPath)
if err != nil {
// Couldn't load config even though one was specified
return err
}
defer file.Close()
w := bufio.NewWriter(file)
for _, line := range configLines {
fmt.Fprintln(w, line)
}
if err = w.Flush(); err != nil {
return err
}
} }
return nil return nil
} }
// Get gets a key/value pair from gf
func (gf *GeneralConfig) Get(k string) string {
return gf.Values[k]
}

3
ext/go-xdg/.bzr/README Normal file
View File

@ -0,0 +1,3 @@
This is a Bazaar control directory.
Do not change any files in this directory.
See http://bazaar.canonical.com/ for more information about Bazaar.

View File

@ -0,0 +1 @@
Bazaar-NG meta directory, format 1

View File

@ -0,0 +1 @@
parent_location = http://bazaar.launchpad.net/~chipaca/go-xdg/trunk/

View File

@ -0,0 +1 @@
Bazaar Branch Format 7 (needs bzr 1.6)

View File

@ -0,0 +1 @@
10 john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa

View File

View File

@ -0,0 +1 @@
BZR conflict list format 1

Binary file not shown.

View File

@ -0,0 +1 @@
Bazaar Working Tree Format 6 (bzr 1.14)

View File

View File

@ -0,0 +1 @@
Bazaar repository format 2a (needs bzr 1.16 or later)

View File

@ -0,0 +1,8 @@
B+Tree Graph Index 2
node_ref_lists=0
key_elements=1
len=10
row_lengths=1
<EFBFBD>Ò;ƒ0 àÔ{
.àŒeùE±3{cGbp 8N¿<4E>†tШüæ×/<2F>ï‡ÿ Þ”?·Xw×à»1vÖt±k¬ W[Â(p
L 犸Ĥ<E28093>«.µÑ…Ë…³ £eL qc A™VÏ<56>{Ð…ß1ÛР1„„Í1åup“(”°ì Ì?ñÎaÈ%émcíÝÊ&¯}ù,¾­¡2!€ s!ÉT;åÇ<>)c<>Ý1©ä'Ý)o[ç ²Û;Þ¿ú_«ÏÖécL¢šS2FìÃûŽ}³,-³ÃŽm f´pÐT<C390>nFÌ_"=|ñJ¬vl õ¹è)ŒQŽdŠ¯~0ë9tH#b½ct+íÒ4çšRR¥Â‰Ö);DlÇÙ|u¿n—I½~Å?ìøÞÅ

Binary file not shown.

23
ext/go-xdg/LICENSE Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2014, John R. Lenton.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

39
ext/go-xdg/README.md Normal file
View File

@ -0,0 +1,39 @@
Go, XDG, go!
===========
This is `go-xdg`, a little library to help you use the `XDG`
[base directory spec](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html).
(There are other `XDG` specs, that might get included in time. Patches welcome.)
Sample usage
------------
Let's say you are writing an app called “frobz”. It has a config file
and a sqlite database. You'd do something like this:
configFileName, err := xdg.Config.Find("frobz/config.txt")
if err == nil {
// a config file exists! load it...
}
dbFileName, err := xdg.Data.Ensure("frobz/frobz.db")
// now the file and all its directories exist; it's up to you to
// determine if it's empty, etc.
Resources
---------
Both `Find` and `Ensure` take a `resource` to construct the path they return.
A resource is usually an application name (or a well-known shared resource
pool name, such as `icons`), followed by a filename. However nothing in the
standard nor in this library limits you to that; you may store e.g. your
application's configuration in just `$XDG_CONFIG_HOME/application.conf` (in
which case the "resource" here would be just `application.conf`), or in a
sub-directory of an application-specific directory.
License, etc.
------------
BSD simplified, © John R. Lenton, blah blah.

View File

@ -0,0 +1,103 @@
// (c) 2014 John R. Lenton. See LICENSE.
package xdg
import (
"os"
"os/user"
"path/filepath"
)
// An XDGDir holds configuration for and can be used to access the
// XDG-specified base directories relative to which user-specific files of a
// given type should be stored.
//
// Typically you wouldn't use XDGDir directly, but one of the
// predefined ones which implement the spec: Data, Config and Cache.
type XDGDir struct {
homeEnv string
homeDefault string
dirsEnv string
dirsDefault string
}
var (
Data *XDGDir // for data files.
Config *XDGDir // for configuration files.
Cache *XDGDir // for non-essential data files.
)
func init() {
// do this here to make the docs nicer
Data = &XDGDir{"XDG_DATA_HOME", ".local/share", "XDG_DATA_DIRS", "/usr/local/share:/usr/share"}
Config = &XDGDir{"XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS", "/etc/xdg"}
Cache = &XDGDir{"XDG_CACHE_HOME", ".cache", "", ""}
}
// Home gets the path to the given user-specific XDG directory, as specified
// (or not) by the user's environment.
func (x *XDGDir) Home() string {
dir := os.Getenv(x.homeEnv)
if dir != "" {
return dir
}
home := os.Getenv("HOME")
if home == "" {
user, err := user.Current()
if err != nil {
panic("unable to determine $HOME")
}
home = user.HomeDir
}
return filepath.Join(home, x.homeDefault)
}
// Dirs returns the preference-ordered set of base directories to search for
// files of the given type, starting with the user-specific one, as specified
// (or not) by the user's environment.
func (x *XDGDir) Dirs() []string {
dirs := []string{x.Home()}
if x.dirsEnv != "" {
xtra := os.Getenv(x.dirsEnv)
if xtra == "" {
xtra = x.dirsDefault
}
for _, path := range filepath.SplitList(xtra) {
if path != "" {
dirs = append(dirs, path)
}
}
}
return dirs
}
// Find attempts to find the path suffix in all of the known XDG directories.
// If not found, an error is returned.
func (x *XDGDir) Find(suffix string) (absPath string, err error) {
var firstError error = nil
for _, path := range x.Dirs() {
name := filepath.Join(path, suffix)
_, err = os.Stat(name)
if err == nil {
return name, nil
} else if firstError == nil {
firstError = err
}
}
return "", firstError
}
// Ensure takes the path suffix given, and ensures that a matching file exists
// in the home XDG directory. If it doesn't exist it is created. If it can't
// be created, or exists but is unreadable, an error is returned.
func (x *XDGDir) Ensure(suffix string) (absPath string, err error) {
absPath = filepath.Join(x.Home(), suffix)
err = os.MkdirAll(filepath.Dir(absPath), 0700)
if err == nil {
f, err := os.OpenFile(absPath, os.O_CREATE, 0600)
if err == nil {
f.Close()
}
}
return
}

View File

@ -0,0 +1,151 @@
// (c) 2014 John R. Lenton. See LICENSE.
package xdg
import (
. "launchpad.net/gocheck"
"os"
"path/filepath"
"strings"
"testing"
)
func TestXDGd(t *testing.T) { TestingT(t) }
type xdgdSuite struct {
home string
env1 string
val1 string
env2 string
val2 string
dir *XDGDir
}
var _ = Suite(&xdgdSuite{})
func (s *xdgdSuite) SetUpTest(c *C) {
s.home = os.Getenv("HOME")
s.env1 = "go_xdg_one"
s.env2 = "go_xdg_two"
s.val1 = "something"
s.val2 = "one:two:three"
s.dir = &XDGDir{s.env1, s.val1, s.env2, s.val2}
}
func (s *xdgdSuite) TestHomePrefersEnviron(c *C) {
err := os.Setenv(s.env1, "algo")
c.Assert(err, IsNil)
defer os.Setenv(s.env1, "")
h := s.dir.Home()
c.Check(h, Equals, "algo")
}
func (s *xdgdSuite) TestHomeUsesDefault(c *C) {
h := s.dir.Home()
c.Check(h, Matches, s.home+".*"+s.val1)
}
func (s *xdgdSuite) TestDirsPrefersEnviron(c *C) {
err := os.Setenv(s.env1, "cero")
c.Assert(err, IsNil)
defer os.Setenv(s.env1, "")
err = os.Setenv(s.env2, "uno:dos")
c.Assert(err, IsNil)
defer os.Setenv(s.env2, "")
hs := s.dir.Dirs()
c.Check(hs, DeepEquals, []string{"cero", "uno", "dos"})
}
func (s *xdgdSuite) TestDirsSkipsEmpty(c *C) {
err := os.Setenv(s.env2, "::")
c.Assert(err, IsNil)
defer os.Setenv(s.env2, "")
hs := s.dir.Dirs()
c.Check(hs, HasLen, 1)
}
func (s *xdgdSuite) TestDirsUsesDefault(c *C) {
hs := s.dir.Dirs()
c.Assert(hs, HasLen, 4)
c.Check(hs[1:], DeepEquals, strings.Split(s.val2, ":"))
c.Check(hs[0], Matches, s.home+".*"+s.val1)
}
// now repeat all the tests, but without the HOME environ.
type xdgdNoHomeSuite struct {
xdgdSuite
}
var _ = Suite(&xdgdNoHomeSuite{})
func (s *xdgdNoHomeSuite) SetUpTest(c *C) {
s.xdgdSuite.SetUpTest(c)
os.Setenv("HOME", "")
}
func (s *xdgdNoHomeSuite) TearDownTest(c *C) {
os.Setenv("HOME", s.home)
}
// and for these tests, an entirely fake HOME
type xdgdFHSuite struct {
xdgdSuite
real_home string
}
var _ = Suite(&xdgdFHSuite{})
func (s *xdgdFHSuite) SetUpTest(c *C) {
s.real_home = os.Getenv("HOME")
home := c.MkDir()
os.Setenv("HOME", home)
s.xdgdSuite.SetUpTest(c)
s.val2 = c.MkDir() + ":" + c.MkDir() + ":" + c.MkDir()
s.dir = &XDGDir{s.env1, s.val1, s.env2, s.val2}
}
func (s *xdgdFHSuite) TearDownTest(c *C) {
os.Setenv("HOME", s.real_home)
}
func (s *xdgdFHSuite) TestFind(c *C) {
vs := strings.Split(s.val2, ":")
res1 := "stuff"
exp1 := filepath.Join(s.home, s.val1, res1)
res2 := "things/that"
exp2 := filepath.Join(vs[1], res2)
res3 := "more"
exp3 := filepath.Join(vs[2], res3)
for _, d := range []string{exp1, exp2, exp3} {
err := os.MkdirAll(d, 0700)
c.Assert(err, IsNil, Commentf(d))
}
for _, it := range []struct {
res string
exp string
}{{res1, exp1}, {res2, exp2}, {res3, exp3}} {
rv, err := s.dir.Find(it.res)
c.Assert(err, IsNil)
c.Check(rv, Equals, it.exp)
}
_, err := s.dir.Find("missing")
c.Check(err, NotNil)
}
func (s *xdgdFHSuite) TestEnsureFirst(c *C) {
// creates it if missing
rv1, err := s.dir.Ensure("missing/file")
c.Assert(err, IsNil)
_, err = os.Stat(rv1)
c.Check(err, IsNil)
c.Check(rv1, Matches, s.home+".*"+"missing/file")
// just gets it if existing
rv2, err := s.dir.Ensure("missing/file")
c.Assert(err, IsNil)
c.Check(rv2, Equals, rv1)
}
func (s *xdgdFHSuite) TestEnsureFirstFailures(c *C) {
_, err := s.dir.Ensure(strings.Repeat("*", 1<<9) + "/" + strings.Repeat("*", 1<<9))
c.Assert(err, NotNil)
}

7
ext/go-xdg/doc.go Normal file
View File

@ -0,0 +1,7 @@
// (c) 2014 John R. Lenton. See LICENSE.
// xdg implements helpers for you to use the XDG spec in your apps.
//
// For now, that's just the base directory spec,
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
package xdg