initial commit
This commit is contained in:
commit
efcb1aeda3
12 changed files with 555 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__debug_bin
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "go-scan",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceFolder}/main.go",
|
||||||
|
"env": {
|
||||||
|
"GO_SCAN_SCANNER": "utsushi:esci:usb:/sys/devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5:1.0",
|
||||||
|
"GO_SCAN_OUTPUTDIRECTORY": "/tmp",
|
||||||
|
"GO_SCAN_ARGUMENTS": "--jpeg-quality 100 --mode Color --scan-area ISO/A4/Portrait --resolution 600"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"go.delveConfig": {
|
||||||
|
"debugAdapter": "dlv-dap",
|
||||||
|
}
|
||||||
|
}
|
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# MIT License
|
||||||
|
**Copyright (c) 2022 Daniel Sommer \<daniel.sommer@velvettear.de\>**
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
12
README.md
Normal file
12
README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# go-scan
|
||||||
|
|
||||||
|
a simple http server serving a page to execute scans
|
||||||
|
|
||||||
|
|
||||||
|
## environment variables
|
||||||
|
|
||||||
|
- `GO_SCAN_ADDRESS`: listen address of the http server
|
||||||
|
- `GO_SCAN_PORT`: port of the http server
|
||||||
|
- `GO_SCAN_SCANNER`: name of the scanner to use
|
||||||
|
- `GO_SCAN_OUTPUTDIRECTORY`: path to the output directory for scans
|
||||||
|
- `GO_SCAN_ARGUMENTS`: extra arguments to pass to the scanimage command
|
43
config/config.go
Normal file
43
config/config.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"velvettear/go-scan/util/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exported functions
|
||||||
|
func New() Config {
|
||||||
|
config := Config{
|
||||||
|
ServerConfig: serverConfig{
|
||||||
|
Address: environment.New("GO_SCAN_ADDRESS", "0.0.0.0").Value(),
|
||||||
|
Port: environment.New("GO_SCAN_PORT", "9000").Value(),
|
||||||
|
},
|
||||||
|
ScannerConfig: scannerConfig{
|
||||||
|
Scanner: environment.New("GO_SCAN_SCANNER", "").Value(),
|
||||||
|
OutputDirectory: environment.New("GO_SCAN_OUTPUTDIRECTORY", os.TempDir()).Value(),
|
||||||
|
Arguments: environment.New("GO_SCAN_ARGUMENTS", "").Value(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(config.ScannerConfig.OutputDirectory, "/") {
|
||||||
|
config.ScannerConfig.OutputDirectory = config.ScannerConfig.OutputDirectory + "/"
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// structs
|
||||||
|
type Config struct {
|
||||||
|
ServerConfig serverConfig
|
||||||
|
ScannerConfig scannerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
Port string
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
type scannerConfig struct {
|
||||||
|
Scanner string
|
||||||
|
OutputDirectory string
|
||||||
|
Arguments string
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module velvettear/go-scan
|
||||||
|
|
||||||
|
go 1.18
|
165
index.html
Normal file
165
index.html
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>go-scan</title>
|
||||||
|
<style>
|
||||||
|
#content {
|
||||||
|
position: absolute;
|
||||||
|
top: 33%;
|
||||||
|
left: 50%;
|
||||||
|
margin-right: -50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#text {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result {
|
||||||
|
margin-top: 5%;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.5em;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button {
|
||||||
|
background-color: #6e6e6e;
|
||||||
|
margin: auto;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
border: none;
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: background-color 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button:hover {
|
||||||
|
background-color: #4e4e4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button-img {
|
||||||
|
min-width: 96px;
|
||||||
|
min-height: 96px;
|
||||||
|
width: 25vh;
|
||||||
|
height: 25vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input {
|
||||||
|
margin-top: 5%;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input-label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #e1e1e1;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<div id="heading">
|
||||||
|
<h1>go-scan</h1>
|
||||||
|
</div>
|
||||||
|
<div id="button" onclick="startScan()">
|
||||||
|
<img id="button-img" src="https://cdn2.iconfinder.com/data/icons/usability-test/100/jan_yulck-26-512.png">
|
||||||
|
</div>
|
||||||
|
<div id="input">
|
||||||
|
<span id="input-label">filename:</span>
|
||||||
|
<input id="input-filename" type="text">
|
||||||
|
</div>
|
||||||
|
<div id="result">
|
||||||
|
<span id="text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function startScan() {
|
||||||
|
let button = document.getElementById("button");
|
||||||
|
let buttonDefaultColor = button.style.backgroundColor;
|
||||||
|
button.style.backgroundColor = "#ffee00";
|
||||||
|
let buttonImg = document.getElementById("button-img");
|
||||||
|
let result = document.getElementById("result");
|
||||||
|
result.style.visibility = "hidden";
|
||||||
|
let text = document.getElementById("text");
|
||||||
|
let input = document.getElementById("input");
|
||||||
|
input.style.opacity = "0"
|
||||||
|
|
||||||
|
// start rotate animation
|
||||||
|
let rotate = true;
|
||||||
|
let rotationDegree = 0;
|
||||||
|
let rotationInterval = setInterval(() => {
|
||||||
|
rotationDegree = rotateElement(buttonImg, rotationInterval, rotationDegree, !rotate, "Y");
|
||||||
|
}, 5);
|
||||||
|
|
||||||
|
// call api
|
||||||
|
fetch('scan', {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: document.getElementById("input-filename").value
|
||||||
|
})
|
||||||
|
}).then(response => {
|
||||||
|
return response.json();
|
||||||
|
}).then(response => {
|
||||||
|
let buttonColor = "#008800";
|
||||||
|
result.style.visibility = "visible";
|
||||||
|
text.innerHTML = "scan finished<br>" + response.Message;
|
||||||
|
if (response.State !== "ok") {
|
||||||
|
text.style.color = "#ff0000";
|
||||||
|
buttonColor = "#880000";
|
||||||
|
text.innerHTML = "an error occured<br>" + text.innerHTML;
|
||||||
|
}
|
||||||
|
text.style.opacity = "100";
|
||||||
|
button.style.backgroundColor = buttonColor;
|
||||||
|
rotate = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
text.style.opacity = "0";
|
||||||
|
result.style.visibility = "hidden";
|
||||||
|
button.style.backgroundColor = buttonDefaultColor;
|
||||||
|
input.style.opacity = "100";
|
||||||
|
}, 5000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateElement(element, interval, degree, stop, direction) {
|
||||||
|
if (element === undefined || interval === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (degree === undefined) {
|
||||||
|
degree = 0;
|
||||||
|
}
|
||||||
|
if (stop === true && degree % 360 === 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
degree++;
|
||||||
|
let value = "rotate";
|
||||||
|
if (direction !== undefined && (direction.toLowerCase() === "x" || direction.toLowerCase() === "y")) {
|
||||||
|
value += direction;
|
||||||
|
}
|
||||||
|
element.style.transform = value + "(" + degree + "deg)";
|
||||||
|
return degree;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
63
log/log.go
Normal file
63
log/log.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"velvettear/go-scan/util/date"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exported functions
|
||||||
|
func Debug(logMessage string, logExtras ...string) {
|
||||||
|
trace(0, logMessage, logExtras...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(logMessage string, logExtras ...string) {
|
||||||
|
trace(1, logMessage, logExtras...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warning(logMessage string, logExtras ...string) {
|
||||||
|
trace(2, logMessage, logExtras...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(logMessage string, logExtras ...string) {
|
||||||
|
trace(3, logMessage, logExtras...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexported functions
|
||||||
|
func trace(logLevel int, logMessage string, logExtras ...string) {
|
||||||
|
if len(logMessage) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
extras := ""
|
||||||
|
for index := 0; index < len(logExtras); index++ {
|
||||||
|
tmp := logExtras[index]
|
||||||
|
if len(tmp) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if index > 0 {
|
||||||
|
extras += " | "
|
||||||
|
}
|
||||||
|
extras += logExtras[index]
|
||||||
|
}
|
||||||
|
if len(extras) > 0 {
|
||||||
|
logMessage = logMessage + " (" + extras + ")"
|
||||||
|
}
|
||||||
|
fmt.Println(buildLogMessage(getPrefixForLogLevel(logLevel), logMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrefixForLogLevel(loglevel int) string {
|
||||||
|
switch loglevel {
|
||||||
|
case 3:
|
||||||
|
return "error"
|
||||||
|
case 2:
|
||||||
|
return "warning"
|
||||||
|
case 1:
|
||||||
|
return "info"
|
||||||
|
default:
|
||||||
|
return "debug"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLogMessage(prefix string, message string) string {
|
||||||
|
timestamp := date.New()
|
||||||
|
return timestamp.GetFormattedDate() + " [" + prefix + "]" + " > " + message
|
||||||
|
}
|
129
main.go
Normal file
129
main.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"velvettear/go-scan/config"
|
||||||
|
"velvettear/go-scan/log"
|
||||||
|
"velvettear/go-scan/util/date"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed index.html
|
||||||
|
var html []byte
|
||||||
|
|
||||||
|
var configuration config.Config
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
configuration = config.New()
|
||||||
|
startServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer() {
|
||||||
|
serverAddress := configuration.ServerConfig.Address + ":" + configuration.ServerConfig.Port
|
||||||
|
http.HandleFunc("/", handleHTTPRequests)
|
||||||
|
log.Info("starting server '" + serverAddress + "'...")
|
||||||
|
error := http.ListenAndServe(serverAddress, nil)
|
||||||
|
if error != nil {
|
||||||
|
log.Error("an error occured starting the server", error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHTTPRequests(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if strings.ToLower(request.Method) == "post" {
|
||||||
|
result := handleScan(request)
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
json, _ := json.Marshal(result)
|
||||||
|
writer.Write(json)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.Write(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleScan(request *http.Request) *result {
|
||||||
|
log.Info("starting scan...")
|
||||||
|
timestamp := date.Milliseconds()
|
||||||
|
|
||||||
|
var parameters requestParameters
|
||||||
|
parseErr := json.NewDecoder(request.Body).Decode(¶meters)
|
||||||
|
if parseErr != nil {
|
||||||
|
log.Error("error parsing request parameters", parseErr.Error())
|
||||||
|
}
|
||||||
|
scanName := generateScanName(parameters.Filename)
|
||||||
|
args := []string{}
|
||||||
|
if len(configuration.ScannerConfig.Scanner) > 0 {
|
||||||
|
args = append(args, "-d")
|
||||||
|
args = append(args, configuration.ScannerConfig.Scanner)
|
||||||
|
}
|
||||||
|
args = append(args, "-o")
|
||||||
|
args = append(args, scanName)
|
||||||
|
extraArgs := strings.Split(configuration.ScannerConfig.Arguments, " ")
|
||||||
|
for index := 0; index < len(extraArgs); index++ {
|
||||||
|
args = append(args, extraArgs[index])
|
||||||
|
}
|
||||||
|
log.Debug("executing command 'scanimage'...", args...)
|
||||||
|
scanCmd := exec.Command(
|
||||||
|
"scanimage",
|
||||||
|
args...,
|
||||||
|
)
|
||||||
|
_, err := scanCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("an error occured executing the scan", err.Error())
|
||||||
|
result := &result{
|
||||||
|
State: "error",
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result := &result{
|
||||||
|
State: "ok",
|
||||||
|
Message: scanName,
|
||||||
|
}
|
||||||
|
log.Info("finished scan after "+strconv.Itoa(date.GetTimeDifference(timestamp))+"ms", scanName)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateScanName(filename string) string {
|
||||||
|
if len(filename) == 0 {
|
||||||
|
day := fmt.Sprint(time.Now().Day())
|
||||||
|
if len(day) < 2 {
|
||||||
|
day = "0" + day
|
||||||
|
}
|
||||||
|
month := fmt.Sprint(int(time.Now().Month()))
|
||||||
|
if len(month) < 2 {
|
||||||
|
month = "0" + month
|
||||||
|
}
|
||||||
|
hour := fmt.Sprint(time.Now().Hour())
|
||||||
|
if len(hour) < 2 {
|
||||||
|
hour = "0" + hour
|
||||||
|
}
|
||||||
|
minute := fmt.Sprint(time.Now().Minute())
|
||||||
|
if len(minute) < 2 {
|
||||||
|
minute = "0" + minute
|
||||||
|
}
|
||||||
|
second := fmt.Sprint(time.Now().Second())
|
||||||
|
if len(second) < 2 {
|
||||||
|
second = "0" + second
|
||||||
|
}
|
||||||
|
filename = day + month + fmt.Sprint(time.Now().Year()) + "-" + hour + minute + second
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(filename, ".png") {
|
||||||
|
filename += ".png"
|
||||||
|
}
|
||||||
|
return configuration.ScannerConfig.OutputDirectory + filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// structs
|
||||||
|
type result struct {
|
||||||
|
State string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestParameters struct {
|
||||||
|
Filename string
|
||||||
|
}
|
64
util/date/date.go
Normal file
64
util/date/date.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exported functions
|
||||||
|
func New() Date {
|
||||||
|
now := time.Now()
|
||||||
|
day := fmt.Sprint(now.Day())
|
||||||
|
if len(day) < 2 {
|
||||||
|
day = "0" + day
|
||||||
|
}
|
||||||
|
month := fmt.Sprint(int(now.Month()))
|
||||||
|
if len(month) < 2 {
|
||||||
|
month = "0" + month
|
||||||
|
}
|
||||||
|
year := fmt.Sprint(now.Year())
|
||||||
|
hour := fmt.Sprint(now.Hour())
|
||||||
|
if len(hour) < 2 {
|
||||||
|
hour = "0" + hour
|
||||||
|
}
|
||||||
|
minute := fmt.Sprint(now.Minute())
|
||||||
|
if len(minute) < 2 {
|
||||||
|
minute = "0" + minute
|
||||||
|
}
|
||||||
|
second := fmt.Sprint(now.Second())
|
||||||
|
if len(second) < 2 {
|
||||||
|
second = "0" + second
|
||||||
|
}
|
||||||
|
return Date{
|
||||||
|
Day: day,
|
||||||
|
Month: month,
|
||||||
|
Year: year,
|
||||||
|
Hour: hour,
|
||||||
|
Minute: minute,
|
||||||
|
Second: second,
|
||||||
|
GetFormattedDate: func() string {
|
||||||
|
return day + "." + month + "." + year + " " + hour + ":" + minute + ":" + second
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Milliseconds() int {
|
||||||
|
return int(time.Now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTimeDifference(timestamp int) int {
|
||||||
|
return Milliseconds() - timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// structs
|
||||||
|
type getFormattedDate func() string
|
||||||
|
|
||||||
|
type Date struct {
|
||||||
|
Day string
|
||||||
|
Month string
|
||||||
|
Year string
|
||||||
|
Hour string
|
||||||
|
Minute string
|
||||||
|
Second string
|
||||||
|
GetFormattedDate getFormattedDate
|
||||||
|
}
|
33
util/environment/environment.go
Normal file
33
util/environment/environment.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package environment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exported functions
|
||||||
|
func New(name string, defaultValue string) EnvironmentVariable {
|
||||||
|
environmentVariable := EnvironmentVariable{
|
||||||
|
Key: name,
|
||||||
|
Default: defaultValue,
|
||||||
|
Value: func() string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
value := os.Getenv(name)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return environmentVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
// structs
|
||||||
|
type getValue func() string
|
||||||
|
|
||||||
|
type EnvironmentVariable struct {
|
||||||
|
Key string
|
||||||
|
Default string
|
||||||
|
Value getValue
|
||||||
|
}
|
Loading…
Reference in a new issue