Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ipfs config command with git-style option value getters and setters #16

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions cmd/ipfs/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"github.com/jbenet/commander"
config "github.com/jbenet/go-ipfs/config"
u "github.com/jbenet/go-ipfs/util"
"os"
"os/exec"
)

var cmdIpfsConfig = &commander.Command{
UsageLine: "config",
Short: "See and Edit ipfs options",
Long: `ipfs config - See or Edit ipfs configuration.

See specific config's values with:
ipfs config datastore.path
Assign a new value with:
ipfs config datastore.path ~/.go-ipfs/datastore

Open the config file in your editor(from $EDITOR):
ipfs config edit
`,
Run: configCmd,
Subcommands: []*commander.Command{
cmdIpfsConfigEdit,
},
}

var cmdIpfsConfigEdit = &commander.Command{
UsageLine: "edit",
Short: "Opens the configuration file in the editor.",
Long: `Looks up environment variable $EDITOR and
attempts to open the config file with it.
`,
Run: configEditCmd,
}

func configCmd(c *commander.Command, inp []string) error {
if len(inp) == 0 {
// "ipfs config" run without parameters
u.POut(c.Long + "\n")
return nil
}

if len(inp) == 1 {
// "ipfs config" run without one parameter, so this is a value getter
value, err := config.GetValueInConfigFile(inp[0])
if err != nil {
u.POut("Failed to get config value: " + err.Error() + "\n")
} else {
u.POut(value + "\n")
}
return nil
}

// "ipfs config" run without two parameter, so this is a value setter
err := config.SetValueInConfigFile(inp[0], inp[1:])
if err != nil {
u.POut("Failed to set config value: " + err.Error() + "\n")
}
return nil
}

func configEditCmd(c *commander.Command, _ []string) error {
if editor := os.Getenv("EDITOR"); editor == "" {
u.POut("ENVIRON variable $EDITOR is not assigned \n")
} else {
exec.Command("sh", "-c", editor+" "+config.DefaultConfigFilePath).Start()
}
return nil
}
1 change: 1 addition & 0 deletions cmd/ipfs/ipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Use "ipfs help <command>" for more information about a command.
cmdIpfsCat,
cmdIpfsLs,
cmdIpfsRefs,
cmdIpfsConfig,
cmdIpfsVersion,
cmdIpfsCommands,
cmdIpfsMount,
Expand Down
6 changes: 3 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package config

import (
"os"
u "github.com/jbenet/go-ipfs/util"
"os"
)

// Identity tracks the configuration of the local node's identity.
Expand All @@ -22,7 +22,7 @@ type Config struct {
Datastore *Datastore
}

var defaultConfigFilePath = "~/.go-ipfs/config"
var DefaultConfigFilePath = "~/.go-ipfs/config"
var defaultConfigFile = `{
"identity": {},
"datastore": {
Expand All @@ -35,7 +35,7 @@ var defaultConfigFile = `{
// LoadConfig reads given file and returns the read config, or error.
func LoadConfig(filename string) (*Config, error) {
if len(filename) == 0 {
filename = defaultConfigFilePath
filename = DefaultConfigFilePath
}

// tilde expansion on config file
Expand Down
83 changes: 83 additions & 0 deletions config/serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package config

import (
"encoding/json"
"errors"
"fmt"
u "github.com/jbenet/go-ipfs/util"
"io/ioutil"
"os"
"path"
"strings"
)

// WriteFile writes the given buffer `buf` into file named `filename`.
Expand Down Expand Up @@ -36,3 +40,82 @@ func WriteConfigFile(filename string, cfg *Config) error {

return WriteFile(filename, buf)
}

// WriteConfigFile writes the config from `cfg` into `filename`.
func GetValueInConfigFile(key string) (value string, err error) {
// reading config file
attrs := strings.Split(key, ".")

filename, _ := u.TildeExpansion(DefaultConfigFilePath)
buf, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}

// deserializing json
var cfg interface{}
var exists bool

err = json.Unmarshal(buf, &cfg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more idiomatic go way of doing this would be to just open the file os.Open and then use json.NewDecoder to do the decoding.

fi,err := os.Open(filename)
if err != nil {//whatever}
dec := json.NewDecoder(fi)
var cfg map[string]interface{}
err := dec.Decode(&cfg)
if err != nil {//whatever}

(obviously written cleaner than my chicken scratch)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rewrite it using json.Decoder, but I don't really see that much of a difference.
Could you please comment on why this way is preferable / more idiomatic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmoiron does a good job explaining it in one of his articles: http://jmoiron.net/blog/crossing-streams-a-love-letter-to-ioreader/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good post!

@dborzov I can rewrite it-- there's a few more stylistic things i'll fix that don't want to annoy with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link, that article makes good sense to me.

if err != nil {
return "", err
}

for i := range attrs {
cfgMap, isMap := cfg.(map[string]interface{})
if !isMap {
return "", errors.New(fmt.Sprintf("%s has no attributes", strings.Join(attrs[:i], ".")))
}
cfg, exists = cfgMap[attrs[i]]
if !exists {
return "", errors.New(fmt.Sprintf("Configuration option key \"%s\" not recognized", strings.Join(attrs[:i+1], ".")))
}
val, is_string := cfg.(string)
if is_string {
return val, nil
}
}
return "", errors.New(fmt.Sprintf("%s is not a string", key))
}

// WriteConfigFile writes the config from `cfg` into `filename`.
func SetValueInConfigFile(key string, values []string) error {
assignee := strings.Join(values, " ")
attrs := strings.Split(key, ".")

filename, _ := u.TildeExpansion(DefaultConfigFilePath)
buf, err := ioutil.ReadFile(filename)
if err != nil {
return err
}

// deserializing json
var cfg, orig interface{}
var exists, isMap bool
cfgMap := make(map[string]interface{})

err = json.Unmarshal(buf, &orig)
cfg = orig
if err != nil {
return err
}

for i := 0; i < len(attrs); i++ {
cfgMap, isMap = cfg.(map[string]interface{})
// curs = append(curs, cfgMap)
if !isMap {
return errors.New(fmt.Sprintf("%s has no attributes", strings.Join(attrs[:i], ".")))
}
cfg, exists = cfgMap[attrs[i]]
if !exists {
return errors.New(fmt.Sprintf("Configuration option key \"%s\" not recognized", strings.Join(attrs[:i+1], ".")))
}
}
cfgMap[attrs[len(attrs)-1]] = assignee
buf, err = json.MarshalIndent(orig, "", " ")
if err != nil {
return err
}
WriteFile(filename, buf)
return nil
}