Merge branch 'feature/restructuring' into develop
authorOllivier Robert <roberto@keltia.net>
Wed, 12 Apr 2017 14:27:09 +0200
changeset 81 ba90955d2d56
parent 68 6252b7eea308 (current diff)
parent 80 d6e8807818c4 (diff)
child 82 09f5e04b1b37
child 83 adc39ae774c0
Merge branch 'feature/restructuring' into develop
cmd/gondole-cli/config.go
cmd/gondole-cli/config_test.go
cmd/gondole-cli/test/config.toml
cmd/gondole-cli/test/foo.token
cmd/gondole-cli/test/garbage.token
cmd/gondole-cli/test/perms.toml
config.go
config_test.go
test/config.toml
test/foo.token
test/garbage.token
test/perms.toml
--- a/app.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/app.go	Wed Apr 12 14:27:09 2017 +0200
@@ -7,13 +7,7 @@
 	"strings"
 )
 
-var (
-	ourScopes = []string{
-		"read",
-		"write",
-		"follow",
-	}
-)
+var ()
 
 type registerApp struct {
 	ID           string `json:"id"`
@@ -21,9 +15,17 @@
 	ClientSecret string `json:"client_secret"`
 }
 
-func registerApplication(name string, scopes []string, redirectURI, baseURL string) (g *Gondole, err error) {
-	g = &Gondole{
+// NewApp registers a new instance
+func NewApp(name string, scopes []string, redirectURI, baseURL string) (g *Client, err error) {
+	var endpoint string
+
+	if baseURL != "" {
+		endpoint = baseURL
+	}
+
+	g = &Client{
 		Name: name,
+		APIBase: endpoint,
 	}
 
 	req := g.prepareRequest("apps")
@@ -47,54 +49,16 @@
 	if err != nil {
 		log.Fatalf("error can not register app: %v", err)
 	}
-	g.ID = resp.ClientID
-	g.Secret = resp.ClientSecret
 
-	server := &Server{
-		ID:          g.ID,
-		Name:        name,
-		BearerToken: g.Secret,
-		BaseURL:     baseURL,
-	}
-	err = server.WriteToken(name)
 	if err != nil {
 		log.Fatalf("error: can not write token for %s", name)
 	}
-	return
-}
 
-// NewApp registers a new instance
-func NewApp(name string, scopes []string, redirectURI, baseURL  string) (g *Gondole, err error) {
-
-	if baseURL != "" {
-		APIEndpoint = baseURL
-	}
-
-	// Load configuration, will register if none is found
-	cnf, err := LoadConfig(name)
-	if err != nil {
-		// Nothing exist yet
-		cnf := Config{
-			Default: name,
-		}
-		err = cnf.Write()
-		if err != nil {
-			log.Fatalf("error: can not write config for %s", name)
-		}
-
-		// Now register this through OAuth
-		if scopes == nil {
-			scopes = ourScopes
-		}
-
-		g, err = registerApplication(name, scopes, redirectURI, baseURL)
-
-	} else {
-		g = &Gondole{
-			Name:   cnf.Name,
-			ID:     cnf.ID,
-			Secret: cnf.BearerToken,
-		}
+	g = &Client{
+		Name:    name,
+		ID:      resp.ClientID,
+		Secret:  resp.ClientSecret,
+		APIBase: endpoint,
 	}
 
 	return
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/config.go	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,140 @@
+// config.go
+//
+// This file implements the configuration part for when you need the API
+// key to modify things in the Mastodon configuration and manage measurements.
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+
+	"github.com/naoina/toml"
+)
+
+/*
+Assume the application is registered if $HOME/.config/<gondole>/config.toml already exist
+We will store the per-instance token into $HOME/.config/<gondole>/<site>.token
+*/
+
+const (
+	DefaultName = "config.toml"
+)
+
+var (
+	baseDir = filepath.Join(os.Getenv("HOME"),
+		".config",
+		"gondole",
+	)
+)
+
+func loadGlobal(file string) (c *Config, err error) {
+	log.Printf("file=%s", file)
+	// Check if there is any config file
+	_, err = os.Stat(file)
+	if err != nil {
+		return
+	}
+
+	log.Printf("file=%s, found it", file)
+	// Read it
+	buf, err := ioutil.ReadFile(file)
+	if err != nil {
+		return c, fmt.Errorf("Can not read %s", file)
+	}
+
+	cnf := Config{}
+	err = toml.Unmarshal(buf, &cnf)
+	if err != nil {
+		return c, fmt.Errorf("Error parsing toml %s: %v", file, err)
+	}
+	c = &cnf
+	return
+}
+
+func loadInstance(name string) (s *Server, err error) {
+	// Load instance-specific file
+	file := filepath.Join(baseDir, name+".token")
+
+	log.Printf("instance is %s", file)
+
+	// Check if there is any config file
+	if _, err = os.Stat(file); err == nil {
+		// Read it
+		buf, err := ioutil.ReadFile(file)
+		if err != nil {
+			return s, fmt.Errorf("Can not read %s", file)
+		}
+
+		sc := Server{}
+		err = toml.Unmarshal(buf, &sc)
+		if err != nil {
+			return s, fmt.Errorf("Error parsing toml %s: %v", file, err)
+		}
+		s = &sc
+	}
+	return
+}
+
+func GetInstanceList() (list []string) {
+	list, err := filepath.Glob(filepath.Join(baseDir, "*.token"))
+	log.Printf("basedir=%s", filepath.Join(baseDir, "*.token"))
+	if err != nil {
+		log.Printf("warning, no *.token files in %s", baseDir)
+		list = nil
+	}
+	log.Printf("list=%v", list)
+	return
+}
+
+// LoadConfig reads a file as a TOML document and return the structure
+func LoadConfig(name string) (s *Server, err error) {
+	// Load global file
+	gFile := filepath.Join(baseDir, DefaultName)
+
+	log.Printf("global is %s", gFile)
+	c, err := loadGlobal(gFile)
+	if err != nil {
+		return
+	}
+	if name == "" {
+		s, err = loadInstance(c.Default)
+	} else {
+		s, err = loadInstance(name)
+	}
+
+	return s, err
+}
+
+func (c *Config) Write() (err error) {
+	if err = os.MkdirAll(baseDir, 0700); err != nil {
+		log.Fatalf("error creating configuration directory: %s", baseDir)
+	}
+
+	var sc []byte
+
+	if sc, err = toml.Marshal(*c); err != nil {
+		log.Fatalf("error saving configuration")
+	}
+	err = ioutil.WriteFile(filepath.Join(baseDir, DefaultName), sc, 0600)
+	return
+}
+
+func (s *Server) WriteToken(instance string) (err error) {
+	if err = os.MkdirAll(baseDir, 0700); err != nil {
+		log.Fatalf("error creating configuration directory: %s", baseDir)
+	}
+
+	var sc []byte
+
+	if sc, err = toml.Marshal(s); err != nil {
+		log.Fatalf("error saving configuration")
+	}
+
+	full := instance + ".token"
+	err = ioutil.WriteFile(filepath.Join(baseDir, full), sc, 0600)
+	return
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/config_test.go	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,105 @@
+package main
+
+import (
+	"github.com/stretchr/testify/assert"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestLoadGlobal(t *testing.T) {
+	baseDir = "."
+
+	_, err := loadGlobal(filepath.Join("test", "non.toml"))
+	assert.Error(t, err, "does not exist")
+
+	_, err = loadGlobal(filepath.Join("test", "garbage.token"))
+	assert.Error(t, err, "just garbage")
+
+	// git does now allow you to checkin 000 files :(
+	err = os.Chmod(filepath.Join("test", "perms.toml"), 0000)
+	assert.NoError(t, err, "should be fine")
+	_, err = loadGlobal(filepath.Join("test", "perms.toml"))
+	assert.Error(t, err, "unreadable")
+	err = os.Chmod(filepath.Join("test", "perms.toml"), 0600)
+	assert.NoError(t, err, "should be fine")
+
+	c, err := loadGlobal(filepath.Join("test", "config.toml"))
+	assert.NoError(t, err, "should read it fine")
+	assert.EqualValues(t, "foo", c.Default, "equal")
+}
+
+func TestLoadInstance(t *testing.T) {
+	baseDir = "."
+
+	_, err := loadInstance("nonexistent")
+	assert.Error(t, err, "does not exist")
+
+	file := filepath.Join("test", "garbage")
+	_, err = loadInstance(file)
+	assert.Error(t, err, "just garbage")
+
+	file = filepath.Join("test", "foo.token")
+	err = os.Chmod(file, 0000)
+	assert.NoError(t, err, "should be fine")
+
+	file = filepath.Join("test", "foo")
+	_, err = loadInstance(file)
+	assert.Error(t, err, "unreadable")
+
+	file = filepath.Join("test", "foo.token")
+	err = os.Chmod(file, 0644)
+	assert.NoError(t, err, "should be fine")
+
+	real := &Server{
+		ID:          "666",
+		Name:        "foo",
+		BearerToken: "d3b07384d113edec49eaa6238ad5ff00",
+		BaseURL:     "https://mastodon.social",
+	}
+	file = filepath.Join("test", "foo")
+	s, err := loadInstance(file)
+	assert.NoError(t, err, "all fine")
+	assert.Equal(t, real, s, "equal")
+}
+
+func TestGetInstanceList(t *testing.T) {
+	baseDir = "test"
+
+	real := []string{
+		filepath.Join("test", "foo.token"),
+		filepath.Join("test", "garbage.token"),
+	}
+	list := GetInstanceList()
+	assert.Equal(t, real, list, "equal")
+
+	baseDir = "/tmp"
+	real = nil
+	list = GetInstanceList()
+	assert.Equal(t, real, list, "equal")
+
+	baseDir = "/nonexistent"
+	real = nil
+	list = GetInstanceList()
+	assert.Equal(t, real, list, "equal")
+}
+
+func TestLoadConfig(t *testing.T) {
+	baseDir = "test"
+
+	_, err := LoadConfig("foo")
+	assert.NoError(t, err, "should be fine")
+
+	_, err = LoadConfig("")
+	assert.NoError(t, err, "should be fine")
+
+	err = os.Chmod(filepath.Join("test", "config.toml"), 0000)
+	assert.NoError(t, err, "should be fine")
+
+	_, err = LoadConfig("")
+	assert.Error(t, err, "should be unreadable")
+
+	err = os.Chmod(filepath.Join("test", "config.toml"), 0600)
+	assert.NoError(t, err, "should be fine")
+
+}
\ No newline at end of file
--- a/cmd/gondole-cli/main.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/cmd/gondole-cli/main.go	Wed Apr 12 14:27:09 2017 +0200
@@ -5,17 +5,95 @@
 	"github.com/urfave/cli"
 	"log"
 	"os"
+	"strings"
 )
 
 var (
-	fVerbose bool
-	fBaseURL string
-    instance *gondole.Gondole
-	cnf      *gondole.Config
+	fVerbose  bool
+	fInstance string
+	fScopes   string
+
+	instance *gondole.Client
+	cnf      *Server
+
+	// For bootstrapping, override the API endpoint w/o any possible /api/vN, that is
+	// supplied by the library
+	APIEndpoint string
+
+	// Deduced though the full instance URL when registering
+	InstanceName string
+
+	// Default scopes
+	ourScopes = []string{
+		"read",
+		"write",
+		"follow",
+	}
 )
 
-func Register(c *cli.Context) (err error) {
-    instance, err = gondole.NewApp("gondole-cli", nil, gondole.NoRedirect, fBaseURL)
+// Config holds our parameters
+type Server struct {
+	ID          string `json:"id"`
+	Name        string `json:"name"`
+	BearerToken string `json:"bearer_token"`
+	BaseURL     string `json:"base_url"` // Allow for overriding APIEndpoint on registration
+}
+
+type Config struct {
+	Default string
+}
+
+func setupEnvironment(c *cli.Context) (err error) {
+	var scopes []string
+
+	if fInstance != "" {
+		InstanceName = basename(fInstance)
+		APIEndpoint = filterURL(fInstance)
+	}
+
+	// Load configuration, will register if none is found
+	cnf, err = LoadConfig(InstanceName)
+	if err != nil {
+		// Nothing exist yet
+		defName := Config{
+			Default: InstanceName,
+		}
+		err = defName.Write()
+		if err != nil {
+			log.Fatalf("error: can not write config for %s", InstanceName)
+		}
+
+		// Now register this through OAuth
+		if fScopes != "" {
+			scopes = strings.Split(fScopes, " ")
+		} else {
+			scopes = ourScopes
+		}
+
+		instance, err = gondole.NewApp("gondole-cli", scopes, gondole.NoRedirect, fInstance)
+
+		server := &Server{
+			ID:          instance.ID,
+			Name:        instance.Name,
+			BearerToken: instance.Secret,
+			BaseURL:     instance.APIBase,
+		}
+		err = server.WriteToken(InstanceName)
+		if err != nil {
+			log.Fatalf("error: can not write token for %s", instance.Name)
+		}
+
+		cnf := Config{
+			Default: instance.Name,
+		}
+
+		err = cnf.Write()
+		if err != nil {
+			log.Fatalf("error: can not write config for %s", instance.Name)
+		}
+
+	}
+	// Log in to the instance
 	return err
 }
 
@@ -36,7 +114,7 @@
 	app.Version = gondole.APIVersion
 	//app.HideVersion = true
 
-	app.Before = Register
+	app.Before = setupEnvironment
 
 	app.Flags = []cli.Flag{
 		cli.BoolFlag{
@@ -45,9 +123,14 @@
 			Destination: &fVerbose,
 		},
 		cli.StringFlag{
-			Name:        "instance,i",
+			Name:        "instance,I",
 			Usage:       "use that instance",
-			Destination: &fBaseURL,
+			Destination: &fInstance,
+		},
+		cli.StringFlag{
+			Name:        "scopes,S",
+			Usage:       "use these scopes",
+			Destination: &fScopes,
 		},
 	}
 	app.Run(os.Args)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/test/config.toml	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,1 @@
+default="foo"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/test/foo.token	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,4 @@
+id = "666"
+name = "foo"
+bearer_token = "d3b07384d113edec49eaa6238ad5ff00"
+base_URL = "https://mastodon.social"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/test/garbage.token	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,1 @@
+>'''(fsljfùùksdjlf1656>
--- a/cmd/gondole-cli/utils.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/cmd/gondole-cli/utils.go	Wed Apr 12 14:27:09 2017 +0200
@@ -1,6 +1,10 @@
 package main
 
-import "github.com/urfave/cli"
+import (
+    "github.com/urfave/cli"
+    "net/url"
+    "strings"
+)
 
 // ByAlphabet is for sorting
 type ByAlphabet []cli.Command
@@ -9,3 +13,24 @@
 func (a ByAlphabet) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a ByAlphabet) Less(i, j int) bool { return a[i].Name < a[j].Name }
 
+func filterURL(in string) (out string) {
+    uri, err := url.Parse(in)
+    if err != nil {
+        out = ""
+    } else {
+        uri := url.URL{Scheme: uri.Scheme, Host: uri.Host}
+        out = uri.String()
+    }
+    return
+}
+
+func basename(in string) (out string) {
+    uri, err := url.Parse(in)
+    if err != nil {
+        out = ""
+    } else {
+        // Remove the :NN part of present
+        out = strings.Split(uri.Host, ":")[0]
+    }
+    return
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/gondole-cli/utils_test.go	Wed Apr 12 14:27:09 2017 +0200
@@ -0,0 +1,30 @@
+package main
+
+import (
+    "testing"
+    "github.com/stretchr/testify/assert"
+)
+
+func TestFilterURL(t *testing.T) {
+    in := "https://example.com"
+    out := filterURL(in)
+    assert.EqualValues(t, in, out, "equal")
+}
+
+func TestBasename(t *testing.T) {
+    in := "https://example.com"
+    out := basename(in)
+    assert.EqualValues(t, "example.com", out, "equal")
+
+    in = "https://example.com:80"
+    out = basename(in)
+    assert.EqualValues(t, "example.com", out, "equal")
+
+    in = "https://example.com:16443"
+    out = basename(in)
+    assert.EqualValues(t, "example.com", out, "equal")
+
+    in = "//example.com:443"
+    out = basename(in)
+    assert.EqualValues(t, "example.com", out, "equal")
+}
--- a/config.go	Tue Apr 11 17:15:12 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +0,0 @@
-// config.go
-//
-// This file implements the configuration part for when you need the API
-// key to modify things in the Mastodon configuration and manage measurements.
-
-package gondole
-
-import (
-	"fmt"
-	"io/ioutil"
-	"log"
-	"os"
-	"path/filepath"
-
-	"github.com/naoina/toml"
-)
-
-/*
-Assume the application is registered if $HOME/.config/<gondole>/config.toml already exist
-We will store the per-instance token into $HOME/.config/<gondole>/<site>.token
-*/
-
-const (
-	DefaultName = "config.toml"
-)
-
-var (
-	baseDir = filepath.Join(os.Getenv("HOME"),
-		".config",
-		"gondole",
-	)
-)
-
-// Config holds our parameters
-type Server struct {
-	ID          string `json:"id"`
-	Name        string `json:"name"`
-	BearerToken string `json:"bearer_token"`
-	BaseURL     string `json:"base_url"`	// Allow for overriding APIEndpoint on registration
-}
-
-type Config struct {
-	Default string
-}
-
-func loadGlobal(file string) (c *Config, err error) {
-	log.Printf("file=%s", file)
-	// Check if there is any config file
-	_, err = os.Stat(file)
-	if err != nil {
-		return
-	}
-
-	log.Printf("file=%s, found it", file)
-	// Read it
-	buf, err := ioutil.ReadFile(file)
-	if err != nil {
-		return c, fmt.Errorf("Can not read %s", file)
-	}
-
-	cnf := Config{}
-	err = toml.Unmarshal(buf, &cnf)
-	if err != nil {
-		return c, fmt.Errorf("Error parsing toml %s: %v", file, err)
-	}
-	c = &cnf
-	return
-}
-
-func loadInstance(name string) (s *Server, err error) {
-	// Load instance-specific file
-	file := filepath.Join(baseDir, name+".token")
-
-	log.Printf("instance is %s", file)
-
-	// Check if there is any config file
-	if _, err = os.Stat(file); err == nil {
-		// Read it
-		buf, err := ioutil.ReadFile(file)
-		if err != nil {
-			return s, fmt.Errorf("Can not read %s", file)
-		}
-
-		sc := Server{}
-		err = toml.Unmarshal(buf, &sc)
-		if err != nil {
-			return s, fmt.Errorf("Error parsing toml %s: %v", file, err)
-		}
-		s = &sc
-	}
-	return
-}
-
-func GetInstanceList() (list []string) {
-	list, err := filepath.Glob(filepath.Join(baseDir, "*.token"))
-	log.Printf("basedir=%s", filepath.Join(baseDir, "*.token"))
-	if err != nil {
-		log.Printf("warning, no *.token files in %s", baseDir)
-		list = nil
-	}
-	log.Printf("list=%v", list)
-	return
-}
-
-// LoadConfig reads a file as a TOML document and return the structure
-func LoadConfig(name string) (s *Server, err error) {
-	// Load global file
-	gFile := filepath.Join(baseDir, DefaultName)
-
-	log.Printf("global is %s", gFile)
-	c, err := loadGlobal(gFile)
-	if err != nil {
-		return
-	}
-	if name == "" {
-		s, err = loadInstance(c.Default)
-	} else {
-		s, err = loadInstance(name)
-	}
-
-	return s, err
-}
-
-func (c *Config) Write() (err error) {
-	if err = os.MkdirAll(baseDir, 0700); err != nil {
-		log.Fatalf("error creating configuration directory: %s", baseDir)
-	}
-
-	var sc []byte
-
-	if sc, err = toml.Marshal(*c); err != nil {
-		log.Fatalf("error saving configuration")
-	}
-	err = ioutil.WriteFile(filepath.Join(baseDir, DefaultName), sc, 0600)
-	return
-}
-
-func (s *Server) WriteToken(instance string) (err error) {
-	if err = os.MkdirAll(baseDir, 0700); err != nil {
-		log.Fatalf("error creating configuration directory: %s", baseDir)
-	}
-
-	var sc []byte
-
-	if sc, err = toml.Marshal(s); err != nil {
-		log.Fatalf("error saving configuration")
-	}
-
-	full := instance + ".token"
-	err = ioutil.WriteFile(filepath.Join(baseDir, full), sc, 0600)
-	return
-}
--- a/config_test.go	Tue Apr 11 17:15:12 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-package gondole
-
-import (
-	"github.com/stretchr/testify/assert"
-	"os"
-	"path/filepath"
-	"testing"
-)
-
-func TestLoadGlobal(t *testing.T) {
-	baseDir = "."
-
-	_, err := loadGlobal(filepath.Join("test", "non.toml"))
-	assert.Error(t, err, "does not exist")
-
-	_, err = loadGlobal(filepath.Join("test", "garbage.token"))
-	assert.Error(t, err, "just garbage")
-
-	// git does now allow you to checkin 000 files :(
-	err = os.Chmod(filepath.Join("test", "perms.toml"), 0000)
-	assert.NoError(t, err, "should be fine")
-	_, err = loadGlobal(filepath.Join("test", "perms.toml"))
-	assert.Error(t, err, "unreadable")
-	err = os.Chmod(filepath.Join("test", "perms.toml"), 0600)
-	assert.NoError(t, err, "should be fine")
-
-	c, err := loadGlobal(filepath.Join("test", "config.toml"))
-	assert.NoError(t, err, "should read it fine")
-	assert.EqualValues(t, "foo", c.Default, "equal")
-}
-
-func TestLoadInstance(t *testing.T) {
-	baseDir = "."
-
-	_, err := loadInstance("nonexistent")
-	assert.Error(t, err, "does not exist")
-
-	file := filepath.Join("test", "garbage")
-	_, err = loadInstance(file)
-	assert.Error(t, err, "just garbage")
-
-	file = filepath.Join("test", "foo.token")
-	err = os.Chmod(file, 0000)
-	assert.NoError(t, err, "should be fine")
-
-	file = filepath.Join("test", "foo")
-	_, err = loadInstance(file)
-	assert.Error(t, err, "unreadable")
-
-	file = filepath.Join("test", "foo.token")
-	err = os.Chmod(file, 0644)
-	assert.NoError(t, err, "should be fine")
-
-	real := &Server{
-		ID:          "666",
-		Name:        "foo",
-		BearerToken: "d3b07384d113edec49eaa6238ad5ff00",
-		BaseURL:     "https://mastodon.social",
-	}
-	file = filepath.Join("test", "foo")
-	s, err := loadInstance(file)
-	assert.NoError(t, err, "all fine")
-	assert.Equal(t, real, s, "equal")
-}
-
-func TestGetInstanceList(t *testing.T) {
-	baseDir = "test"
-
-	real := []string{
-		filepath.Join("test", "foo.token"),
-		filepath.Join("test", "garbage.token"),
-	}
-	list := GetInstanceList()
-	assert.Equal(t, real, list, "equal")
-
-	baseDir = "/tmp"
-	real = nil
-	list = GetInstanceList()
-	assert.Equal(t, real, list, "equal")
-
-	baseDir = "/nonexistent"
-	real = nil
-	list = GetInstanceList()
-	assert.Equal(t, real, list, "equal")
-}
-
-func TestLoadConfig(t *testing.T) {
-	baseDir = "test"
-
-	_, err := LoadConfig("foo")
-	assert.NoError(t, err, "should be fine")
-
-	_, err = LoadConfig("")
-	assert.NoError(t, err, "should be fine")
-
-	err = os.Chmod(filepath.Join("test", "config.toml"), 0000)
-	assert.NoError(t, err, "should be fine")
-
-	_, err = LoadConfig("")
-	assert.Error(t, err, "should be unreadable")
-
-	err = os.Chmod(filepath.Join("test", "config.toml"), 0600)
-	assert.NoError(t, err, "should be fine")
-
-}
\ No newline at end of file
--- a/gondole.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/gondole.go	Wed Apr 12 14:27:09 2017 +0200
@@ -9,7 +9,11 @@
 const (
 	APIVersion = "0.0"
 
-	defAPIEndpoint = "https://mastodon.social/api/v1"
+	// That is not overridable
+	apiURL = "/api/v1"
+
+	// Fallback instance
+	FallBackURL = "https://mastodon.social"
 
 	NoRedirect = "urn:ietf:wg:oauth:2.0:oob"
 )
@@ -20,27 +24,28 @@
 )
 
 // prepareRequest insert all pre-defined stuff
-func (g *Gondole) prepareRequest(what string) (req rest.Request) {
-	var endPoint string
+func (g *Client) prepareRequest(what string) (req rest.Request) {
+	var APIBase string
 
 	// Allow for overriding for registration
-	if APIEndpoint == "" {
-		endPoint = defAPIEndpoint
+	if g.APIBase == "" {
+		APIBase = FallBackURL
 	} else {
-		endPoint = APIEndpoint
+		APIBase = g.APIBase
 	}
 
-	endPoint = endPoint + fmt.Sprintf("/%s", what)
+	APIEndpoint = fmt.Sprintf("%s%s/%", APIBase, apiURL, what)
+
 	// Add at least one option, the APIkey if present
 	hdrs := make(map[string]string)
 	opts := make(map[string]string)
 
 	// Insert our sig
-	hdrs["User-Agent"] = fmt.Sprintf("Gondole/%s", APIVersion)
+	hdrs["User-Agent"] = fmt.Sprintf("Client/%s", APIVersion)
 	hdrs["Authorization"] = fmt.Sprintf("Bearer %s", g.Secret)
 
 	req = rest.Request{
-		BaseURL:     endPoint,
+		BaseURL:     APIEndpoint,
 		Headers:     hdrs,
 		QueryParams: opts,
 	}
--- a/gondole_test.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/gondole_test.go	Wed Apr 12 14:27:09 2017 +0200
@@ -6,8 +6,12 @@
 )
 
 func TestPrepareRequest(t *testing.T) {
-    g, err := NewApp("foo", nil, NoRedirect, "")
-    assert.NoError(t, err, "no error")
+    g := &Client{
+        Name: "foo",
+        ID: "666",
+        Secret: "biiiip",
+        APIBase: "http://example.com",
+    }
 
     req := g.prepareRequest("bar")
     assert.NotNil(t, req.Headers, "not nil")
--- a/test/config.toml	Tue Apr 11 17:15:12 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-default="foo"
--- a/test/foo.token	Tue Apr 11 17:15:12 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-id = "666"
-name = "foo"
-bearer_token = "d3b07384d113edec49eaa6238ad5ff00"
-base_URL = "https://mastodon.social"
\ No newline at end of file
--- a/test/garbage.token	Tue Apr 11 17:15:12 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
->'''(fsljfùùksdjlf1656>
--- a/types.go	Tue Apr 11 17:15:12 2017 +0200
+++ b/types.go	Wed Apr 12 14:27:09 2017 +0200
@@ -4,133 +4,146 @@
 	"time"
 )
 
-type Gondole struct {
-	Name   string
-	ID     string
-	Secret string
-}
-
 type Client struct {
-	BaseURL     string
-	BearerToken string
+	Name    string
+	ID      string
+	Secret  string
+	APIBase string
 }
 
 /*
-
 Entities:
 
 Everything manipulated/returned by the API
 */
 
+// Account represents a Mastodon account entity
 type Account struct {
-	ID          string
-	Username    string
-	Acct        string
-	DisplayName string
-	Note        string
-	URL         string
-	Avatar      string
-	Header      string
-	Locked      bool
-	Followers   int
-	Followings  int
-	Statuses    int
+	ID             int    `json:"id"`
+	Username       string `json:"username"`
+	Acct           string `json:"acct"`
+	DisplayName    string `json:"display_name"`
+	Note           string `json:"note"`
+	URL            string `json:"url"`
+	Avatar         string `json:"avatar"`
+	Header         string `json:"header"`
+	Locked         bool   `json:"locked"`
+	CreatedAt      string `json:"created_at"`
+	FollowersCount int    `json:"followers_count"`
+	FollowingCount int    `json:"following_count"`
+	StatusesCount  int    `json:"statuses_count"`
 }
 
+// Application represents a Mastodon application entity
 type Application struct {
-	Name    string
-	Website string
+	Name    string `json:"name"`
+	Website string `json:"website"`
 }
 
-type Attachement struct {
-	ID         string
-	Type       string
-	URL        string
-	RemoteURL  string
-	PreviewURL string
-	TextURL    string
+// Attachment represents a Mastodon attachement entity
+type Attachment struct {
+	ID         int    `json:"iD"`
+	Type       string `json:"type"`
+	URL        string `json:"url"`
+	RemoteURL  string `json:"remote_url"`
+	PreviewURL string `json:"preview_url"`
+	TextURL    string `json:"text_url"`
 }
 
+/*
+// Card represents a Mastodon card entity
 type Card struct {
-	URL         string
-	Title       string
-	Description string
-	Image       string
+	URL         string       `json:"url"`
+	Title       string       `json:"title"`
+	Description string       `json:"description"`
+	Image       *interface{} `json:"image"`
+}
+*/
+
+// Context represents a Mastodon context entity
+type Context struct {
+	Ancestors   []Status `json:"ancestors"`
+	Descendents []Status `json:"descendents"`
 }
 
-type Context struct {
-	Ancestors   []Status
-	Descendents []Status
+// Error represents a Mastodon error entity
+type Error struct {
+	Text string `json:"error"`
 }
 
-type Error struct {
-	Text string
-}
-
+// Instance represents a Mastodon instance entity
 type Instance struct {
-	URI         string
-	Title       string
-	Description string
-	Email       string
+	URI         string `json:"uri"`
+	Title       string `json:"title"`
+	Description string `json:"description"`
+	Email       string `json:"email"`
 }
 
+// Mention represents a Mastodon mention entity
 type Mention struct {
-	ID       string
-	URL      string
-	Username string
-	Acct     string
+	ID       int    `json:"id"`
+	URL      string `json:"url"`
+	Username string `json:"username"`
+	Acct     string `json:"acct"`
 }
 
+// Notification represents a Mastodon notification entity
 type Notification struct {
-	ID        string
-	Type      string
-	CreatedAt time.Time
-	Account   *Account
-	Status    *Status
+	ID        int      `json:"id"`
+	Type      string   `json:"type"`
+	CreatedAt string   `json:"created_at"`
+	Account   *Account `json:"account"`
+	Status    *Status  `json:"status"`
 }
 
+// Relationship represents a Mastodon relationship entity
 type Relationship struct {
-	Following  bool
-	FollowedBy bool
-	Blocking   bool
-	Muting     bool
-	Requested  bool
+	Following  bool `json:"following"`
+	FollowedBy bool `json:"followed_by"`
+	Blocking   bool `json:"blocking"`
+	Muting     bool `json:"muting"`
+	Requested  bool `json:"requested"`
 }
+
+// Report represents a Mastodon report entity
 type Report struct {
-	ID          int64
-	ActionTaken string
+	ID          int    `json:"iD"`
+	ActionTaken string `json:"action_taken"`
 }
 
-type Result struct {
-	Accounts []Account
-	Statutes []Status
-	Hashtags []Tag
+// Results represents a Mastodon results entity
+type Results struct {
+	Accounts []Account `json:"accounts"`
+	Statuses []Status  `json:"statuses"`
+	Hashtags []string  `json:"hashtags"`
 }
 
+// Status represents a Mastodon status entity
 type Status struct {
-	ID                 string
-	URI                string
-	URL                string
-	Account            *Account
-	InReplyToId        string
-	InReplyToAccountID string
-	Reblog             *Status
-	Content            string
-	CreatedAT          time.Time
-	Reblogs            int
-	Favourites         int
-	Reblogged          bool
-	Favourited         bool
-	Sensitive          bool
-	SpoilerText        string
-	Visibility         string
-	MediaAttachments   []Attachement
-	Mentions           []Mention
-	Tags               []Tag
-	App                Application
+	ID                 int          `json:"id"`
+	URI                string       `json:"uri"`
+	URL                string       `json:"url"`
+	Account            *Account     `json:"account"`
+	InReplyToID        int          `json:"in_reply_to_id"`
+	InReplyToAccountID int          `json:"in_reply_to_account_id"`
+	Reblog             *Status      `json:"reblog"`
+	Content            string       `json:"content"`
+	CreatedAt          time.Time    `json:"created_at"`
+	ReblogsCount       int          `json:"reblogs_count"`
+	FavouritesCount    int          `json:"favourites_count"`
+	Reblogged          bool         `json:"reblogged"`
+	Favourited         bool         `json:"favourited"`
+	Sensitive          bool         `json:"sensitive"`
+	SpoilerText        string       `json:"spoiler_text"`
+	Visibility         string       `json:"visibility"`
+	MediaAttachments   []Attachment `json:"media_attachments"`
+	Mentions           []Mention    `json:"mentions"`
+	Tags               []Tag        `json:"tags"`
+	Application        Application  `json:"application"`
 }
 
+// Tag represents a Mastodon tag entity
 type Tag struct {
-	Name string
-	URL  string
+	Name string `json:"name"`
+	URL  string `json:"url"`
 }