~pkal/dirconf

686cfad07e185020231e6401350960837b547e64 — Philip K 2 years ago d040510
total rewrite of the library
10 files changed, 316 insertions(+), 165 deletions(-)

M dirconf.go
D duration.go
D file.go
D float32.go
D float64.go
D getbool.go
D int32.go
D int64.go
D string.go
D url.go
M dirconf.go => dirconf.go +316 -15
@@ 1,45 1,346 @@
package dirconf

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/user"
	"path"
	"strconv"
)

var (
	// ErrNoSuchKey is returned when an invalid key is requested
	ErrNoSuchKey = errors.New("No Such Key")
const schemaFile = ".schema"

var (
	// ErrWrongType is returned when the content of a value cannot be
	// cast to the requested type
	ErrWrongType = errors.New("Wrong Type")

	// ErrNoValueFound is returned when a value MUST have a value but no
	// such value could be found
	ErrNoValueFound = errors.New("Wrong Type")
)

// Option is a option
type Option interface {
	Name() string
	Set(c *Conf, r io.Reader) error
}

// Conf represents an open directory configuration
type Conf struct {
	path string
	err     error
	path    string
	options map[string]Option
}

// Open a directory configuration object
func Open(path string) (*Conf, error) {
	return &Conf{path: path}, os.MkdirAll(path, os.ModePerm)
func getPath(name string) (string, error) {
	dir := os.Getenv("XDG_CONFIG_HOME")
	if dir == "" {
		me, err := user.Current()
		if err != nil {
			return "", err
		}
		dir = path.Join(me.HomeDir, ".config")
	}
	return path.Join(dir, name), nil
}

// OpenConf a directory configuration object while paying attention to
// Open a directory configuration object while paying attention to
// XDG variables.
//
// See https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html
func OpenConf(name string) (*Conf, error) {
	dir := os.Getenv("XDG_CONFIG_HOME")
	if dir == "" {
		me, err := user.Current()
func Open(name string) (*Conf, error) {
	dir, err := getPath(name)
	if err != nil {
		return nil, err
	}
	return &Conf{path: dir}, nil
}

// Add adds a option to the current configuration
func (c *Conf) Add(o Option, key ...string) {
	if c.path == "" {
		c.path, c.err = getPath(os.Args[0])
	}
	if c.err != nil {
		return
	}
	if c.options == nil {
		c.options = make(map[string]Option)
	}
	c.options[path.Join(append([]string{c.path}, key...)...)] = o
}

func (c *Conf) writeSchema() error {
	file, err := os.OpenFile(path.Join(c.path, schemaFile),
		os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	for path, opt := range c.options {
		if opt.Name() == "" {
			continue
		}
		_, err := fmt.Fprintf(file, "%s\t%s\n", opt.Name(), path)
		if err != nil {
			return nil, err
			return err
		}
	}

		dir = path.Join(me.HomeDir, ".config")
	return nil
}

// Parse is a function
func (c *Conf) Parse() error {
	if c.err != nil {
		return c.err
	}

	err := os.MkdirAll(c.path, 0755)
	if err != nil {
		return err
	}

	err = c.writeSchema()
	if err != nil {
		return err
	}

	for path, opt := range c.options {
		file, err := os.Open(path)
		if err != nil {
			opt.Set(c, nil)
		} else {
			opt.Set(c, file)
			file.Close()
		}
	}

	return nil
}

////////////////
// INT OPTION //
////////////////

// Int is an option for a int
type Int struct {
	Ptr     *int
	Default int
	Must    bool
	OnSet   func(int)
}

// Name returns the name of the option type
func (o *Int) Name() string { return "int" }

// Set is called when the value of a Int is processed
func (o *Int) Set(c *Conf, r io.Reader) error {
	if r == nil {
		if o.Must {
			return ErrNoValueFound
		}
		*o.Ptr = o.Default
	} else {
		data, err := ioutil.ReadAll(r)
		if err != nil {
			return err
		}

		i, err := strconv.ParseInt(string(data), 10, 0)
		if err != nil {
			return ErrWrongType
		}

		*o.Ptr = int(i)
	}
	if o.OnSet != nil {
		o.OnSet(*o.Ptr)
	}
	return nil
}

//////////////////
// FLOAT OPTION //
//////////////////

// Float is an option for a int
type Float struct {
	Ptr     *float64
	Default float64
	Must    bool
	OnSet   func(float64)
}

// Name returns the name of the option type
func (o *Float) Name() string { return "float" }

// Set is called when the value of a Int is processed
func (o *Float) Set(c *Conf, r io.Reader) error {
	if r == nil {
		if o.Must {
			return ErrNoValueFound
		}
		*o.Ptr = o.Default
	} else {
		data, err := ioutil.ReadAll(r)
		if err != nil {
			return err
		}

		*o.Ptr, err = strconv.ParseFloat(string(data), 64)
		if err != nil {
			return ErrWrongType
		}
	}
	if o.OnSet != nil {
		o.OnSet(*o.Ptr)
	}
	return nil
}

///////////////////
// STRING OPTION //
///////////////////

// String is an option for a string
type String struct {
	Ptr     *string
	Default string
	Must    bool
	OnSet   func(string)
}

// Name returns the name of the option type
func (o *String) Name() string { return "string" }

// Set is called when the value of a Int is processed
func (o *String) Set(c *Conf, r io.Reader) error {
	if r == nil {
		if o.Must {
			return ErrNoValueFound
		}
		*o.Ptr = o.Default
	} else {
		data, err := ioutil.ReadAll(r)
		if err != nil {
			return err
		}

		*o.Ptr = string(data)
	}
	if o.OnSet != nil {
		o.OnSet(*o.Ptr)
	}
	return nil
}

/////////////////////////////
// LIST (of string) OPTION //
/////////////////////////////

// List is an option for a string
type List struct {
	Ptr     *[]string
	Default []string
	Must    bool
	OnSet   func([]string)
}

// Name returns the name of the option type
func (o *List) Name() string { return "stringarray" }

// Set is called when the value of a Int is processed
func (o *List) Set(c *Conf, r io.Reader) error {
	if r == nil {
		if o.Must {
			return ErrNoValueFound
		}
		*o.Ptr = o.Default
	} else {
		data, err := ioutil.ReadAll(r)
		if err != nil {
			return err
		}

		list := bytes.Split(data, []byte("\n"))
		*o.Ptr = make([]string, len(list))
		for i, b := range list {
			(*o.Ptr)[i] = string(b)
		}
	}
	if o.OnSet != nil {
		o.OnSet(*o.Ptr)
	}
	return nil
}

/////////////////
// BOOL OPTION //
/////////////////

// Bool is an option for a bool
type Bool struct {
	Ptr   *bool
	OnSet func(bool)
}

// Name returns the name of the option type
func (o *Bool) Name() string { return "boolean" }

// Set is called when the value of a Int is processed
func (o *Bool) Set(c *Conf, r io.Reader) error {
	*o.Ptr = r == nil
	if o.OnSet != nil {
		o.OnSet(*o.Ptr)
	}
	return nil
}

////////////////////
// GENERIC OPTION //
////////////////////

// Func is an option for a bool
type Func func(r io.Reader) error

// Name returns the name of the option type
func (f *Func) Name() string { return "file" }

// Set is called when the value of a Int is processed
func (f *Func) Set(c *Conf, r io.Reader) error {
	return (*f)(r)
}

/////////////////
// PATH OPTION //
/////////////////

// Path is an option for a to get the path
type Path struct {
	Ptr   *string
	OnSet func(string)
}

	return Open(path.Join(dir, name))
// Name returns the name of the option type
func (o *Path) Name() string { return "" }

// Set is called when the value of a Int is processed
func (o *Path) Set(c *Conf, r io.Reader) error {
	for p, opt := range c.options {
		if opt == o {
			*o.Ptr = p
			if o.OnSet != nil {
				o.OnSet(p)
			}
			return nil
		}
	}
	panic("this shoudln't happen")
}

D duration.go => duration.go +0 -17
@@ 1,17 0,0 @@
package dirconf

import "time"

// Duration returns an time.Duration object from the current configuration
func (c *Conf) Duration(key string) (time.Duration, error) {
	str, err := c.String(key)
	if err != nil {
		return 0, err
	}

	d, err := time.ParseDuration(str)
	if err != nil {
		return 0, ErrWrongType
	}
	return d, nil
}

D file.go => file.go +0 -19
@@ 1,19 0,0 @@
package dirconf

import (
	"os"
	"path"
)

// File returns an os.File object from the current configuration
func (c *Conf) File(key ...string) (file *os.File, err error) {
	file, err = os.Open(path.Join(append(key, c.path)...))
	if err != nil {
		if os.IsNotExist(err) {
			return nil, ErrNoSuchKey
		}
		return
	}

	return file, nil
}

D float32.go => float32.go +0 -18
@@ 1,18 0,0 @@
package dirconf

import "fmt"

// Float32 returns an Float32 value from the current configuration
func (c *Conf) Float32(key ...string) (float32, error) {
	str, err := c.String(key...)
	if err != nil {
		return 0, err
	}

	var f float32
	_, err = fmt.Sscanf(str, "%f", &f)
	if err != nil {
		return 0, ErrWrongType
	}
	return f, nil
}

D float64.go => float64.go +0 -18
@@ 1,18 0,0 @@
package dirconf

import "fmt"

// Float64 returns an Float64 value from the current configuration
func (c *Conf) Float64(key ...string) (float64, error) {
	str, err := c.String(key...)
	if err != nil {
		return 0, err
	}

	var f float64
	_, err = fmt.Sscanf(str, "%f", &f)
	if err != nil {
		return 0, ErrWrongType
	}
	return f, nil
}

D getbool.go => getbool.go +0 -8
@@ 1,8 0,0 @@
package dirconf

// String returns a String from the current configuration
func (c *Conf) Bool(key ...string) (b bool, err error) {
	file, err := c.File(key...)
	file.Close()
	return err != nil, err
}

D int32.go => int32.go +0 -18
@@ 1,18 0,0 @@
package dirconf

import "fmt"

// Int32 returns an Int32eger from the current configuration
func (c *Conf) Int32(key ...string) (int32, error) {
	str, err := c.String(key...)
	if err != nil {
		return 0, err
	}

	var i int32
	_, err = fmt.Sscanf(str, "%d", &i)
	if err != nil {
		return 0, ErrWrongType
	}
	return i, nil
}

D int64.go => int64.go +0 -18
@@ 1,18 0,0 @@
package dirconf

import "fmt"

// Int64 returns an Int64eger from the current configuration
func (c *Conf) Int64(key ...string) (int64, error) {
	str, err := c.String(key...)
	if err != nil {
		return 0, err
	}

	var i int64
	_, err = fmt.Sscanf(str, "%d", &i)
	if err != nil {
		return 0, ErrWrongType
	}
	return i, nil
}

D string.go => string.go +0 -17
@@ 1,17 0,0 @@
package dirconf

import (
	"io/ioutil"
)

// GetString returns a String from the current configuration
func (c *Conf) String(key ...string) (string, error) {
	file, err := c.File(key...)
	if err != nil {
		return "", err
	}

	byte, err := ioutil.ReadAll(file)
	file.Close()
	return string(byte), nil
}

D url.go => url.go +0 -17
@@ 1,17 0,0 @@
package dirconf

import "net/url"

// URL returns an url.URL object from the current configuration
func (c *Conf) URL(key string) (*url.URL, error) {
	str, err := c.String(key)
	if err != nil {
		return nil, err
	}

	u, err := url.Parse(str)
	if err != nil {
		return nil, ErrWrongType
	}
	return u, nil
}