Skip to content

Commit

Permalink
Merge pull request #1 from hypnoglow/feature-add-file
Browse files Browse the repository at this point in the history
Feature add file
  • Loading branch information
hypnoglow authored Sep 23, 2016
2 parents 04f2f90 + 9dfae9c commit a8a10e8
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 4 deletions.
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
language: go
go:
- tip
- tip
before_install:
- go get github.com/mattn/goveralls
script:
- $HOME/gopath/bin/goveralls -service=travis-ci
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Dotbro remembers path to this file and use it in further runs.

Dotbro cleans broken symlinks in your `$HOME` (or your another destination path).

#### Add command

Dotbro can automate routine of adding files to your dotfiles repo with one single
command. It does a backup copy, moves the file and creates a symlink to your file.
After that you only need to add this file to your dotbro config (*I'm working on automation of this*) and commit that file to your repo.

# Configuration

Configuration can be either TOML or JSON file.
Expand Down Expand Up @@ -174,6 +180,10 @@ So just run:

dotbro

To move a file to your dotfiles, perform an `add` command:

dotbro add ./path-to-file

# Issues

If you experience any problems, please submit an issue and attach dotbro log file,
Expand Down
12 changes: 9 additions & 3 deletions docopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ package main

import "github.com/docopt/docopt-go"

const version = "0.1.0"
const version = "0.2.0"

func parseArguments() (map[string]interface{}, error) {
usage := `dotbro - simple yet effective dotfiles manager.
Usage:
dotbro [options] [--config=<filepath>]
dotbro add [options] <filename>
dotbro -h | --help
dotbro --version
Options:
Common options:
-c --config=<filepath> Dotbro's configuration file in JSON or TOML format.
-h --help Show this helpful info.
-q --quiet Quiet mode. Do not print any output, except warnings
and errors.
-v --verbose Verbose mode. Detailed output.
Add options:
<filename> File to add.
Other options:
-h --help Show this helpful info.
-V --version Show version.
`

Expand Down
73 changes: 73 additions & 0 deletions fileutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"fmt"
"io"
"os"
"path"
)

// Copy copies a file from src to dst.
func Copy(src, dst string) error {
sfi, err := os.Lstat(src)
if err != nil {
return err
}

if !sfi.Mode().IsRegular() {
return fmt.Errorf("Non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
}

dfi, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return err
}
// file not exists - do not do anything
} else {
// file exists - check it
if !(dfi.Mode().IsRegular()) {
return fmt.Errorf("Non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
}
}

err = copyFileContents(src, dst)
return err
}

// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}

defer in.Close()

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

out, err := os.Create(dst)
if err != nil {
return
}

defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()

if _, err = io.Copy(out, in); err != nil {
return err
}

err = out.Sync()
return err
}
121 changes: 121 additions & 0 deletions fileutils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"io/ioutil"
"os"
"path"
"testing"
)

func TestCopy(t *testing.T) {
testCopyPositive(t)
testCopyNegativeLstat(t)
testCopyNegativeSymlink(t)
}

func testCopyPositive(t *testing.T) {
// set up

src := "/tmp/dotbro/fileutils/original.txt"
content := []byte("Some Content")

if err := os.MkdirAll(path.Dir(src), 0755); err != nil {
t.Fatal(err)
}

if err := ioutil.WriteFile(src, content, 0755); err != nil {
t.Fatal(err)
}

// test

dest := "/tmp/dotbro/fileutils/copy.txt"
if err := Copy(src, dest); err != nil {
t.Error(err)
}

copyContent, err := ioutil.ReadFile(dest)
if err != nil {
t.Error(err)
}

if string(copyContent) != string(content) {
t.Error(err)
}

// tear down

if err := os.Remove(src); err != nil {
t.Error(err)
}

if err := os.Remove(dest); err != nil {
t.Error(err)
}
}

func testCopyNegativeLstat(t *testing.T) {
// set up

src := "/tmp/dotbro/fileutils/original.txt"

if err := os.MkdirAll(path.Dir(src), 0755); err != nil {
t.Fatal(err)
}

// no read permissions
if err := ioutil.WriteFile(src, nil, 0333); err != nil {
t.Fatal(err)
}

// test

dest := "/tmp/dotbro/fileutils/copy.txt"
err := Copy(dest, dest)
if err == nil {
t.Error("No error!")
}

// tear down

if err := os.Remove(src); err != nil {
t.Error(err)
}
}

func testCopyNegativeSymlink(t *testing.T) {
// set up

original := "/tmp/dotbro/fileutils/original.txt"

if err := os.MkdirAll(path.Dir(original), 0755); err != nil {
t.Fatal(err)
}

if err := ioutil.WriteFile(original, nil, 0755); err != nil {
t.Fatal(err)
}

symlink := "/tmp/dotbro/fileutils/symlink"
if err := os.Symlink(original, symlink); err != nil {
t.Fatal(err)
}

// test

dest := "/tmp/dotbro/fileutils/symlink-copy.txt"
err := Copy(symlink, dest)
if err == nil {
t.Error("No error!")
}

// tear down

if err := os.Remove(original); err != nil {
t.Error(err)
}

if err := os.Remove(symlink); err != nil {
t.Error(err)
}
}
22 changes: 22 additions & 0 deletions linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"os"
"path"
"path/filepath"
)

// processDest inspects destination path, and reports whether symlink and backup
Expand Down Expand Up @@ -66,6 +67,27 @@ func backup(dest string, destAbs string, backupDir string) error {
return err
}

func backupCopy(filename, backupDir string) error {
rel := path.Base(filename)
abs, err := filepath.Abs(filename)
if err != nil {
return err
}

backupPath := backupDir + "/" + rel

// Create subdirectories, if need
dir := path.Dir(backupPath)
if err = os.MkdirAll(dir, 0755); err != nil {
return err
}

outVerbose(" → backup %s to %s", abs, backupPath)

err = Copy(filename, backupPath)
return err
}

// setSymlink symlinks scrAbs to destAbs
func setSymlink(srcAbs string, destAbs string) error {
var err error
Expand Down
59 changes: 59 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"fmt"
"os"
"path"
"path/filepath"
)

Expand Down Expand Up @@ -47,6 +49,22 @@ func main() {
outVerbose("Destination dir: %s", config.Directories.Destination)
}

// Select action

switch {
case args["add"] == true:
filename := args["<filename>"].(string)
if err = addAction(filename, config); err != nil {
outError("%s", err)
exit(1)
}

outInfo("`%s` was successfully added to your dotfiles!", filename)
exit(0)
}

// Default action: install

err = cleanDeadSymlinks(config.Directories.Destination)
if err != nil {
outError("Error cleaning dead symlinks: %s", err)
Expand Down Expand Up @@ -77,6 +95,47 @@ func main() {
exit(0)
}

func addAction(filename string, config Configuration) error {
fileInfo, err := os.Lstat(filename)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("%s: no such file or directory", filename)
}
return err
}

if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
return fmt.Errorf("Cannot add file %s - it is a symlink", filename)
}

if fileInfo.Mode().IsDir() {
return fmt.Errorf("Cannot add dir %s - directories are not supported yet.", filename)
}

outVerbose("Adding file `%s` to dotfiles root `%s`", filename, config.Directories.Dotfiles)

// backup file
err = backupCopy(filename, config.Directories.Backup)
if err != nil {
return fmt.Errorf("Cannot backup file %s: %s", filename, err)
}

// move file to dotfiles root
newPath := config.Directories.Dotfiles + "/" + path.Base(filename)
if err = os.Rename(filename, newPath); err != nil {
return err
}

// Add a symlink to the moved file
if err = setSymlink(newPath, filename); err != nil {
return err
}

// TODO: write to config file

return nil
}

func getConfigPath(args map[string]interface{}) string {
var configPath string
if args["--config"] == nil {
Expand Down

0 comments on commit a8a10e8

Please sign in to comment.