From 9dce529fe5a239c1bde8b4970b12625f149ae7a5 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Tue, 9 Feb 2016 08:32:36 -0600 Subject: [PATCH] Copied over from Gogs Repo --- README.md | 3 + config.go | 128 +++++++++++++++ config_file.go | 82 ++++++++++ ext/go-xdg/.bzr/README | 3 + ext/go-xdg/.bzr/branch-format | 1 + ext/go-xdg/.bzr/branch/branch.conf | 1 + ext/go-xdg/.bzr/branch/format | 1 + ext/go-xdg/.bzr/branch/last-revision | 1 + ext/go-xdg/.bzr/branch/tags | 0 ext/go-xdg/.bzr/checkout/conflicts | 1 + ext/go-xdg/.bzr/checkout/dirstate | Bin 0 -> 1336 bytes ext/go-xdg/.bzr/checkout/format | 1 + ext/go-xdg/.bzr/checkout/views | 0 ext/go-xdg/.bzr/repository/format | 1 + .../d61ea2acf68934c72101b4363af27ab1.cix | Bin 0 -> 518 bytes .../d61ea2acf68934c72101b4363af27ab1.iix | Bin 0 -> 418 bytes .../d61ea2acf68934c72101b4363af27ab1.rix | Bin 0 -> 418 bytes .../d61ea2acf68934c72101b4363af27ab1.six | 8 + .../d61ea2acf68934c72101b4363af27ab1.tix | Bin 0 -> 746 bytes ext/go-xdg/.bzr/repository/pack-names | Bin 0 -> 142 bytes .../d61ea2acf68934c72101b4363af27ab1.pack | Bin 0 -> 24862 bytes ext/go-xdg/LICENSE | 23 +++ ext/go-xdg/README.md | 39 +++++ ext/go-xdg/base_directory.go | 103 ++++++++++++ ext/go-xdg/base_directory_test.go | 151 ++++++++++++++++++ ext/go-xdg/doc.go | 7 + 26 files changed, 554 insertions(+) create mode 100644 README.md create mode 100644 config.go create mode 100644 config_file.go create mode 100644 ext/go-xdg/.bzr/README create mode 100644 ext/go-xdg/.bzr/branch-format create mode 100644 ext/go-xdg/.bzr/branch/branch.conf create mode 100644 ext/go-xdg/.bzr/branch/format create mode 100644 ext/go-xdg/.bzr/branch/last-revision create mode 100644 ext/go-xdg/.bzr/branch/tags create mode 100644 ext/go-xdg/.bzr/checkout/conflicts create mode 100644 ext/go-xdg/.bzr/checkout/dirstate create mode 100644 ext/go-xdg/.bzr/checkout/format create mode 100644 ext/go-xdg/.bzr/checkout/views create mode 100644 ext/go-xdg/.bzr/repository/format create mode 100644 ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.cix create mode 100644 ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.iix create mode 100644 ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.rix create mode 100644 ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.six create mode 100644 ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.tix create mode 100644 ext/go-xdg/.bzr/repository/pack-names create mode 100644 ext/go-xdg/.bzr/repository/packs/d61ea2acf68934c72101b4363af27ab1.pack create mode 100644 ext/go-xdg/LICENSE create mode 100644 ext/go-xdg/README.md create mode 100644 ext/go-xdg/base_directory.go create mode 100644 ext/go-xdg/base_directory_test.go create mode 100644 ext/go-xdg/doc.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb5ac84 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# user-config + +A go library for easily managing config files/directories in your XDGConfig directory \ No newline at end of file diff --git a/config.go b/config.go new file mode 100644 index 0000000..a849a08 --- /dev/null +++ b/config.go @@ -0,0 +1,128 @@ +// Package userConfig eases the use of config files in a user's home directory +package userConfig + +import ( + "errors" + "os" + "strings" + + "gogs.bullercodeworks.com/brian/user-config/ext/go-xdg" +) + +// Config is a stuct for managing the config +type Config struct { + name string + generalConfig *GeneralConfig +} + +// NewConfig generates a Config struct +func NewConfig(name string) (*Config, error) { + c := &Config{name: name} + if err := c.Load(); err != nil { + return c, err + } + return c, nil +} + +// Set at the config level sets a value in the .conf file +func (c *Config) Set(k, v string) error { + return c.generalConfig.Set(k, v) +} + +// Get at the config level retrieves a value from the .conf file +func (c *Config) Get(k string) string { + 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 +func (c *Config) Load() error { + var err error + if strings.TrimSpace(c.name) == "" { + return errors.New("Invalid Config Name: " + c.name) + } + + var cfgPath string + cfgPath = xdg.Config.Dirs()[0] + if cfgPath != "" { + cfgPath = cfgPath + "/" + c.name + if err = c.verifyOrCreateDirectory(cfgPath); err != nil { + return err + } + // We always have a .conf file + //cfgPath = cfgPath + "/" + c.name + ".conf" + } + // Load general config + if c.generalConfig, err = NewGeneralConfig(c.name, cfgPath); err != nil { + return err + } + + return nil +} + +// Save writes the config to file(s) +func (c *Config) Save() error { + if c.generalConfig == nil { + return errors.New("Bad setup.") + } + return c.generalConfig.Save() + /* + 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 != "" { + 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 + */ +} + +// verifyOrCreateDirectory is a helper function for building an +// individual directory +func (c *Config) verifyOrCreateDirectory(path string) error { + var tstDir *os.File + var tstDirInfo os.FileInfo + var err error + if tstDir, err = os.Open(path); err != nil { + if err = os.Mkdir(path, 0755); err != nil { + return err + } + if tstDir, err = os.Open(path); err != nil { + return err + } + } + if tstDirInfo, err = tstDir.Stat(); err != nil { + return err + } + if !tstDirInfo.IsDir() { + return errors.New(path + " exists and is not a directory") + } + // We were able to open the path and it was a directory + return nil +} diff --git a/config_file.go b/config_file.go new file mode 100644 index 0000000..076a7ce --- /dev/null +++ b/config_file.go @@ -0,0 +1,82 @@ +// Package userConfig eases the use of config files in a user's home directory +package userConfig + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "strings" + + "github.com/BurntSushi/toml" +) + +// 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"` + RawFiles []string `toml:"raw_files"` + Values map[string]string `toml:"general"` +} + +// NewGeneralConfig generates a General Config struct +func NewGeneralConfig(name, path string) (*GeneralConfig, error) { + gf := &GeneralConfig{Name: name, Path: path} + gf.ConfigFiles = []string{} + gf.RawFiles = []string{} + gf.Values = make(map[string]string) + + if err := gf.Load(); err != nil { + return gf, err + } + return gf, nil +} + +// Load loads config files into the config +func (gf *GeneralConfig) Load() error { + if strings.TrimSpace(gf.Name) == "" || strings.TrimSpace(gf.Path) == "" { + return errors.New("Invalid ConfigFile Name: " + gf.Path + "/" + gf.Name) + } + + // Config files end with .conf + cfgPath := gf.Path + "/" + gf.Name + ".conf" + tomlData, err := ioutil.ReadFile(cfgPath) + if err != nil { + return err + } + if _, err := toml.Decode(string(tomlData), &gf); err != nil { + return err + } + return nil +} + +// Save writes the config to file(s) +func (gf *GeneralConfig) Save() error { + buf := new(bytes.Buffer) + cfgPath := gf.Path + "/" + gf.Name + ".conf" + fmt.Println("Writing Config File: " + cfgPath) + if err := toml.NewEncoder(buf).Encode(gf); err != nil { + return err + } + fmt.Println("Writing Config File: " + buf.String()) + return ioutil.WriteFile(cfgPath, buf.Bytes(), 0644) +} + +// Set sets a key/value pair in gf, if unable to save, revert to old value +// (and return the error) +func (gf *GeneralConfig) Set(k, v string) error { + oldVal := gf.Values[k] + gf.Values[k] = v + if err := gf.Save(); err != nil { + gf.Values[k] = oldVal + return err + } + return nil +} + +// Get gets a key/value pair from gf +func (gf *GeneralConfig) Get(k string) string { + return gf.Values[k] +} \ No newline at end of file diff --git a/ext/go-xdg/.bzr/README b/ext/go-xdg/.bzr/README new file mode 100644 index 0000000..f82dc1c --- /dev/null +++ b/ext/go-xdg/.bzr/README @@ -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. diff --git a/ext/go-xdg/.bzr/branch-format b/ext/go-xdg/.bzr/branch-format new file mode 100644 index 0000000..9eb09b7 --- /dev/null +++ b/ext/go-xdg/.bzr/branch-format @@ -0,0 +1 @@ +Bazaar-NG meta directory, format 1 diff --git a/ext/go-xdg/.bzr/branch/branch.conf b/ext/go-xdg/.bzr/branch/branch.conf new file mode 100644 index 0000000..8877240 --- /dev/null +++ b/ext/go-xdg/.bzr/branch/branch.conf @@ -0,0 +1 @@ +parent_location = http://bazaar.launchpad.net/~chipaca/go-xdg/trunk/ diff --git a/ext/go-xdg/.bzr/branch/format b/ext/go-xdg/.bzr/branch/format new file mode 100644 index 0000000..dc392f4 --- /dev/null +++ b/ext/go-xdg/.bzr/branch/format @@ -0,0 +1 @@ +Bazaar Branch Format 7 (needs bzr 1.6) diff --git a/ext/go-xdg/.bzr/branch/last-revision b/ext/go-xdg/.bzr/branch/last-revision new file mode 100644 index 0000000..c518c0b --- /dev/null +++ b/ext/go-xdg/.bzr/branch/last-revision @@ -0,0 +1 @@ +10 john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa diff --git a/ext/go-xdg/.bzr/branch/tags b/ext/go-xdg/.bzr/branch/tags new file mode 100644 index 0000000..e69de29 diff --git a/ext/go-xdg/.bzr/checkout/conflicts b/ext/go-xdg/.bzr/checkout/conflicts new file mode 100644 index 0000000..0dc2d3a --- /dev/null +++ b/ext/go-xdg/.bzr/checkout/conflicts @@ -0,0 +1 @@ +BZR conflict list format 1 diff --git a/ext/go-xdg/.bzr/checkout/dirstate b/ext/go-xdg/.bzr/checkout/dirstate new file mode 100644 index 0000000000000000000000000000000000000000..0bd7e6b0861266d2899edf0c109683a50ea1f65e GIT binary patch literal 1336 zcmbu;OK%e~5CCAG^D828-e^4IXHOu=0SP$352(EMI9<9AvJbk=ug9SvAu5qZtYq05 zt>yXbnOAiyY^3-) z-1WP*gogfg4E@kIF|@llbQOYBfZ)I@2T&cBI_WOiI1bd!YY-a&We72k%(vq(%uhQc zIw=9rR;h8+Jbn*d7|>b2R#GHUHuYk?@csVpLUKQkBBjbvi)wH&aXl2^klJ%%AKt&+ zfBLc)Z4b&Jk*{*oWRA#9n2`GwhK5p$C?o+D@CzV_ByF|AVQM|wmQs6^3!fpM_ix^P-0!+1Mh>atvbu=DYcM7xN1Twz z_~5{?WYVBfDQ!qPkkWfXDj4Z6V@llskk_VqxJ#DDq*AC)A%=Ly)M4V=Qmq{4VSL;j zhUbxsICTY1QqIz0uxx?^9qZ(sbH*7%CIw6$NqYbmXwA7aFTB)0`{6Ij?ab5sT=*2e zh}2gglaJ_)v&vB})fWM5%`sqht_F~nBNjJAK}zCFkVaZ?3aWXaWBYhd$L4hEFplRJ zOhY`Qo~B<6l2Vc*Tg?l%Sx@(AsgK{LLw!|Oflo$d^e9tK%95`=CkRm)6@2L@r)=pj KrvxhZpNb!Qy`<3q literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/checkout/format b/ext/go-xdg/.bzr/checkout/format new file mode 100644 index 0000000..e0261c7 --- /dev/null +++ b/ext/go-xdg/.bzr/checkout/format @@ -0,0 +1 @@ +Bazaar Working Tree Format 6 (bzr 1.14) diff --git a/ext/go-xdg/.bzr/checkout/views b/ext/go-xdg/.bzr/checkout/views new file mode 100644 index 0000000..e69de29 diff --git a/ext/go-xdg/.bzr/repository/format b/ext/go-xdg/.bzr/repository/format new file mode 100644 index 0000000..b200528 --- /dev/null +++ b/ext/go-xdg/.bzr/repository/format @@ -0,0 +1 @@ +Bazaar repository format 2a (needs bzr 1.16 or later) diff --git a/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.cix b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.cix new file mode 100644 index 0000000000000000000000000000000000000000..918495883fdfbbe37536974fe1898de1c7b7b68c GIT binary patch literal 518 zcmV+h0{Q(yD^zl2Wgtg#VQ^?5Np56icpx$gZf|5|Uvgz;UuDI})Ij$(I;;8l@h00sT6UL*>Fb*9$Qw19%CZfN?n4 z_F5*yoQcT|8YVk$2!bsa3BU`nhKQGeNQMn84{|p(*WgN~LyETddOYTabur2{_qdkq z_x!Ye)5UUN=|(mOv6j>h!hN0XOH1v`JY4g?1MS#n#Q>S0p~BhjgzXrf^rKoya$?^c z^!gVr(l=raq9wDF=Cr{XxFD@UvW_f4r?lyHL-h288^;@w)W1J7RA(2~#Jz&_>@JXH zVrr4qxM>`1fmGfpc7_gVwI|#PfS*oJVK*}vnQA(3-}gm2z-JvVM67PhyMbnaI9Jh; z#OH}vAeg2c6TL0+?C~?o8(~ApyY@45vZ2J8Ef8IJrcMdeC8_?kOu%QM@A;rSLI`9d zYAf}1o|7Y$N&;rXkh)7XSbN literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.iix b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.iix new file mode 100644 index 0000000000000000000000000000000000000000..73f2931952d6f8426272047de076e5eb080ef100 GIT binary patch literal 418 zcmV;T0bTw=D^zl2Wgtg#VQ^?5Np56icpx$gZf|5|Uvgz;Uu(-kt598x_D2&+#sLK& z&k#un$!sSsyi3h+uDUWI5B`Ek zvNt4-NKry2fVF{Y|GH6q8OM^FW)tEwe?bU^GbE0XC@_PBRRfWFCf(iT&*ntuIAWE* zAV{ehvI)UK5bMLUZuIo%%y%tO5l5`@e-R*ONF0%Y5*YwHcezbUD@_xqYzMsw@tMCM MU}o%p09rPOlvU@tzW@LL literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.rix b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.rix new file mode 100644 index 0000000000000000000000000000000000000000..7593b1b20e3a3282a50cd7d3d207c9aa337fbc2a GIT binary patch literal 418 zcmV;T0bTw=D^zl2Wgtg#VQ^?5Np56icpx$gZf|5|Uvgz;UujelOTIZso?OuI|*-oGiNN*767p@2g|1*u2G^db72%N}ixMO^XG zzbOQ+l3fZ$Ig%kfnpV%(PJG`XmT|>L|E5627D-%@(3B^XlA_P7sH`$=z^WVdF2#HQ MrdS=vUrD@)o1o&o`v3p{ literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.six b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.six new file mode 100644 index 0000000..2fc2675 --- /dev/null +++ b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.six @@ -0,0 +1,8 @@ +B+Tree Graph Index 2 +node_ref_lists=0 +key_elements=1 +len=10 +row_lengths=1 +x;0 { +.eE3{cGbp 8NtШ/ ޔ?Xw1vtk W[(p +L 犸.х˅ %eL qc AVϐ{Ѕ1Р11up(?a%mc&},2!as!T;ǝ)c1')o[ 3;޿_cLS2F},-Îm f<pTnF_"=|Jvl )Qd~09tH#bct+4RR‰);Dl|unI~? \ No newline at end of file diff --git a/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.tix b/ext/go-xdg/.bzr/repository/indices/d61ea2acf68934c72101b4363af27ab1.tix new file mode 100644 index 0000000000000000000000000000000000000000..bdb632bb263ec07e800479a05684ae77f29bfd45 GIT binary patch literal 746 zcmVTko1aG(U}QLQ@&-wZ3^%=@!o@lA~$s!upo>`oS|j_P+wU*xcsXvpiBdP81D=$;ZOZzz^e{fjQjNX}}UICNV~3%xzX%VcGz7Kj>uOf5&?Vp2&Y` zKYG{o+ovsLZ(5B6Gr`6uXCh%EFYU(#-&XpYkQdO+aO1X^h-+UN*L3db7kys(#eH>c zZ^HzPi}S6u9~_)Z_-4_L6Eh*#t<$!4+uA-YPTv$RQ<)rpqZ9|W{sjO4|No=1?Kpy$ zK7K^?qNxeQqg3@G?u(VRB)XQEqm!S*B%1YFcp;RlP`5 z31nhnW?>5PC^fx^r#huiKq7k)RK!6%%2iU7ni^k}pI?Gxxq*QZFr{Z&7-v?NCFUmP c8(ElJR#2^&1wyWhlyrigHKw9{0K$_r@wWzBcK`qY literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/repository/pack-names b/ext/go-xdg/.bzr/repository/pack-names new file mode 100644 index 0000000000000000000000000000000000000000..46bc0bacb750042abfcd18595eb2df06670f5f6c GIT binary patch literal 142 zcmZ?L4k=1aRd6p#EXYvs%u7kFP%z@k%TGy-FG@{|&&ez%1*6}Pt8fqP0a&x z4Y_hs^MGVgetA5Qm0pqo604Y_y;8?x>se1U!4d77CUnHcyO o8JZdU`4}7d`xvDp7#MB|U`Q+$HfBxc_GS%s=GJ2fF6aLN0E&|>`v3p{ literal 0 HcmV?d00001 diff --git a/ext/go-xdg/.bzr/repository/packs/d61ea2acf68934c72101b4363af27ab1.pack b/ext/go-xdg/.bzr/repository/packs/d61ea2acf68934c72101b4363af27ab1.pack new file mode 100644 index 0000000000000000000000000000000000000000..93e98b0f44c9586b37feef8fc077445407dc96bf GIT binary patch literal 24862 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 V&pb}b>B)=p_kSz=-#(+*=tXPHW&{8L literal 0 HcmV?d00001 diff --git a/ext/go-xdg/LICENSE b/ext/go-xdg/LICENSE new file mode 100644 index 0000000..f6fb46d --- /dev/null +++ b/ext/go-xdg/LICENSE @@ -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. diff --git a/ext/go-xdg/README.md b/ext/go-xdg/README.md new file mode 100644 index 0000000..b59a263 --- /dev/null +++ b/ext/go-xdg/README.md @@ -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. diff --git a/ext/go-xdg/base_directory.go b/ext/go-xdg/base_directory.go new file mode 100644 index 0000000..fe9cdd0 --- /dev/null +++ b/ext/go-xdg/base_directory.go @@ -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 +} diff --git a/ext/go-xdg/base_directory_test.go b/ext/go-xdg/base_directory_test.go new file mode 100644 index 0000000..387322c --- /dev/null +++ b/ext/go-xdg/base_directory_test.go @@ -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) +} diff --git a/ext/go-xdg/doc.go b/ext/go-xdg/doc.go new file mode 100644 index 0000000..2868481 --- /dev/null +++ b/ext/go-xdg/doc.go @@ -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