initial commit (second try)
This commit is contained in:
parent
a62cf65376
commit
ed3c0eae14
7 changed files with 656 additions and 0 deletions
226
commands.go
Normal file
226
commands.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func initCommand(lang string) error {
|
||||
if lang == "" {
|
||||
fmt.Println("Building base hako image...")
|
||||
return buildBaseImage()
|
||||
}
|
||||
|
||||
if !imageExists("hako-userland") {
|
||||
fmt.Println("Base image not found, building it first...")
|
||||
if err := buildBaseImage(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Building %s language image...\n", lang)
|
||||
return buildLanguageImage(lang)
|
||||
}
|
||||
|
||||
func upCommand(lang string) error {
|
||||
containerName, err := getProjectContainerName(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageName := fmt.Sprintf("hako-userland-%s", lang)
|
||||
if !imageExists(imageName) {
|
||||
return fmt.Errorf("image %s not found, run 'hako init %s' first", imageName, lang)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if containerExists(containerName) {
|
||||
if !containerIsRunning(containerName) {
|
||||
fmt.Printf("Starting existing container %s...\n", containerName)
|
||||
if err := startContainer(containerName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Container %s is already running\n", containerName)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Creating new container %s...\n", containerName)
|
||||
if err := createContainer(containerName, imageName, cwd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := startContainer(containerName); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Copying workspace to container...\n")
|
||||
if err := copyWorkspaceToContainer(containerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Entering container %s...\n", containerName)
|
||||
return execContainer(containerName)
|
||||
}
|
||||
|
||||
func downCommand(container string) error {
|
||||
var containerName string
|
||||
|
||||
if container != "" {
|
||||
containerName = container
|
||||
} else {
|
||||
// Find any hako container for current project
|
||||
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Names}}")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(containers) == 0 || containers[0] == "" {
|
||||
return fmt.Errorf("no hako containers found for current project")
|
||||
}
|
||||
containerName = containers[0] // Use first found container
|
||||
}
|
||||
|
||||
if !containerExists(containerName) {
|
||||
return fmt.Errorf("container %s does not exist", containerName)
|
||||
}
|
||||
|
||||
fmt.Printf("Stopping container %s...\n", containerName)
|
||||
if containerIsRunning(containerName) {
|
||||
if err := stopContainer(containerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Removing container %s...\n", containerName)
|
||||
return removeContainer(containerName)
|
||||
}
|
||||
|
||||
func psCommand() error {
|
||||
fmt.Println("Hako containers:")
|
||||
return listHakoContainers()
|
||||
}
|
||||
|
||||
func syncCommand() error {
|
||||
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Names}}")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers := strings.Split(strings.TrimSpace(output), "\n")
|
||||
if len(containers) == 0 || containers[0] == "" {
|
||||
return fmt.Errorf("no hako containers found for current project")
|
||||
}
|
||||
containerName := containers[0] // Use first found container
|
||||
|
||||
if !containerExists(containerName) {
|
||||
return fmt.Errorf("container %s does not exist", containerName)
|
||||
}
|
||||
|
||||
fmt.Printf("Syncing files from container %s...\n", containerName)
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "hako-sync-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
if err := copyFromContainer(containerName, "/workspace/.", tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syncFiles(tempDir, ".")
|
||||
}
|
||||
|
||||
func syncFiles(srcDir, destDir string) error {
|
||||
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(srcDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if relPath == ".git" || relPath == ".gitignore" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filepath.Base(relPath), ".git") {
|
||||
return nil
|
||||
}
|
||||
|
||||
destPath := filepath.Join(destDir, relPath)
|
||||
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(destPath, info.Mode())
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func copyWorkspaceToContainer(containerName string) error {
|
||||
// Get all files that should be copied (tracked + untracked, excluding ignored)
|
||||
trackedFiles, err := runCommandOutput("git", "ls-files")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
untrackedFiles, err := runCommandOutput("git", "ls-files", "--others", "--exclude-standard")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allFiles := []string{}
|
||||
if trackedFiles != "" {
|
||||
allFiles = append(allFiles, strings.Split(trackedFiles, "\n")...)
|
||||
}
|
||||
if untrackedFiles != "" {
|
||||
allFiles = append(allFiles, strings.Split(untrackedFiles, "\n")...)
|
||||
}
|
||||
|
||||
if len(allFiles) == 0 {
|
||||
return fmt.Errorf("no files to copy")
|
||||
}
|
||||
|
||||
// Copy files one by one to preserve directory structure
|
||||
for _, file := range allFiles {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create directory structure in container if needed
|
||||
dir := filepath.Dir(file)
|
||||
if dir != "." {
|
||||
if err := runCommand("docker", "exec", containerName, "mkdir", "-p", "/workspace/"+dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the file
|
||||
if err := copyToContainer(containerName, file, "/workspace/"+file); err != nil {
|
||||
return fmt.Errorf("failed to copy %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
208
docker.go
Normal file
208
docker.go
Normal file
|
@ -0,0 +1,208 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const hakoVersion = "1"
|
||||
|
||||
func getConfigDir() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
configDir := filepath.Join(homeDir, ".config", "hako")
|
||||
return configDir, nil
|
||||
}
|
||||
|
||||
func ensureConfigDir() error {
|
||||
configDir, err := getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(configDir, 0755)
|
||||
}
|
||||
|
||||
func getBaseDockerfile() string {
|
||||
return fmt.Sprintf(`# HAKO-VERSION=%s
|
||||
FROM debian:bookworm
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
git \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
fish \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js LTS
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Install Claude Code
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
|
||||
# configure some things
|
||||
RUN mkdir /workspace
|
||||
RUN git config --global --add safe.directory /workspace
|
||||
|
||||
WORKDIR /workspace
|
||||
CMD ["/usr/bin/fish"]`, hakoVersion)
|
||||
}
|
||||
|
||||
func getLanguageDockerfile(lang string) string {
|
||||
switch lang {
|
||||
case "go":
|
||||
return fmt.Sprintf(`# HAKO-VERSION=%s
|
||||
FROM hako-userland
|
||||
|
||||
RUN curl -fsSL https://go.dev/dl/go1.21.5.linux-amd64.tar.gz | tar -C /usr/local -xzf -
|
||||
ENV PATH="/usr/local/go/bin:${PATH}"
|
||||
|
||||
WORKDIR /workspace
|
||||
CMD ["/bin/bash"]`, hakoVersion)
|
||||
case "py", "python":
|
||||
return fmt.Sprintf(`# HAKO-VERSION=%s
|
||||
FROM hako-userland
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
WORKDIR /workspace
|
||||
CMD ["/bin/bash"]`, hakoVersion)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func buildBaseImage() error {
|
||||
if err := ensureConfigDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configDir, _ := getConfigDir()
|
||||
dockerfilePath := filepath.Join(configDir, "Dockerfile.base")
|
||||
|
||||
dockerfile := getBaseDockerfile()
|
||||
if err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Building base image hako-userland...\n")
|
||||
return runCommand("docker", "build", "-f", dockerfilePath, "-t", "hako-userland", ".")
|
||||
}
|
||||
|
||||
func buildLanguageImage(lang string) error {
|
||||
dockerfile := getLanguageDockerfile(lang)
|
||||
if dockerfile == "" {
|
||||
return fmt.Errorf("unsupported language: %s", lang)
|
||||
}
|
||||
|
||||
configDir, err := getConfigDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerfilePath := filepath.Join(configDir, fmt.Sprintf("Dockerfile.%s", lang))
|
||||
if err := os.WriteFile(dockerfilePath, []byte(dockerfile), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageName := fmt.Sprintf("hako-userland-%s", lang)
|
||||
fmt.Printf("Building %s image %s...\n", lang, imageName)
|
||||
return runCommand("docker", "build", "-f", dockerfilePath, "-t", imageName, ".")
|
||||
}
|
||||
|
||||
func imageExists(imageName string) bool {
|
||||
err := runCommand("docker", "image", "inspect", imageName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func containerExists(containerName string) bool {
|
||||
err := runCommand("docker", "container", "inspect", containerName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func containerIsRunning(containerName string) bool {
|
||||
output, err := runCommandOutput("docker", "container", "inspect", "-f", "{{.State.Running}}", containerName)
|
||||
return err == nil && strings.TrimSpace(output) == "true"
|
||||
}
|
||||
|
||||
func createContainer(containerName, imageName, workspaceDir string) error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := []string{"create", "--name", containerName}
|
||||
|
||||
// Mount workspace
|
||||
args = append(args, "-v", workspaceDir+":/workspace")
|
||||
|
||||
|
||||
args = append(args, "-it", imageName)
|
||||
|
||||
if err := runCommand("docker", args...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy Claude config directory if it exists
|
||||
claudeDir := filepath.Join(homeDir, ".claude")
|
||||
if _, err := os.Stat(claudeDir); err == nil {
|
||||
if err := copyToContainer(containerName, claudeDir, "/root/.claude"); err != nil {
|
||||
return fmt.Errorf("failed to copy Claude config directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy Claude config file if it exists
|
||||
claudeFile := filepath.Join(homeDir, ".claude.json")
|
||||
if _, err := os.Stat(claudeFile); err == nil {
|
||||
if err := copyToContainer(containerName, claudeFile, "/root/.claude.json"); err != nil {
|
||||
return fmt.Errorf("failed to copy Claude config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startContainer(containerName string) error {
|
||||
return runCommand("docker", "start", containerName)
|
||||
}
|
||||
|
||||
func stopContainer(containerName string) error {
|
||||
return runCommand("docker", "stop", containerName)
|
||||
}
|
||||
|
||||
func removeContainer(containerName string) error {
|
||||
return runCommand("docker", "rm", containerName)
|
||||
}
|
||||
|
||||
func execContainer(containerName string) error {
|
||||
cmd := exec.Command("docker", "exec", "-it", containerName, "/usr/bin/fish")
|
||||
fmt.Fprintf(os.Stderr, "+ %s %s\n", "docker", strings.Join([]string{"exec", "-it", containerName, "/usr/bin/fish"}, " "))
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func copyToContainer(containerName, srcPath, destPath string) error {
|
||||
return runCommand("docker", "cp", srcPath, containerName+":"+destPath)
|
||||
}
|
||||
|
||||
func copyFromContainer(containerName, srcPath, destPath string) error {
|
||||
return runCommand("docker", "cp", containerName+":"+srcPath, destPath)
|
||||
}
|
||||
|
||||
func listHakoContainers() error {
|
||||
return runCommand("docker", "ps", "-a", "--filter", "name=hako-", "--format", "table {{.Names}}\\t{{.Status}}\\t{{.Image}}")
|
||||
}
|
68
git.go
Normal file
68
git.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isGitRepository() bool {
|
||||
_, err := os.Stat(".git")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getGitRootPath() (string, error) {
|
||||
output, err := runCommandOutput("git", "rev-parse", "--show-toplevel")
|
||||
if err != nil {
|
||||
return "", errors.New("not in a git repository")
|
||||
}
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
func getProjectContainerName(lang string) (string, error) {
|
||||
if !isGitRepository() {
|
||||
return "", errors.New("must be run inside a git repository")
|
||||
}
|
||||
|
||||
gitRoot, err := getGitRootPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(gitRoot, cwd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
projectName := filepath.Base(gitRoot)
|
||||
|
||||
var pathComponents []string
|
||||
if relPath != "." {
|
||||
pathComponents = strings.Split(relPath, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
containerName := "hako-" + lang + "-" + sanitizeName(projectName)
|
||||
for _, component := range pathComponents {
|
||||
containerName += "-" + sanitizeName(component)
|
||||
}
|
||||
|
||||
if len(containerName) > 60 {
|
||||
return "", errors.New("container name too long, try running from a shallower directory")
|
||||
}
|
||||
|
||||
return containerName, nil
|
||||
}
|
||||
|
||||
func sanitizeName(name string) string {
|
||||
result := strings.ToLower(name)
|
||||
result = strings.ReplaceAll(result, " ", "_")
|
||||
result = strings.ReplaceAll(result, ".", "_")
|
||||
result = strings.ReplaceAll(result, "-", "_")
|
||||
return result
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
|||
module hako
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/spf13/cobra v1.8.0
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
10
go.sum
Normal file
10
go.sum
Normal file
|
@ -0,0 +1,10 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
99
main.go
Normal file
99
main.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "hako",
|
||||
Short: "Docker container management for Claude Code",
|
||||
Long: "A tool that manages Docker containers providing sandboxed environments for Claude Code",
|
||||
}
|
||||
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init [language]",
|
||||
Short: "Initialize Docker images",
|
||||
Long: "Build base Docker image or language-specific variants",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var lang string
|
||||
if len(args) > 0 {
|
||||
lang = args[0]
|
||||
}
|
||||
if err := initCommand(lang); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var upCmd = &cobra.Command{
|
||||
Use: "up <language>",
|
||||
Short: "Start and enter a container",
|
||||
Long: "Create or start a container for the current project and drop into shell",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := upCommand(args[0]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var downCmd = &cobra.Command{
|
||||
Use: "down [container]",
|
||||
Short: "Stop a container",
|
||||
Long: "Stop and remove a container",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var container string
|
||||
if len(args) > 0 {
|
||||
container = args[0]
|
||||
}
|
||||
if err := downCommand(container); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var psCmd = &cobra.Command{
|
||||
Use: "ps",
|
||||
Short: "List running containers",
|
||||
Long: "Show all running hako containers",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := psCommand(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var syncCmd = &cobra.Command{
|
||||
Use: "sync",
|
||||
Short: "Sync files from container",
|
||||
Long: "Copy files from container workspace to working directory",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := syncCommand(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(initCmd)
|
||||
rootCmd.AddCommand(upCmd)
|
||||
rootCmd.AddCommand(downCmd)
|
||||
rootCmd.AddCommand(psCmd)
|
||||
rootCmd.AddCommand(syncCmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
35
utils.go
Normal file
35
utils.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func runCommand(name string, args ...string) error {
|
||||
cmd := exec.Command(name, args...)
|
||||
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func runCommandOutput(name string, args ...string) (string, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func runCommandWithInput(name string, input string, args ...string) error {
|
||||
cmd := exec.Command(name, args...)
|
||||
fmt.Fprintf(os.Stderr, "+ %s %s\n", name, strings.Join(args, " "))
|
||||
cmd.Stdin = strings.NewReader(input)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue