Compare commits
2 Commits
master
...
circleci/m
Author | SHA1 | Date |
---|---|---|
Adrien Marquès | f13c25c7d3 | |
Adrien Marquès | bd522bde3a |
11
.drone.yml
11
.drone.yml
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: test
|
|
||||||
image: golang:1.13
|
|
||||||
commands:
|
|
||||||
- go get ./...
|
|
||||||
- go test -v -race -cover -coverprofile ./coverage.out ./...
|
|
61
README.md
61
README.md
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
[![Go version](https://img.shields.io/badge/go_version-1.11-blue.svg)](https://golang.org/doc/go1.11)
|
[![Go version](https://img.shields.io/badge/go_version-1.11-blue.svg)](https://golang.org/doc/go1.11)
|
||||||
[![License: MIT](https://img.shields.io/github/license/xdrm-brackets/nix-amer.svg)](https://opensource.org/licenses/MIT)
|
[![License: MIT](https://img.shields.io/github/license/xdrm-brackets/nix-amer.svg)](https://opensource.org/licenses/MIT)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/git.xdrm.io/go/nix-amer)](https://goreportcard.com/report/git.xdrm.io/go/nix-amer)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/xdrm-brackets/nix-amer)](https://goreportcard.com/report/github.com/xdrm-brackets/nix-amer)
|
||||||
[![Coverage Status](https://img.shields.io/coveralls/github/xdrm-brackets/nix-amer/master.svg)](https://coveralls.io/github/xdrm-brackets/nix-amer?branch=master)
|
[![Coverage Status](https://img.shields.io/coveralls/github/xdrm-brackets/nix-amer/master.svg)](https://coveralls.io/github/xdrm-brackets/nix-amer?branch=master)
|
||||||
[![Build Status](https://drone.xdrm.io/api/badges/go/nix-amer/status.svg)](https://drone.xdrm.io/go/nix-amer)
|
[![CircleCI Build Status](https://img.shields.io/circleci/project/github/xdrm-brackets/nix-amer/master.svg)](https://circleci.com/gh/xdrm-brackets/nix-amer)
|
||||||
[![Go doc](https://godoc.org/git.xdrm.io/go/nix-amer?status.svg)](https://godoc.org/git.xdrm.io/go/nix-amer)
|
[![Go doc](https://godoc.org/github.com/xdrm-brackets/nix-amer?status.svg)](https://godoc.org/github.com/xdrm-brackets/nix-amer)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: nix-amer
|
name: nix-amer
|
||||||
|
@ -25,14 +25,13 @@ author: xdrm-brackets
|
||||||
* [1. Create build file](#1-create-build-file)
|
* [1. Create build file](#1-create-build-file)
|
||||||
* [2. Run on the target](#2-run-on-the-target)
|
* [2. Run on the target](#2-run-on-the-target)
|
||||||
* [II. Commands](#ii-commands)
|
* [II. Commands](#ii-commands)
|
||||||
+ [1) Sections](#1-sections)
|
+ [1) Comments](#1-comments)
|
||||||
+ [2) Comments](#2-comments)
|
+ [2) Install/remove Packages](#2-installremove-packages)
|
||||||
+ [3) Install/remove Packages](#3-installremove-packages)
|
+ [3) Setup configuration](#3-setup-configuration)
|
||||||
+ [4) Setup configuration](#4-setup-configuration)
|
+ [4) Service management](#4-service-management)
|
||||||
+ [5) Service management](#5-service-management)
|
+ [5) Custom scripts](#5-custom-scripts)
|
||||||
+ [6) Custom scripts](#6-custom-scripts)
|
+ [6) Copy files](#6-copy-files)
|
||||||
+ [7) Copy files](#7-copy-files)
|
+ [7) Aliases](#7-aliases)
|
||||||
+ [8) Aliases](#8-aliases)
|
|
||||||
* [III. Path Expressions](#iii-path-expressions)
|
* [III. Path Expressions](#iii-path-expressions)
|
||||||
+ [1) Syntax](#1-syntax)
|
+ [1) Syntax](#1-syntax)
|
||||||
+ [2) File Formats](#2-file-formats)
|
+ [2) File Formats](#2-file-formats)
|
||||||
|
@ -60,10 +59,10 @@ In order to install the `nix-amer` executable, you must have :
|
||||||
Simply launch the following command in any terminal
|
Simply launch the following command in any terminal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go get -u git.xdrm.io/go/nix-amer
|
$ go get -u github.com/xdrm-brackets/nix-amer
|
||||||
```
|
```
|
||||||
|
|
||||||
> For those who don't know, it will load the project sources into `$GOPATH/src/git.xdrm.io/go/nix-amer` and compile into the executable at `$GOPATH/bin/nix-amer`.
|
> For those who don't know, it will load the project sources into `$GOPATH/src/github.com/xdrm-brackets/nix-amer` and compile into the executable at `$GOPATH/bin/nix-amer`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ $ go get -u git.xdrm.io/go/nix-amer
|
||||||
|
|
||||||
###### 1. Create build file
|
###### 1. Create build file
|
||||||
|
|
||||||
The first step is to write your build file according to the installation you want. While writing it you can check the syntax and validate instructions by using the `-dry-run` command-line argument as follows :
|
The first step is to write your build file according to the installation you want. While writing your build file you can check the syntax by using the `-dry-run` command-line argument as follows :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ nix-amer -p apt-get -dry-run <path/to/build/file>
|
$ nix-amer -p apt-get -dry-run <path/to/build/file>
|
||||||
|
@ -83,7 +82,7 @@ $ nix-amer -p apt-get -dry-run <path/to/build/file>
|
||||||
|
|
||||||
###### 2. Run on the target
|
###### 2. Run on the target
|
||||||
|
|
||||||
Once your build file is correct and fulfills your needs, you can log in to the target machine, install nix-amer and run it with your build file. The rich and colorful command-line output will give you a good feedback to rapidly fix problems.
|
Once your build file is correct and fulfills your needs, you can log in to the target machine, install the nix-amer and run it with your build file. Nix-amer's rich and colorful command-line output will give you a good feedback to rapidly fix problems.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,23 +94,17 @@ Once your build file is correct and fulfills your needs, you can log in to the t
|
||||||
|
|
||||||
### II. Commands
|
### II. Commands
|
||||||
|
|
||||||
Your whole setup remains in only one file. Each line contains one instruction, the list of instructions is listed below.
|
Your whole setup remains in 1 only build file. Each line contains one instruction, the list of instructions is listed below.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 1) Sections
|
#### 1) Comments
|
||||||
|
|
||||||
Each instruction is enclosed in a section (_cf. ini file format_), a section definition stands on a line where the name of the section is surrounded by `[` and `]`. Each section is executed in parallel ; the special section named `pre` is executed before every other.
|
Each line beginning with one of the following characters : `[`, `#` or `;` is considered a comment and is not interpreted.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2) Comments
|
#### 2) Install/remove Packages
|
||||||
|
|
||||||
Each line beginning with one of the following characters : `#` or `;` is considered a comment and is not interpreted.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 3) Install/remove Packages
|
|
||||||
|
|
||||||
These instructions allow you to interact with the package system available on your system.
|
These instructions allow you to interact with the package system available on your system.
|
||||||
|
|
||||||
|
@ -129,7 +122,7 @@ Remove the listed packages. If more than one, use spaces to separate package nam
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 4) Setup configuration
|
#### 3) Setup configuration
|
||||||
|
|
||||||
This instruction allow you to set fields of configuration files without the need of an editor and in a developer-readable manner.
|
This instruction allow you to set fields of configuration files without the need of an editor and in a developer-readable manner.
|
||||||
|
|
||||||
|
@ -141,7 +134,7 @@ Update a configuration file where \<expr\> is a dot-separated human-readable [pa
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 5) Service management
|
#### 4) Service management
|
||||||
|
|
||||||
These instructions allow you to interact with the service system (_cf. [systemd](https://github.com/systemd/systemd)_).
|
These instructions allow you to interact with the service system (_cf. [systemd](https://github.com/systemd/systemd)_).
|
||||||
|
|
||||||
|
@ -152,7 +145,7 @@ Perform the action on services. If more than one, use spaces to separate service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 6) Custom scripts
|
#### 5) Custom scripts
|
||||||
|
|
||||||
This instruction allows you to use custom scripts for complex operations.
|
This instruction allows you to use custom scripts for complex operations.
|
||||||
|
|
||||||
|
@ -160,9 +153,9 @@ This instruction allows you to use custom scripts for complex operations.
|
||||||
run <script>
|
run <script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Execute the executable located at the path \<script\>. If script is an [alias](#8-aliases) it will resolve to its path
|
Execute the executable located at the path \<script\>. If script is an [alias](#7-aliases) it will resolve to its path
|
||||||
|
|
||||||
#### 7) Copy files
|
#### 6) Copy files
|
||||||
|
|
||||||
This instruction allows you to copy files.
|
This instruction allows you to copy files.
|
||||||
|
|
||||||
|
@ -174,7 +167,7 @@ Try to copy the file \<src\> to the path \<dst\>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 8) Aliases
|
#### 7) Aliases
|
||||||
|
|
||||||
The file format allows you to create aliases to file paths for more readability in the [path expression](#ii-path-expressions) or with the [`run` command](#5-custom-scripts).
|
The file format allows you to create aliases to file paths for more readability in the [path expression](#ii-path-expressions) or with the [`run` command](#5-custom-scripts).
|
||||||
|
|
||||||
|
@ -202,7 +195,7 @@ The syntax is pretty fast-forward, it uses 2 levels (file, fields) to find your
|
||||||
|
|
||||||
| Field | Description | Example |
|
| Field | Description | Example |
|
||||||
| --------- | :----------------------------------- | -------------------------- |
|
| --------- | :----------------------------------- | -------------------------- |
|
||||||
| `location_or_alias` | Path to the configuration file to edit. The file will be created if not found. If the path is an [alias](#8-aliases) created before in the file, it will resolve to the alias value as a filename. | `/etc/nginx/nginx.conf`, `some-alias` |
|
| `location_or_alias` | Path to the configuration file to edit. The file will be created if not found. If the path is an [alias](#7-aliases) created before in the file, it will resolve to the alias value as a filename. | `/etc/nginx/nginx.conf`, `some-alias` |
|
||||||
| `fields` | Dot-separated chain of strings that match a configuration field. If the field does not point to a raw field but an existing field container, the \<value\> will replace the group with a text value. | `AllowGroups`, `http.gzip` |
|
| `fields` | Dot-separated chain of strings that match a configuration field. If the field does not point to a raw field but an existing field container, the \<value\> will replace the group with a text value. | `AllowGroups`, `http.gzip` |
|
||||||
|
|
||||||
> The `fields` is processed only for known file formats listed in this [section](#2-file-formats).
|
> The `fields` is processed only for known file formats listed in this [section](#2-file-formats).
|
||||||
|
@ -219,8 +212,8 @@ Configuration files can be written according to some standards or application-sp
|
||||||
|
|
||||||
- [yaml](https://en.wikipedia.org/wiki/YAML) with [go-yaml/yaml](https://github.com/go-yaml/yaml).
|
- [yaml](https://en.wikipedia.org/wiki/YAML) with [go-yaml/yaml](https://github.com/go-yaml/yaml).
|
||||||
- [ini](https://en.wikipedia.org/wiki/INI_file) with [go-ini/ini](https://github.com/go-ini/ini).
|
- [ini](https://en.wikipedia.org/wiki/INI_file) with [go-ini/ini](https://github.com/go-ini/ini).
|
||||||
- [nginx configurations](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/) with [my own library](https://godoc.org/git.xdrm.io/go/nix-amer/internal/cnf/parser/nginx).
|
|
||||||
- [bash sourced configurations]() with [my own library](https://godoc.org/git.xdrm.io/go/nix-amer/internal/cnf/parser/bash) (_e.g. ~/.bashrc_).
|
- [nginx configurations](https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/) with [my own library](https://godoc.org/github.com/xdrm-brackets/nix-amer/internal/cnf/parser/nginx).
|
||||||
- _and more to come..._
|
- _and more to come..._
|
||||||
|
|
||||||
|
|
||||||
|
|
2
args.go
2
args.go
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/instruction"
|
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetArgs manages cli arguments to build executionContext,
|
// GetArgs manages cli arguments to build executionContext,
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
shared: &shared
|
||||||
|
environment: # environment variables for the build itself
|
||||||
|
GOPATH: /go
|
||||||
|
TEST_RESULTS: /tmp/test-results
|
||||||
|
steps: # steps that comprise the `build` job
|
||||||
|
- checkout # check out source code to working directory
|
||||||
|
- run: mkdir -p $TEST_RESULTS # create the test results directory
|
||||||
|
|
||||||
|
- restore_cache: # restores saved cache if no changes are detected since last run
|
||||||
|
keys:
|
||||||
|
- v1-pkg-cache
|
||||||
|
- run:
|
||||||
|
name: Load dependencies
|
||||||
|
command: go get github.com/mattn/goveralls && go get github.com/go-ini/ini && go get gopkg.in/yaml.v2
|
||||||
|
- run:
|
||||||
|
name: Load nix-amer
|
||||||
|
command: go get github.com/xdrm-brackets/nix-amer
|
||||||
|
- run:
|
||||||
|
name: Unit tests
|
||||||
|
command: go test -v -cover -race -coverprofile=$COVER_PROFILE github.com/xdrm-brackets/nix-amer/... | tee $TEST_RESULTS/out
|
||||||
|
- run:
|
||||||
|
name: go fmt
|
||||||
|
command: go fmt github.com/xdrm-brackets/nix-amer/...
|
||||||
|
- run:
|
||||||
|
name: go vet
|
||||||
|
command: go vet github.com/xdrm-brackets/nix-amer/...
|
||||||
|
|
||||||
|
|
||||||
|
- store_artifacts: # Upload test summary for display in Artifacts
|
||||||
|
path: /tmp/test-results
|
||||||
|
destination: raw-test-output
|
||||||
|
|
||||||
|
- store_test_results: # Upload test results for display in Test Summary
|
||||||
|
path: /tmp/test-results
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
latest:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang
|
||||||
|
<<: *shared
|
||||||
|
- run:
|
||||||
|
name: Update coveralls.io
|
||||||
|
command: /go/bin/goveralls -coverprofile=$COVER_PROFILE -service=circle-ci -repotoken=$COVERALLS_TOKEN
|
||||||
|
|
||||||
|
go-1.10:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.10
|
||||||
|
<<: *shared
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -1,8 +0,0 @@
|
||||||
module git.xdrm.io/go/nix-amer
|
|
||||||
|
|
||||||
go 1.12
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/go-ini/ini v1.51.0
|
|
||||||
gopkg.in/yaml.v2 v2.2.5
|
|
||||||
)
|
|
5
go.sum
5
go.sum
|
@ -1,5 +0,0 @@
|
||||||
github.com/go-ini/ini v1.51.0 h1:VPJKXGzbKlyExUE8f41aV57yxkYx5R49yR6n7flp0M0=
|
|
||||||
github.com/go-ini/ini v1.51.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
2
help.go
2
help.go
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func help() {
|
func help() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package buildfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LineError wraps errors with a line index
|
// LineError wraps errors with a line index
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/instruction"
|
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -2,7 +2,7 @@ package buildfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"git.xdrm.io/go/nix-amer/internal/instruction"
|
"github.com/xdrm-brackets/nix-amer/internal/instruction"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
package cnf
|
|
||||||
|
|
||||||
import (
|
|
||||||
lib "git.xdrm.io/go/nix-amer/internal/cnf/parser/bash"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type bash struct {
|
|
||||||
data *lib.File
|
|
||||||
parsed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom
|
|
||||||
func (d *bash) ReadFrom(_reader io.Reader) (int64, error) {
|
|
||||||
|
|
||||||
d.data = new(lib.File)
|
|
||||||
|
|
||||||
// 1. get bash decoder
|
|
||||||
decoder := lib.NewDecoder(_reader)
|
|
||||||
err := decoder.Decode(d.data)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.parsed = true
|
|
||||||
return 0, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo
|
|
||||||
func (d *bash) WriteTo(_writer io.Writer) (int64, error) {
|
|
||||||
encoder := lib.NewEncoder(_writer)
|
|
||||||
return 0, encoder.Encode(d.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// browse returns the target of a dot-separated path (as an interface{} chain where the last is the target if found)
|
|
||||||
// if <create> is true, create if does not exist
|
|
||||||
func (d *bash) browse(_path string, create ...bool) (*lib.Line, bool) {
|
|
||||||
|
|
||||||
mustCreate := len(create) > 0 && create[0]
|
|
||||||
|
|
||||||
// 1. extract path
|
|
||||||
path := strings.Split(_path, ".")
|
|
||||||
field := path[len(path)-1]
|
|
||||||
|
|
||||||
// 2. nothing
|
|
||||||
if len(path) < 1 {
|
|
||||||
return &lib.Line{}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. iterate over path / nested fields
|
|
||||||
for _, line := range d.data.Lines {
|
|
||||||
if line.Type == lib.ASSIGNMENT && len(line.Components) > 1 && line.Components[1] == field {
|
|
||||||
return line, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. create assignment
|
|
||||||
if mustCreate {
|
|
||||||
assignment := &lib.Line{
|
|
||||||
Type: lib.ASSIGNMENT,
|
|
||||||
Components: []string{"", field, "", ""},
|
|
||||||
}
|
|
||||||
d.data.Lines = append(d.data.Lines, assignment)
|
|
||||||
return assignment, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the value of a dot-separated path, and if it exists
|
|
||||||
func (d *bash) Get(_path string) (string, bool) {
|
|
||||||
|
|
||||||
// 1. browse path
|
|
||||||
last, found := d.browse(_path, true)
|
|
||||||
if !found {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Get last field
|
|
||||||
return last.Components[2], true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the value of a dot-separated path, and creates it if not found
|
|
||||||
func (d *bash) Set(_path, _value string) bool {
|
|
||||||
|
|
||||||
// 1. browse path
|
|
||||||
last, found := d.browse(_path, true)
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Set value
|
|
||||||
last.Components[2] = _value
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,220 +0,0 @@
|
||||||
package cnf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBashGet(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
raw string
|
|
||||||
key string
|
|
||||||
}{
|
|
||||||
{"key=value;\n", "key"},
|
|
||||||
{" \t key=value;\n", "key"},
|
|
||||||
{"key=value; #comment\n", "key"},
|
|
||||||
{"\t key=value; #comment\n", "key"},
|
|
||||||
{"key=value; # comment\n", "key"},
|
|
||||||
{"\t \tkey=value; # comment\n", "key"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
|
|
||||||
parser := new(bash)
|
|
||||||
reader := bytes.NewBufferString(test.raw)
|
|
||||||
|
|
||||||
// try to extract value
|
|
||||||
_, err := parser.ReadFrom(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] parse error: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract value
|
|
||||||
value, found := parser.Get(test.key)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("[%d] expected a result, got none", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if value != "value" {
|
|
||||||
t.Errorf("[%d] expected 'value' got '%s'", i, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBashSetPathExists(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
raw string
|
|
||||||
key string
|
|
||||||
value string
|
|
||||||
}{
|
|
||||||
|
|
||||||
{"key=value;\n", "key", "newvalue"},
|
|
||||||
{" \t key=value;\n", "key", "newvalue"},
|
|
||||||
{"key=value; #comment\n", "key", "newvalue"},
|
|
||||||
{"\t key=value; #comment\n", "key", "newvalue"},
|
|
||||||
{"key=value; # comment\n", "key", "newvalue"},
|
|
||||||
{"\t \tkey=value; # comment\n", "key", "newvalue"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
|
|
||||||
parser := new(bash)
|
|
||||||
reader := bytes.NewBufferString(test.raw)
|
|
||||||
|
|
||||||
// try to extract value
|
|
||||||
_, err := parser.ReadFrom(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] parse error: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// update value
|
|
||||||
if !parser.Set(test.key, test.value) {
|
|
||||||
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check new value
|
|
||||||
value, found := parser.Get(test.key)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("[%d] expected a result, got none", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if value != test.value {
|
|
||||||
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBashSetCreatePath(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
raw string
|
|
||||||
key string
|
|
||||||
ignore string // path to field that must be present after transformation
|
|
||||||
value string
|
|
||||||
}{
|
|
||||||
{"ignore=xxx;\n", "key", "ignore", "newvalue"},
|
|
||||||
{"unknown-line\nignore=xxx;\n", "key", "ignore", "newvalue"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
|
|
||||||
parser := new(bash)
|
|
||||||
reader := bytes.NewBufferString(test.raw)
|
|
||||||
|
|
||||||
// try to extract value
|
|
||||||
_, err := parser.ReadFrom(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] parse error: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// update value
|
|
||||||
if !parser.Set(test.key, test.value) {
|
|
||||||
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check new value
|
|
||||||
value, found := parser.Get(test.key)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("[%d] expected a result, got none", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if value != test.value {
|
|
||||||
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that ignore field is still there
|
|
||||||
value, found = parser.Get(test.ignore)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("[%d] expected ignore field, got none", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if value != "xxx" {
|
|
||||||
t.Errorf("[%d] expected ignore value to be '%s' got '%s'", i, "xxx", value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBashSetCreateEncode(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
raw string
|
|
||||||
key string
|
|
||||||
value string
|
|
||||||
encoded string
|
|
||||||
}{
|
|
||||||
{"ignore=xxx;\n", "key", `"newvalue"`, "ignore=xxx;\nkey=\"newvalue\";\n"},
|
|
||||||
{"#!/bin/bash\n\nfunc(){\n\techo \"something\";\n}\nignore=xxx;\n", "key", `"newvalue"`, "#!/bin/bash\nfunc(){\n\techo \"something\";\n}\nignore=xxx;\nkey=\"newvalue\";\n"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
|
|
||||||
parser := new(bash)
|
|
||||||
r, w := bytes.NewBufferString(test.raw), new(bytes.Buffer)
|
|
||||||
|
|
||||||
// try to extract value
|
|
||||||
_, err := parser.ReadFrom(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] parse error: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// update value
|
|
||||||
if !parser.Set(test.key, test.value) {
|
|
||||||
t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check new value
|
|
||||||
value, found := parser.Get(test.key)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("[%d] expected a result, got none", i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if value != test.value {
|
|
||||||
t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeToBuffer
|
|
||||||
_, err = parser.WriteTo(w)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%d] unexpected write error <%s>", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded := w.String()
|
|
||||||
|
|
||||||
// check value
|
|
||||||
if encoded != test.encoded {
|
|
||||||
t.Errorf("[%d] wrong encoded value \n-=-=-= HAVE =-=-=-\n%s\n-=-=-= WANT =-=-=-\n%s\n-=-=-=-=-=\n", i, escape(test.encoded), escape(encoded))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to write
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -70,8 +70,6 @@ func loadFromExtension(ext string) ConfigurationFormat {
|
||||||
return new(yaml)
|
return new(yaml)
|
||||||
case ".nginx":
|
case ".nginx":
|
||||||
return new(nginx)
|
return new(nginx)
|
||||||
case ".sh":
|
|
||||||
return new(bash)
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -88,7 +86,7 @@ func loadFromContent(path string) (ConfigurationFormat, error) {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// extensions ordered by strictness of the language's syntax
|
// extensions ordered by strictness of the language's syntax
|
||||||
extensions := []string{".json", ".nginx", ".ini", ".yaml", ".sh"}
|
extensions := []string{".json", ".nginx", ".ini", ".yaml"}
|
||||||
|
|
||||||
// try to load each available extension
|
// try to load each available extension
|
||||||
for _, ext := range extensions {
|
for _, ext := range extensions {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package cnf
|
package cnf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
lib "git.xdrm.io/go/nix-amer/internal/cnf/parser/nginx"
|
lib "github.com/xdrm-brackets/nix-amer/internal/cnf/parser/nginx"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package bash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File represents a bash file
|
|
||||||
type File struct {
|
|
||||||
Lines []*Line
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder implements parser.T
|
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
|
||||||
return &Decoder{reader: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder implements parser.T
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{writer: w}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package bash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Decoder implements parser.Decoder
|
|
||||||
type Decoder struct{ reader io.Reader }
|
|
||||||
|
|
||||||
// Decode is the main function of the parser.Decoder interface
|
|
||||||
func (d *Decoder) Decode(v interface{}) error {
|
|
||||||
|
|
||||||
// check 'v'
|
|
||||||
if v == nil {
|
|
||||||
return ErrNullReceiver
|
|
||||||
}
|
|
||||||
vcast, ok := v.(*File)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidReceiver
|
|
||||||
}
|
|
||||||
vcast.Lines = make([]*Line, 0)
|
|
||||||
|
|
||||||
r := bufio.NewReader(d.reader)
|
|
||||||
n := -1 // line number
|
|
||||||
|
|
||||||
// regexes
|
|
||||||
reAssign := regexp.MustCompile(`(?m)^(\s*)([A-Za-z0-9_]+)=([^;]+);?\s*(#.+)?$`)
|
|
||||||
eof := false
|
|
||||||
|
|
||||||
for {
|
|
||||||
n++
|
|
||||||
if eof {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &Line{Type: ANY}
|
|
||||||
|
|
||||||
// 1. read line
|
|
||||||
line, err := r.ReadString('\n')
|
|
||||||
if err == io.EOF {
|
|
||||||
if len(line) > 0 {
|
|
||||||
eof = true
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return &LineError{n, err}
|
|
||||||
}
|
|
||||||
line = strings.Trim(line, "\r\n")
|
|
||||||
|
|
||||||
// 2. ignore empty
|
|
||||||
if len(strings.Trim(line, " \t\r\n")) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. assignment
|
|
||||||
match := reAssign.FindStringSubmatch(line)
|
|
||||||
if match != nil {
|
|
||||||
l.Type = ASSIGNMENT
|
|
||||||
l.Components = match[1:]
|
|
||||||
} else {
|
|
||||||
l.Components = []string{line}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. add to file
|
|
||||||
vcast.Lines = append(vcast.Lines, l)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package bash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoder implements parser.Encoder
|
|
||||||
type Encoder struct {
|
|
||||||
writer io.Writer
|
|
||||||
prefix []byte
|
|
||||||
indent []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode is the main function of the parser.Encoder interface
|
|
||||||
func (e *Encoder) Encode(v interface{}) error {
|
|
||||||
|
|
||||||
// check 'v'
|
|
||||||
vcast, ok := v.(*File)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidReceiver
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty config
|
|
||||||
if len(vcast.Lines) < 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range vcast.Lines {
|
|
||||||
|
|
||||||
// line representation
|
|
||||||
repr := ""
|
|
||||||
|
|
||||||
// ASSIGNMENT
|
|
||||||
if line.Type == ASSIGNMENT {
|
|
||||||
repr = fmt.Sprintf("%s%s=%s;", line.Components[0], line.Components[1], line.Components[2])
|
|
||||||
|
|
||||||
// optional comment
|
|
||||||
if len(line.Components[3]) > 0 {
|
|
||||||
repr += fmt.Sprintf(" %s", line.Components[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANY
|
|
||||||
} else {
|
|
||||||
repr = strings.Join(line.Components, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
repr += "\n"
|
|
||||||
|
|
||||||
_, err := e.writer.Write([]byte(repr))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package bash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNullReceiver is raised when a null receiver is provided
|
|
||||||
var ErrNullReceiver = fmt.Errorf("receiver must not be null")
|
|
||||||
|
|
||||||
// ErrInvalidReceiver is raised when an invalid receiver is provided
|
|
||||||
var ErrInvalidReceiver = fmt.Errorf("receiver must be compatible with *File")
|
|
||||||
|
|
||||||
// LineError wraps errors with a line index
|
|
||||||
type LineError struct {
|
|
||||||
Line int
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements Error
|
|
||||||
func (le LineError) Error() string {
|
|
||||||
return fmt.Sprintf(":%d %s", le.Line, clifmt.Color(31, le.Err.Error()))
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package bash
|
|
||||||
|
|
||||||
// LineType enumerates available line types
|
|
||||||
type LineType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ANY is the default line type (all except assignments)
|
|
||||||
ANY LineType = iota
|
|
||||||
// ASSIGNMENT line
|
|
||||||
ASSIGNMENT
|
|
||||||
)
|
|
||||||
|
|
||||||
// Line represents a meaningful line
|
|
||||||
type Line struct {
|
|
||||||
// Type of line
|
|
||||||
Type LineType
|
|
||||||
|
|
||||||
// Components of the line
|
|
||||||
//
|
|
||||||
// When Type = ASSIGNMENT :
|
|
||||||
// [0] = indentation (spaces and tabs)
|
|
||||||
// [1] = variable name
|
|
||||||
// [2] = variable value
|
|
||||||
// [3] = comment (optional)
|
|
||||||
Components []string
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ package nginx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNullReceiver is raised when a null receiver is provided
|
// ErrNullReceiver is raised when a null receiver is provided
|
||||||
|
|
|
@ -2,9 +2,9 @@ package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
"git.xdrm.io/go/nix-amer/internal/pkg"
|
"github.com/xdrm-brackets/nix-amer/internal/pkg"
|
||||||
"git.xdrm.io/go/nix-amer/internal/ser"
|
"github.com/xdrm-brackets/nix-amer/internal/ser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// T is the instruction common interface
|
// T is the instruction common interface
|
||||||
|
|
|
@ -2,7 +2,7 @@ package instruction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.xdrm.io/go/nix-amer/internal/cnf"
|
"github.com/xdrm-brackets/nix-amer/internal/cnf"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "git.xdrm.io/go/nix-amer/internal/exec"
|
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
|
|
||||||
type apk struct{ exec exec.Executor }
|
type apk struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type aptGet struct{ exec exec.Executor }
|
type aptGet struct{ exec exec.Executor }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultManager if not empty is the default package-manager to use when missing
|
// DefaultManager if not empty is the default package-manager to use when missing
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "git.xdrm.io/go/nix-amer/internal/exec"
|
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
|
|
||||||
type dnf struct{ exec exec.Executor }
|
type dnf struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "git.xdrm.io/go/nix-amer/internal/exec"
|
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
|
|
||||||
type eopkg struct{ exec exec.Executor }
|
type eopkg struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownManager is raised when the asked manager does not exist
|
// ErrUnknownManager is raised when the asked manager does not exist
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "git.xdrm.io/go/nix-amer/internal/exec"
|
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
|
|
||||||
type pacman struct{ exec exec.Executor }
|
type pacman struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "git.xdrm.io/go/nix-amer/internal/exec"
|
import "github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
|
|
||||||
type yum struct{ exec exec.Executor }
|
type yum struct{ exec exec.Executor }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ser
|
package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultManager if not empty is the default service-manager to use when missing
|
// DefaultManager if not empty is the default service-manager to use when missing
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownManager is raised when the asked manager does not exist
|
// ErrUnknownManager is raised when the asked manager does not exist
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ser
|
package ser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.xdrm.io/go/nix-amer/internal/exec"
|
"github.com/xdrm-brackets/nix-amer/internal/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type systemd struct{ exec exec.Executor }
|
type systemd struct{ exec exec.Executor }
|
||||||
|
|
Loading…
Reference in New Issue