diff --git a/cmd/nix-amer/args.go b/cmd/nix-amer/args.go new file mode 100644 index 0000000..29b9533 --- /dev/null +++ b/cmd/nix-amer/args.go @@ -0,0 +1,55 @@ +package main + +import ( + "flag" + "fmt" + "git.xdrm.io/xdrm-brackets/nix-amer/internal/instruction" +) + +func GetArgs() (*instruction.ExecutionContext, string, error) { + setupFlags(flag.CommandLine) + + longPack := flag.String("package", "", "") + longServ := flag.String("service", "", "") + pack := flag.String("p", "", "") + serv := flag.String("s", "", "") + flag.Parse() + + // 1. fail on missing build file + if len(flag.Args()) < 1 { + return nil, "", fmt.Errorf("missing buildfile") + } + buildfile := flag.Arg(0) + + // 2. override short version with long + if longPack != nil && len(*longPack) > 0 { + pack = longPack + } + if longServ != nil && len(*longServ) > 0 { + serv = longServ + } + + // 3. fail on missing mandatory fields + if pack == nil || len(*pack) < 1 { + return nil, "", fmt.Errorf("missing -package") + } + // if serv == nil || len(*serv) < 1 { // default service + // return nil, "", fmt.Errorf("missing -service") + // } + + // 3. Load context + ctx, err := instruction.CreateContext(*pack, *serv) + if err != nil { + return nil, "", err + } + /*DEBUG*/ //fmt.Printf("package: '%s' | '%s'\n", fpackage, lpackage) + /*DEBUG*/ //fmt.Printf("service: '%s' | '%s'\n", fservice, lservice) + + return ctx, buildfile, nil +} + +func setupFlags(f *flag.FlagSet) { + f.Usage = func() { + help() + } +} diff --git a/cmd/nix-amer/help.go b/cmd/nix-amer/help.go index 73a0165..abb11f1 100644 --- a/cmd/nix-amer/help.go +++ b/cmd/nix-amer/help.go @@ -11,7 +11,7 @@ func help() { fmt.Printf("\tnix-amer - the automatic setup tool you need\n") fmt.Printf("\n%s\n", clifmt.Color(1, "SYNOPSIS", true)) - fmt.Printf("\tnix-amer [-d [-p ] [-s ] \n") + fmt.Printf("\tnix-amer [-p ] [-s ] \n") fmt.Printf("\tnix-amer -help\n") fmt.Printf("\tnix-amer -h\n") fmt.Printf("\n") @@ -28,11 +28,6 @@ func help() { fmt.Printf("\n%s\n", clifmt.Color(1, "OPTIONS", true)) - fmt.Printf("\t-d, -distro\n\t Define which linux distribution the target will run on.\n") - fmt.Printf("\t Available values are 'debian', 'ubuntu', 'alpine', 'solus',\n") - fmt.Printf("\t 'arch', 'centos' and 'fedora'.\n") - fmt.Printf("\n") - fmt.Printf("\t-p, -package\n\t Define which package manager to use ; normally it is\n") fmt.Printf("\t automatically guessed if you define the -distro. Available\n") fmt.Printf("\t values are 'apt-get', 'eopkg', 'apk', 'yum', 'dnf' and 'pacman'.\n") diff --git a/cmd/nix-amer/main.go b/cmd/nix-amer/main.go index 189984c..c99114d 100644 --- a/cmd/nix-amer/main.go +++ b/cmd/nix-amer/main.go @@ -1,5 +1,37 @@ package main +import ( + "fmt" + "git.xdrm.io/xdrm-brackets/nix-amer/internal/buildfile" + "git.xdrm.io/xdrm-brackets/nix-amer/internal/clifmt" + "os" +) + func main() { - help() + + // Manage arguments + ctx, bf, err := GetArgs() + if err != nil { + fmt.Printf("%s", err) + return + } + + // 1. get buildfile reader + bfreader, err := os.Open(bf) + if err != nil { + fmt.Printf("cannot open buildfile | %s\n", err) + return + } + defer bfreader.Close() + + // 2. parse buildfile + _, err = buildfile.NewReader(ctx, bfreader) + if err != nil { + fmt.Printf("%s%s\n", bf, err) + return + } + + clifmt.Align("build file") + fmt.Printf("%s\n", clifmt.Color(32, "parsed")) + } diff --git a/internal/buildfile/errors.go b/internal/buildfile/errors.go index d00ffdb..57adbd5 100644 --- a/internal/buildfile/errors.go +++ b/internal/buildfile/errors.go @@ -2,6 +2,7 @@ package buildfile import ( "fmt" + "git.xdrm.io/xdrm-brackets/nix-amer/internal/clifmt" ) type LineError struct { @@ -10,5 +11,5 @@ type LineError struct { } func (le LineError) Error() string { - return fmt.Sprintf("line %d | %s", le.Line, le.Err.Error()) + return fmt.Sprintf(":%d %s", le.Line, clifmt.Color(31, le.Err.Error())) } diff --git a/internal/buildfile/reader.go b/internal/buildfile/reader.go index 2066758..b994790 100644 --- a/internal/buildfile/reader.go +++ b/internal/buildfile/reader.go @@ -2,23 +2,33 @@ package buildfile import ( "bufio" + "errors" "git.xdrm.io/xdrm-brackets/nix-amer/internal/instruction" "io" + "strings" ) +var ErrNullContext = errors.New("null context") + // Reader is the buildfile reader type Reader struct { - // linux distribution to run on - distribution string - Content []instruction.T + // Context is the linux distribution-specified execution context (package manager, service manager, etc) + Context *instruction.ExecutionContext + // Content is the instruction list + Content []instruction.T } // NewReader creates a new reader for the specified build file and linux distribution -func NewReader(distro string, buildfile io.Reader) (*Reader, error) { +func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) { + + // fail on null context + if ctx == nil { + return nil, ErrNullContext + } r := &Reader{ - distribution: distro, - Content: make([]instruction.T, 0), + Context: ctx, + Content: make([]instruction.T, 0), } // add each line as instruction @@ -33,6 +43,12 @@ func NewReader(distro string, buildfile io.Reader) (*Reader, error) { } else if err != nil { return nil, LineError{l, err} } + line = strings.Trim(line, " \t\r\n") + + // ignore newline & comments + if len(line) < 1 || line[0] == '[' { + continue + } // turn into instruction inst, err := instruction.Parse(line) diff --git a/internal/buildfile/reader_test.go b/internal/buildfile/reader_test.go index 3055977..03a8120 100644 --- a/internal/buildfile/reader_test.go +++ b/internal/buildfile/reader_test.go @@ -34,11 +34,13 @@ func TestInstructionSyntax(t *testing.T) { {"ins args\ncm args\n", 2, instruction.ErrInvalidSyntax}, } + ctx, _ := instruction.CreateContext("apt-get", "") + for i, test := range tests { // create reader buffer := bytes.NewBufferString(test.File) - _, err := NewReader("ubuntu", buffer) + _, err := NewReader(ctx, buffer) // no error expected if test.Err == nil { diff --git a/internal/instruction/cnf.go b/internal/instruction/cnf.go index 51facc6..fa46d07 100644 --- a/internal/instruction/cnf.go +++ b/internal/instruction/cnf.go @@ -2,6 +2,7 @@ package instruction import ( "fmt" + "git.xdrm.io/xdrm-brackets/nix-amer/internal/cnf" "strings" ) @@ -12,20 +13,24 @@ type config struct { Path []string // Value if the value to add or update Value string + // Format is the configuration format in use + Format *cnf.ConfigurationFormat } func (d *config) Build(_args string) error { // 1. extract action (sub command) - split := strings.SplitN(_args, " ", 2) + split := strings.Fields(_args) // 2. check path if len(split) < 2 { return fmt.Errorf("missing configuration path") } + path := split[0] + value := strings.Join(split[1:], "") // 3. check path separator - splitPath := strings.Split(split[0], "@") + splitPath := strings.Split(path, "@") if len(splitPath) > 2 { return fmt.Errorf("invalid path (additional '@'?)") } @@ -36,7 +41,7 @@ func (d *config) Build(_args string) error { } // add value - d.Value = split[1] + d.Value = value return nil } diff --git a/internal/instruction/common.go b/internal/instruction/common.go index 66b383b..619739f 100644 --- a/internal/instruction/common.go +++ b/internal/instruction/common.go @@ -1,7 +1,7 @@ package instruction import ( - "git.xdrm.io/xdrm-brackets/nix-amer/internal/cnf" + "fmt" "git.xdrm.io/xdrm-brackets/nix-amer/internal/pkg" "git.xdrm.io/xdrm-brackets/nix-amer/internal/ser" ) @@ -16,6 +16,43 @@ type T interface { type ExecutionContext struct { PackageManager pkg.PackageManager - Configuration cnf.ConfigurationFormat ServiceManager ser.ServiceManager } + +// CreateContext creates an execution contet with the given package-manager and service-manager +// default values are taken from each go package (pkg, ser) +func CreateContext(_pkg, _ser string) (*ExecutionContext, error) { + + // 1. fail if no value and no defaults + if len(_pkg)+len(pkg.DefaultManager) < 1 { + return nil, fmt.Errorf("missing package manager") + } + if len(_ser)+len(ser.DefaultManager) < 1 { + return nil, fmt.Errorf("missing service manager") + } + + // 2. set default + if len(_pkg) < 1 { + _pkg = pkg.DefaultManager + } + if len(_ser) < 1 { + _ser = ser.DefaultManager + } + + // 3. load managers + pkg, err := pkg.Load(_pkg) + if err != nil { + return nil, fmt.Errorf("package manager: %s", err) + } + ser, err := ser.Load(_ser) + if err != nil { + return nil, fmt.Errorf("service manager: %s", err) + } + + // 4. build context + return &ExecutionContext{ + PackageManager: pkg, + ServiceManager: ser, + }, nil + +} diff --git a/internal/instruction/reader.go b/internal/instruction/reader.go index b3fc4ee..73f1fef 100644 --- a/internal/instruction/reader.go +++ b/internal/instruction/reader.go @@ -8,16 +8,15 @@ import ( func Parse(raw string) (T, error) { // 1. format (trim + split first space) - raw = strings.Trim(raw, " \t\n") cmd := strings.SplitN(raw, " ", 2) cmd[0] = strings.Trim(cmd[0], " \t") - cmd[1] = strings.Trim(cmd[1], " \t") // 2. fail if invalid base syntax 'cmd args...' // where command is 3 letters if len(cmd) < 2 || len(cmd[0]) < 3 || cmd[0][1] == ' ' { return nil, ErrInvalidSyntax } + cmd[1] = strings.Trim(cmd[1], " \t") // 3. Extract instruction type switch cmd[0] { @@ -29,10 +28,6 @@ func Parse(raw string) (T, error) { i := &delete{} err := i.Build(cmd[1]) return i, err - case "upd": - i := &update{} - err := i.Build(cmd[1]) - return i, err case "ser": i := &service{} err := i.Build(cmd[1]) diff --git a/internal/pkg/common.go b/internal/pkg/common.go index f0c1615..00c86be 100644 --- a/internal/pkg/common.go +++ b/internal/pkg/common.go @@ -1,5 +1,7 @@ package pkg +var DefaultManager = "" + // PackageManager is the common interface for all package-manager drivers (e.g. `dpkg` for debian-based, `pacman` for arch) type PackageManager interface { // Name of executable (to check if installed and for debug) diff --git a/internal/pkg/loader.go b/internal/pkg/loader.go index bd36210..d15c24d 100644 --- a/internal/pkg/loader.go +++ b/internal/pkg/loader.go @@ -1,71 +1,38 @@ package pkg import ( - "encoding/json" "errors" - "os" - "os/exec" - "path/filepath" ) -var ErrUnknownDistribution = errors.New("unknown linux distribution") -var ErrNoCandidateInstalled = errors.New("no package-manager candidate installed") -var ErrNoDriverFound = errors.New("no driver found for the package manager") +var ErrUnknownManager = errors.New("unknown package manager") +var ErrNotInstalled = errors.New("no candidate installed") -func Load(_distro string) (PackageManager, error) { +func Load(_manager string) (PackageManager, error) { - // 1. load config file - driverTable := filepath.Join(os.Getenv("GOPATH"), "src/git.xdrm.io/xdrm-brackets/nix-amer/meta/pkg-drivers.json") - file, err := os.Open(driverTable) - if err != nil { - return nil, err - } - defer file.Close() - - // 2. Parse json - table := make(map[string][]string) - decoder := json.NewDecoder(file) - if err := decoder.Decode(&table); err != nil { - return nil, err - } - decoder = nil - - // 3. Get available package-manager list for distro - available, exists := table[_distro] - if !exists || len(available) < 1 { - return nil, ErrUnknownDistribution - } - - // 4. Check each available package-manager in order - selected := "" - for _, current := range available { - if exec.Command("which", current).Run() == nil { - selected = current - break - } - } - - // no candidate installed - if len(selected) < 1 { - return nil, ErrNoCandidateInstalled - } - - // 5. Instanciate - switch selected { + // 1. create manager (fail if unknown) + var manager PackageManager + switch _manager { case "apt-get": - return new(Apt), nil + manager = new(Apt) case "apk": - return new(Apk), nil + manager = new(Apk) case "eopkg": - return new(Eopkg), nil + manager = new(Eopkg) case "pacman": - return new(Pacman), nil + manager = new(Pacman) case "dnf": - return new(Dnf), nil + manager = new(Dnf) case "yum": - return new(Yum), nil + manager = new(Yum) + default: + return nil, ErrUnknownManager } - return nil, ErrNoDriverFound + // 2. fail if not installed + // if exec.Command("which", manager.Name()).Run() != nil { + // return nil, ErrNotInstalled + // } + + return manager, nil } diff --git a/internal/pkg/loader_test.go b/internal/pkg/loader_test.go index 9d6c26c..2c5fb82 100644 --- a/internal/pkg/loader_test.go +++ b/internal/pkg/loader_test.go @@ -1,46 +1,23 @@ package pkg import ( - "os/exec" - "strings" "testing" ) -func TestUnknownDistribution(t *testing.T) { +func TestUnknownManager(t *testing.T) { - _, err := Load("invalid-distro-xxx") - if err != ErrUnknownDistribution { - t.Errorf("expected <%s>, got <%s>", ErrUnknownDistribution, err) + _, err := Load("apt-put") + if err != ErrUnknownManager { + t.Errorf("expected <%s>, got <%s>", ErrUnknownManager, err) t.Fail() } } -func TestNoCandidateInstalled(t *testing.T) { +func TestKnownManager(t *testing.T) { - // get host distro - out, err := exec.Command("lsb_release", "-is").Output() + _, err := Load("apt-get") if err != nil { - t.Errorf("cannot get host linux distribution") - t.Fail() - } - hostDistro := strings.ToLower(strings.Trim(string(out), " \n\t")) - - // valid candidate - _, err = Load(hostDistro) - if err != nil { - t.Errorf("expected , got <%s>", err) - t.Fail() - } - - // invalid candidate - distro := "solus" - if hostDistro == distro { - distro = "ubuntu" - } - - _, err = Load(distro) - if err != ErrNoCandidateInstalled { - t.Errorf("expected <%s>, got <%s>", ErrNoCandidateInstalled, err) + t.Errorf("unexpected error <%s>", err) t.Fail() } diff --git a/internal/ser/common.go b/internal/ser/common.go index ff3465f..dfe1e72 100644 --- a/internal/ser/common.go +++ b/internal/ser/common.go @@ -1,5 +1,7 @@ package ser +var DefaultManager = "systemd" + // ServiceManager is the common interface for service managers (systemd, init.d) type ServiceManager interface { // Name of executable (to check if installed and for debug) diff --git a/internal/ser/loader.go b/internal/ser/loader.go index 4c35168..31636bb 100644 --- a/internal/ser/loader.go +++ b/internal/ser/loader.go @@ -2,31 +2,14 @@ package ser import ( "errors" - "os/exec" ) var ErrUnknownManager = errors.New("unknown service manager") -var ErrNoCandidateInstalled = errors.New("no package-manager candidate installed") - -// available service managers -// var available = []string{"systemd", "init"} -var available = []string{"systemd"} +var ErrNotInstalled = errors.New("not candidate installed") func Load(_manager string) (ServiceManager, error) { - // 1. fail if unknown manager - known := false - for _, mgr := range available { - if _manager == mgr { - known = true - break - } - } - if !known { - return nil, ErrUnknownManager - } - - // 2. Create manager accordingly + // 1. create manager (fail if unknown) var manager ServiceManager switch _manager { case "systemd": @@ -36,9 +19,9 @@ func Load(_manager string) (ServiceManager, error) { } // 2. fail if not installed - if exec.Command("which", manager.Name()).Run() == nil { - return nil, ErrNoCandidateInstalled - } + // if exec.Command("which", manager.Name()).Run() != nil { + // return nil, ErrNotInstalled + // } return manager, nil diff --git a/meta/pkg-drivers.json b/meta/pkg-drivers.json deleted file mode 100644 index 956d76e..0000000 --- a/meta/pkg-drivers.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ubuntu": [ "apt-get" ], - "debian": [ "apt-get" ], - - "alpine": [ "apk" ], - - "solus": [ "eopkg" ], - - "arch": [ "pacman" ], - - "centos": [ "dnf", "yum" ], - "fedora": [ "dnf", "yum" ] -} \ No newline at end of file