Skip to content

Commit

Permalink
Merge 52e7bb8 into e49b618
Browse files Browse the repository at this point in the history
  • Loading branch information
saheljalal committed Jan 28, 2022
2 parents e49b618 + 52e7bb8 commit fdf5be9
Show file tree
Hide file tree
Showing 14 changed files with 708 additions and 289 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -9,6 +9,7 @@ sudo: false
install:
- go get golang.org/x/tools/cmd/cover
- go install github.com/mattn/goveralls@latest
- go get

script:
- echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}"
Expand Down
70 changes: 60 additions & 10 deletions README.md
Expand Up @@ -69,7 +69,15 @@ nostromo init

To customize the directory (and change it from `~/.nostromo`), set the `NOSTROMO_HOME` environment variable to a location of your choosing.

> With every update, it's a good idea to run `nostromo init` to ensure any manifest changes are migrated and commands continue to work.
> With every update, it's a good idea to run `nostromo init` to ensure any manifest changes are migrated and commands continue to work. `nostromo` will attempt to perform any migrations as well at this time to files and folders so 🤞
The quickest way to populate your commands database is using the `sync` feature:

```sh
nostromo sync <source>
```

where `source` can be any local or remote file sources. See the [Distributed Manifests](#distributed-manifests) section for more details.

To destroy the manifest and start over you can always run:

Expand All @@ -85,12 +93,12 @@ nostromo manifest set backupCount 10

## Key Features

- Simplified alias management
- Scoped commands and substitutions
- Build complex command trees
- Shell completion support
- Preserves flags and arguments
- Execute code snippets
- [Simplified alias management](#managing-aliases)
- [Scoped commands and substitutions](#scoped-commands-and-substitutions)
- [Build complex command trees](#complex-command-tree)
- [Shell completion support](#shell-completion)
- [Execute code snippets](#execute-code-snippets)
- [Distributed manifests](#distributed-manifests)

## Usage

Expand Down Expand Up @@ -185,7 +193,7 @@ foo() { eval $(nostromo eval foo "$*") }
> Notice how the keypath has no affect in building a command tree when using the **alias only** feature. Standard shell aliases can only be root level commands.
### Scoped Commands & Substitutions
### Scoped Commands And Substitutions
Scope affects a tree of commands such that a parent scope is prepended first and then each command in the keypath to the root. If a command is run as follows:
Expand Down Expand Up @@ -302,12 +310,54 @@ nostromo add cmd foo --code 'console.log("hello js")' --language js
For more complex snippets you can edit `~/.nostromo/ships/manifest.yaml` directly but multiline YAML must be escaped correctly to work.
### Distributed Manifests
`nostromo` now supports keeping multiple manifest sources 💪 allowing you to organize and distribute your commands as you please. This feature enables synchronization functionality to get remote manifests from multiple data sources including:
- Local Files
- Git
- Mercurial
- HTTP
- Amazon S3
- Google GCS
Configs can be found in the `~/.nostromo/ships` folder. The **core manifest** is named `manifest.yaml`.
You can add as many additional manifests in the same folder and `nostromo` will parse and aggregate all the commands, useful for organizations wanting to build their own command suite.
To add or sync manifests, use the following:
```sh
nostromo sync <source>...
```
And that's it! Your commands will now incorporate the new manifest.
To update all synchronized manifests to the latest versions, just run:
```sh
nostromo sync
```
`nostromo` syncs manifests using version information in the manifest. It will only update if the version identifier is different. To force update a manifest, run:
```sh
# Force sync one or more manifests
nostromo sync -f <source>...
# Force sync all manifests
nostromo sync -f
```
> Details on supported file formats and requirements can be found in the [go-getter](https://github.com/hashicorp/go-getter) documentation since we are using that for downloading files
## Credits
- This tool was bootstrapped using [cobra](https://github.com/spf13/cobra).
- Colored logging provided by [aurora](https://github.com/logrusorgru/aurora).
- `nostromo` fan art supplied by [Ian Stewart](https://www.artstation.com/artwork/EBBVN).
- Colored logging provided by [aurora](https://github.com/logrusorgru/aurora/v3).
- Fan art supplied by [Ian Stewart](https://www.artstation.com/artwork/EBBVN).
- Gopher artwork by [@egonelbre](https://github.com/egonelbre/gophers) and original by [Renee French](http://reneefrench.blogspot.com/).
- File downloader using [go-getter](https://github.com/hashicorp/go-getter)
## Contibuting
Expand Down
35 changes: 35 additions & 0 deletions cmd/sync.go
@@ -0,0 +1,35 @@
package cmd

import (
"os"

"github.com/pokanop/nostromo/task"
"github.com/spf13/cobra"
)

var force bool

// syncCmd represents the sync command
var syncCmd = &cobra.Command{
Use: "sync [source] [options]",
Short: "Sync nostromo manifests from source locations",
Long: `Sync nostromo manifests from source locations and
makes commands available for execution.
Sync can be used to copy a single manifest to nostromo's config
folder. Or it can be used to update previously synchronized
manifests from respective data sources.
Providing a [source] file will sync only that file. Omitting it
directs nostromo to sync all manifests.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
os.Exit(task.Sync(force, args))
},
}

func init() {
rootCmd.AddCommand(syncCmd)

syncCmd.Flags().BoolVarP(&force, "force", "f", false, "Force sync manifest(s)")
}
34 changes: 17 additions & 17 deletions cmd/update.go
Expand Up @@ -12,23 +12,23 @@ var updateCmd = &cobra.Command{
Use: "update [key.path] [command] [options]",
Short: "Update a command in nostromo manifest",
Long: `Update a command in nostromo manifest for a given key path.
A key path is a '.' delimited string, e.g., "key.path" which represents
the alias which can be run as "key path" for the actual command provided.
This will update appropriate command scopes for all levels in the provided
key path. A command scope can contain a tree of sub commands and
substitutions.
A command's mode indicates how it will be executed. By default, nostromo
concatenates parent and child commands along the tree. There are 3 modes
available to commands:
concatenate Concatenate this command with subcommands exactly as defined
independent Execute this command with subcommands using ';' to separate
exclusive Execute this and only this command ignoring parent commands
You can set using -m or --mode when adding a command or globally using:
nostromo manifest set mode <mode>`,
A key path is a '.' delimited string, e.g., "key.path" which represents
the alias which can be run as "key path" for the actual command provided.
This will update appropriate command scopes for all levels in the provided
key path. A command scope can contain a tree of sub commands and
substitutions.
A command's mode indicates how it will be executed. By default, nostromo
concatenates parent and child commands along the tree. There are 3 modes
available to commands:
concatenate Concatenate this command with subcommands exactly as defined
independent Execute this command with subcommands using ';' to separate
exclusive Execute this and only this command ignoring parent commands
You can set using -m or --mode when adding a command or globally using:
nostromo manifest set mode <mode>`,
Args: addCmdArgs,
Run: func(cmd *cobra.Command, args []string) {
var name string
Expand Down
103 changes: 103 additions & 0 deletions config/backup.go
@@ -0,0 +1,103 @@
package config

import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"time"

"github.com/pokanop/nostromo/log"
"github.com/pokanop/nostromo/model"
"github.com/pokanop/nostromo/pathutil"
)

// backupManifest at config path based on timestamp
func backupManifest(m *model.Manifest) error {
// Before saving backup, prune old files
pruneBackups(m)

// Prevent backups if max count is 0
if m.Config.BackupCount == 0 {
return nil
}

// Create backups under base dir in a folder
backupDir, err := ensureBackupDir()
if err != nil {
return err
}

// Copy existing manifest to backup path
ts := fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UnixNano()/int64(time.Millisecond))
destinationFile := filepath.Join(backupDir, m.Name+"_"+ts+".yaml")
sourceFile := pathutil.Abs(m.Path)

// Check if manifest exists
if _, err := os.Stat(sourceFile); os.IsNotExist(err) {
return nil
}

input, err := ioutil.ReadFile(sourceFile)
if err != nil {
return err
}

err = ioutil.WriteFile(destinationFile, input, 0644)
if err != nil {
return err
}

return nil
}

func pruneBackups(m *model.Manifest) {
backupDir, err := ensureBackupDir()
if err != nil {
return
}

// Read all files, sort by timestamp, and drop items > max count
files, err := ioutil.ReadDir(backupDir)
if err != nil {
log.Warningf("unable to read backup dir: %s\n", err)
return
}

// Filter file list for matching names
matches := []fs.FileInfo{}
for _, file := range files {
if isMatch, _ := regexp.MatchString(fmt.Sprintf("%s_\\d+\\.yaml", m.Name), file.Name()); isMatch {
matches = append(matches, file)
}
}

sort.SliceStable(matches, func(i, j int) bool {
return matches[i].ModTime().After(matches[j].ModTime())
})

// Add one more to backup count since a new backup will be created
maxCount := m.Config.BackupCount - 1
if maxCount < 0 {
maxCount = 0
}
if len(matches) > maxCount {
for _, file := range matches[maxCount:] {
filename := filepath.Join(backupDir, file.Name())
if err := os.Remove(filename); err != nil {
log.Warningf("failed to prune backup file %s: %s\n", filename, err)
}
}
}
}

func ensureBackupDir() (string, error) {
backupDir := backupsPath()
if err := pathutil.EnsurePath(backupDir); err != nil {
return "", err
}
return pathutil.Abs(backupDir), nil
}

0 comments on commit fdf5be9

Please sign in to comment.