diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e7d7ad6..6a9b479 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,9 @@ updates: directory: "/" schedule: interval: "weekly" + day: "friday" + time: "12:00" + timezone: "America/New_York" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ebe7280..86b8b12 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -17,11 +17,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # Step 2: Set up Go environment - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' + # Step 2: Run your build script + - name: Run build script + run: | + rm -rf ./bin + chmod +x ./scripts/build + ./scripts/build # Step 3: Use release-please to create a release - name: Create release @@ -30,29 +31,12 @@ jobs: with: release-type: go - # Step 4: Update the version in the build and perform multi-platform builds - - name: Build for multiple platforms - if: ${{ steps.release.outputs.release_created }} - run: | - VERSION="${{ steps.release.outputs.version }}" - echo "Updating version to ${VERSION}" - - mkdir -p bin - - # Build for Linux - GOOS=linux GOARCH=amd64 go build -o bin/hkup-linux -ldflags="-s -w -X cmd.version=${VERSION}" . - - # Build for Darwin/macOS - GOOS=darwin GOARCH=amd64 go build -o bin/hkup-darwin -ldflags="-s -w -X cmd.version=${VERSION}" . - - # Build for Windows (optional, uncomment if needed) - # GOOS=windows GOARCH=amd64 go build -o bin/hkup.exe -ldflags="-s -w -X cmd.version=${VERSION}" . - - # Step 5: Upload Release Artifacts if a release was created + # Step 4: Upload Release Artifacts if a release was created - name: Upload Release Artifacts if: ${{ steps.release.outputs.release_created }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release upload ${{ steps.release.outputs.tag_name }} \ - bin/* + ./bin/* + diff --git a/.hkup/pre-commit b/.hkup/pre-commit index 1e1627e..e4f22e3 100755 --- a/.hkup/pre-commit +++ b/.hkup/pre-commit @@ -4,7 +4,10 @@ if git diff --cached --name-only | grep -q '\.go$'; then echo "Formatting Go files..." gofmt -w . + echo "" + # Add formatted files to the staging area + git add . else echo "No Go files changed. Skipping formatting." echo "" diff --git a/README.md b/README.md index 2bc8c8e..61d5da3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # HkUp -> Your CLI tool with benefits built by [iton0](https://github.com/iton0) in [Go](https://go.dev/)! +Your CLI tool with benefits built by [iton0](https://github.com/iton0) in [Go](https://go.dev/)! [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/iton0/hkup-cli)](https://github.com/iton0/hkup-cli/releases/latest) [![godoc](https://godoc.org/github.com/iton0/hkup-cli?status.svg)](http://godoc.org/github.com/iton0/hkup-cli) @@ -11,16 +11,9 @@ ## Introduction Git hooks automate and implement processes in your workflow, increasing code quality and consistency. -Common use cases include: -- Commit Message Validation -- Environment Configuration -- Formatting -- Linting -- Testing +However, many developers avoid git hooks due to a lack of awareness and the perceived complexity of setup, discouraging them from using this feature. -However, many developers avoid Git hooks due to a lack of awareness and the perceived complexity of setup, discouraging them from using this feature. - -**HkUp** simplifies the management of Git hooks, allowing you to focus on the logic and usage of your hooks instead. +**HkUp** simplifies the management of git hooks, allowing you to focus on the logic and usage of your hooks instead. ## Installation External Dependencies: @@ -34,9 +27,10 @@ Run the script below (supports Linux and macOS): curl -sSL https://raw.githubusercontent.com/iton0/hkup-cli/main/scripts/install | sh ``` > [!Tip] -> To update HkUp, simply rerun the script above. It will automatically replace your current version with the latest release. +> To update HkUp, rerun the above script. +> It will replace the current version. -### Uninstalling HkUp +#### Uninstalling hkup ```sh # Locates and deletes the HkUp binary @@ -46,63 +40,49 @@ sh -c 'rm "$(command -v 'hkup')"' ## Usage Quickstart -This section provides basic information about core usage. For detailed usage information run `hkup --help`. +This section provides basic information about core usage. For detailed options run `hkup --help`. -### Initializing hkup +#### Initializing hkup Run the following command in your git repository to initialize HkUp: ```sh hkup init ``` -This creates a **.hkup** directory and sets the local **core.hooksPath** variable. If the directory already exists, it will simply update the path variable. The path is relative, ensuring that moving your repository won’t affect hook sourcing. +This command creates a **.hkup** folder and sets the local **core.hooksPath** variable. If the folder already exists, it will simply update the path variable. The path is relative, ensuring that moving your repository won’t affect hook sourcing. -### Adding & Removing hooks +#### Adding & Removing hooks Add or remove hooks easily with: ```sh hkup add - hkup remove ``` -### Templates -A **template** is a pre-configured, reusable Git hook that simplifies and automates the process of setting up hooks in a Git repository. With **HkUp**, you can create, copy, edit, or remove templates, allowing for consistent and easy application of hooks without needing to rewrite scripts each time. +#### Info & Docs +There are two commands that will help you with both HkUp and git hooks: -The templates are stored in the HkUp config templates directory that can either be found at **$XDG_CONFIG_HOME/hkup/templates** or **$HOME/.config/hkup/templates** depending on your system. +**`hkup list {hook|lang}`** +Outputs list of either available hooks or supported languages. -#### Naming Convention -Template files follow the naming convention: -`#` -Where: -- `` is the name of the template. -- `` is the specific Git hook (e.g., pre-commit, post-merge). +**`hkup doc `** +Opens your browser with Git documentation for the specified git hook, helping you understand its usage. -**Create a template**: -```sh -hkup template create -# OR -hkup template create -``` +## Future TODOs +- [ ] make an update subcommand +- [ ] store custom git hooks as templates for future use (via add template subcmd) + - Allow users to create, store, and share templates for common hooks. Users can fetch these templates over the network. +- [ ] branch-specific hooks +- [ ] logo maybe? -**Copy a template** into current working directory: -```sh -hkup template copy -``` +## Contributing +HkUp welcomes contributions to enhance this CLI application! Before submitting a pull request (PR) for a new feature, please follow these steps: -**Edit a template**: -```sh -hkup template edit -``` ->[!CAUTION] -> Editing a template will not update its copies. +1. **Create an Issue**: + If you have an idea for a new feature, please create a new issue in the repository using the **feature_request** template. Provide a clear description of the feature and its potential benefits. Please note that issues submitted without using the template may be closed without warning. -**Remove a template**: -```sh -hkup template remove -``` +2. **Wait for Approval**: + Once you submit your issue, I’ll review it and provide feedback. If I approve the feature request, I will let you know that you're free to proceed with your PR. + +3. **Submit Your PR**: + After receiving approval, you can create your PR. Be sure to reference the issue in your PR description. -## Roadmap to v1.0.0 -1. windows support -2. wrapper for git init & clone and gh repo create & clone -3. HkUp logo (may or may not keep this one) -4. better test coverage -5. Allow users to create, store, and share templates. Users can fetch these templates over internet (may need to make another repo for this). +Please note that PRs submitted without prior approval through an issue may be closed without merging. This process helps us manage feature requests effectively and ensures that contributions align with the project’s goals. diff --git a/cmd/add.go b/cmd/add.go index d1e3765..a1d5586 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -18,5 +18,6 @@ var ( ) func init() { - addCmd.Flags().StringVar(&logic.LangFlg, "lang", "", "supported languages for git hooks") + addCmd.Flags().StringVar(&logic.Lang, "lang", "", "supported languages for git hooks") + rootCmd.AddCommand(addCmd) } diff --git a/cmd/config/get.go b/cmd/config/get.go deleted file mode 100644 index 4492cfe..0000000 --- a/cmd/config/get.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -import ( - "github.com/iton0/hkup-cli/internal/logic/config" - "github.com/spf13/cobra" -) - -var ( - getCmd = &cobra.Command{ - Use: "get ", - Short: "Get a HkUp config setting", - Args: cobra.ExactArgs(1), - RunE: config.Get, - } -) - -func init() {} diff --git a/cmd/config/main.go b/cmd/config/main.go deleted file mode 100644 index ebe9b89..0000000 --- a/cmd/config/main.go +++ /dev/null @@ -1,9 +0,0 @@ -/* -Package template initializes the config subcommand and its subcommands. - -This package is utilized in the root command of [github.com/iton0/hkup-cli/cmd] -package. -*/ -package config - -// NOTE: This file is for documentation purposes and should be kept empty. diff --git a/cmd/config/root.go b/cmd/config/root.go deleted file mode 100644 index 2fdb099..0000000 --- a/cmd/config/root.go +++ /dev/null @@ -1,19 +0,0 @@ -package config - -import ( - "github.com/spf13/cobra" -) - -var ( - // RootCmd is the config command that will be added to the root HkUp command. - RootCmd = &cobra.Command{ - Use: "config", - Short: "HkUp configuration settings", - Hidden: true, // TODO: remove after finalizing configuration settings - } -) - -func init() { - RootCmd.AddCommand(getCmd) - RootCmd.AddCommand(setCmd) -} diff --git a/cmd/config/set.go b/cmd/config/set.go deleted file mode 100644 index 8f7b6f6..0000000 --- a/cmd/config/set.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -import ( - "github.com/iton0/hkup-cli/internal/logic/config" - "github.com/spf13/cobra" -) - -var ( - setCmd = &cobra.Command{ - Use: "set ", - Short: "Set a HkUp config setting", - Args: cobra.ExactArgs(2), - RunE: config.Set, - } -) - -func init() {} diff --git a/cmd/doc.go b/cmd/doc.go index cd2a9c2..f2c8a83 100644 --- a/cmd/doc.go +++ b/cmd/doc.go @@ -18,4 +18,6 @@ var ( } ) -func init() {} +func init() { + rootCmd.AddCommand(docCmd) +} diff --git a/cmd/init.go b/cmd/init.go index 7a1548b..2c3b8e0 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -9,14 +9,15 @@ var ( initCmd = &cobra.Command{ Use: "init", Short: "Initialize hkup", - Long: "Create an empty hkup directory or reinitialize an existing one", + Long: "Create an empty hkup folder or reinitialize an existing one", Args: cobra.NoArgs, RunE: logic.Init, } ) func init() { - initCmd.Flags().StringVar(&logic.GitDirFlg, "gitdir", "", "specified path to git directory") - initCmd.Flags().StringVar(&logic.WorkTreeFlg, "worktree", "", "specified path to working tree") + initCmd.Flags().StringVar(&logic.GitDir, "gitdir", "", "specified path to git directory") + initCmd.Flags().StringVar(&logic.WorkTree, "worktree", "", "specified path to working tree") initCmd.MarkFlagsRequiredTogether("gitdir", "worktree") + rootCmd.AddCommand(initCmd) } diff --git a/cmd/list.go b/cmd/list.go index 71724d0..ab8abf3 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -16,4 +16,6 @@ var ( } ) -func init() {} +func init() { + rootCmd.AddCommand(listCmd) +} diff --git a/cmd/main.go b/cmd/main.go index 00e8a88..ee9fc84 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,8 @@ /* Package cmd initializes all commands (including root command) for the HkUp CLI. + +Additionally, the package holds all tests for commands. */ package cmd -// NOTE: This file is for documentation purposes and should be kept empty. +// Note: This file should be kept empty. diff --git a/cmd/remove.go b/cmd/remove.go index 580ff73..92f6fcb 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -18,4 +18,6 @@ var ( } ) -func init() {} +func init() { + rootCmd.AddCommand(removeCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 9f05a57..85a508f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,39 +1,23 @@ package cmd import ( - "github.com/iton0/hkup-cli/cmd/config" - "github.com/iton0/hkup-cli/cmd/template" "github.com/spf13/cobra" ) var ( - // version holds the centralized version of HkUp. - // It is updated to the latest release version at build time of the binaries. - // - // INFO: look at the .github/workflows/release-please.yml to view how version - // is updated. - version = "dev" - rootCmd = &cobra.Command{ Use: "hkup", Short: "hkup CLI", Long: `hkup is a management tool for git hooks`, - Version: version, + Args: cobra.MinimumNArgs(1), + Version: "0.2.1", } ) -func init() { - rootCmd.AddCommand(initCmd) - rootCmd.AddCommand(addCmd) - rootCmd.AddCommand(removeCmd) - rootCmd.AddCommand(template.RootCmd) - rootCmd.AddCommand(config.RootCmd) - rootCmd.AddCommand(docCmd) - rootCmd.AddCommand(listCmd) -} +func init() {} // Execute serves as a wrapper for the Cobra API's Execute function, -// allowing it to be called from the [github.com/iton0/hkup-cli] package. +// allowing it to be called from the main package. func Execute() { rootCmd.Execute() } diff --git a/cmd/template/copy.go b/cmd/template/copy.go deleted file mode 100644 index 95be23b..0000000 --- a/cmd/template/copy.go +++ /dev/null @@ -1,17 +0,0 @@ -package template - -import ( - "github.com/iton0/hkup-cli/internal/logic/template" - "github.com/spf13/cobra" -) - -var ( - copyCmd = &cobra.Command{ - Use: "copy ", - Short: "Copy a git hook template", - Args: cobra.ExactArgs(1), - RunE: template.Copy, - } -) - -func init() {} diff --git a/cmd/template/create.go b/cmd/template/create.go deleted file mode 100644 index 7f9c72f..0000000 --- a/cmd/template/create.go +++ /dev/null @@ -1,28 +0,0 @@ -package template - -import ( - "github.com/iton0/hkup-cli/internal/git" - "github.com/iton0/hkup-cli/internal/logic/template" - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -var ( - createCmd = &cobra.Command{ - Use: "create []", - Short: "Create a git hook template", - ValidArgs: util.ConvertMapKeysToSlice(git.Hooks()), - Args: cobra.MatchAll(cobra.MaximumNArgs(1), cobra.OnlyValidArgs), - RunE: template.Create, - } -) - -func init() { - createCmd.Flags().StringVar(&template.TemplateLangFlg, "lang", "", "supported languages for git hooks") - createCmd.Flags().StringVar(&template.TemplateNameFlg, "name", "", "specified name for git hook template") - createCmd.Flags().BoolVar(&template.TemplateCwdFlg, "cwd", false, "use hook from current working directory") - createCmd.Flags().BoolVar(&template.TemplateCopyFlg, "copy", false, "copy to current working directory") - createCmd.Flags().BoolVar(&template.TemplateEditFlg, "edit", false, "open template in editor") - createCmd.MarkFlagsMutuallyExclusive("cwd", "lang") - createCmd.MarkFlagsMutuallyExclusive("cwd", "copy") -} diff --git a/cmd/template/edit.go b/cmd/template/edit.go deleted file mode 100644 index 5ef363c..0000000 --- a/cmd/template/edit.go +++ /dev/null @@ -1,17 +0,0 @@ -package template - -import ( - "github.com/iton0/hkup-cli/internal/logic/template" - "github.com/spf13/cobra" -) - -var ( - editCmd = &cobra.Command{ - Use: "edit ", - Short: "Edit a git hook template", - Args: cobra.ExactArgs(1), - RunE: template.Edit, - } -) - -func init() {} diff --git a/cmd/template/main.go b/cmd/template/main.go deleted file mode 100644 index 7d05225..0000000 --- a/cmd/template/main.go +++ /dev/null @@ -1,9 +0,0 @@ -/* -Package template initializes the template subcommand and its subcommands. - -This package is utilized in the root command of [github.com/iton0/hkup-cli/cmd] -package. -*/ -package template - -// NOTE: This file is for documentation purposes and should be kept empty. diff --git a/cmd/template/remove.go b/cmd/template/remove.go deleted file mode 100644 index 6445ab0..0000000 --- a/cmd/template/remove.go +++ /dev/null @@ -1,18 +0,0 @@ -package template - -import ( - "github.com/iton0/hkup-cli/internal/logic/template" - "github.com/spf13/cobra" -) - -var ( - removeCmd = &cobra.Command{ - Use: "remove ", - Aliases: []string{"rm"}, - Short: "Remove a git hook template", - Args: cobra.ExactArgs(1), - RunE: template.Remove, - } -) - -func init() {} diff --git a/cmd/template/root.go b/cmd/template/root.go deleted file mode 100644 index 8bcfc3f..0000000 --- a/cmd/template/root.go +++ /dev/null @@ -1,21 +0,0 @@ -package template - -import ( - "github.com/spf13/cobra" -) - -var ( - // RootCmd is the template command that will be added to the root HkUp command. - RootCmd = &cobra.Command{ - Use: "template", - Short: "Reusable Git hook", - Long: "A template refers to a pre-configured, reusable Git hook that can be easily applied\nto a Git repository. The main goal of a template is to simplify and automate the setup\nof these hooks, making it easy to apply them consistently without having to \nwrite or configure the scripts from scratch each time.", - } -) - -func init() { - RootCmd.AddCommand(createCmd) - RootCmd.AddCommand(copyCmd) - RootCmd.AddCommand(editCmd) - RootCmd.AddCommand(removeCmd) -} diff --git a/internal/git/main.go b/internal/git/main.go index 0a6576c..a5a0963 100644 --- a/internal/git/main.go +++ b/internal/git/main.go @@ -1,12 +1,9 @@ /* -Package git provides utilities related to Git hooks and supported scripting -languages for those hooks. +Package git provides utilities related to Git hooks and supported scripting languages for those hooks. -This package includes functionality for retrieving information about Git hooks -and determining the supported languages for writing hooks. +This package includes functionality for retrieving information about Git hooks and determining the supported languages for writing hooks. -This package is designed to facilitate the use and understanding of Git hooks -in various programming environments. +This package is designed to facilitate the use and understanding of Git hooks in various programming environments. */ package git @@ -14,12 +11,12 @@ import ( "fmt" ) +// HookDocSite is a constant of the base URL for the Git hooks documentation. +const HookDocSite = "https://git-scm.com/docs/githooks#" + var ( - // hooks is a map of Git hook names to their respective section of the - // Git hooks documentation site. - // - // INFO: This map is up to date as of 11/19/2024. - // Source: https://git-scm.com/docs/githooks + // hooks is a map of Git hook names to their corresponding sections in the Git hooks documentation. This map is kept up to date as of 10/24/2024. + // source: https://git-scm.com/docs/githooks hooks = map[string]string{ "applypatch-msg": "_applypatch_msg", "pre-applypatch": "_pre_applypatch", @@ -51,11 +48,8 @@ var ( "post-index-change": "_post_index_change", } - // supportedLangs is a map indicating which programming languages are supported - // for Git hooks. + // supportedLangs is a map indicating which programming languages are supported for Git hooks, excluding the default bash. supportedLangs = map[string]bool{ - "sh": true, - "bash": true, "python": true, "ruby": true, "node": true, @@ -64,14 +58,13 @@ var ( } ) -// GetHook retrieves URL section of git doc site for specified Git hook. -// Returns URL section of specified hook and error if the hook is not supported. +// GetHook retrieves the URL section of the documentation for a specified Git hook. Returns an error if the hook is not found. func GetHook(key string) (string, error) { - if val, exist := hooks[key]; !exist { - return "", fmt.Errorf("hook not supported: %s", key) - } else { - return val, nil + value, exists := hooks[key] + if !exists { + return "", fmt.Errorf("hook not found: %s", key) } + return value, nil } // Hooks returns the complete map of all defined Git hooks. @@ -79,14 +72,13 @@ func Hooks() map[string]string { return hooks } -// GetLang reports if a specified language is supported for Git hooks. -// Returns boolean and error if the language is not recognized. +// GetLang reports if a specified language is supported for Git hooks. Returns an error if the language is not recognized. func GetLang(key string) (bool, error) { - if _, exist := supportedLangs[key]; !exist { + value, exists := supportedLangs[key] + if !exists { return false, fmt.Errorf("language not supported: %s", key) - } else { - return true, nil } + return value, nil } // SupportedLangs returns the map of supported programming languages for Git hooks. diff --git a/internal/logic/add.go b/internal/logic/add.go index 2bc9ed4..171f503 100644 --- a/internal/logic/add.go +++ b/internal/logic/add.go @@ -2,6 +2,7 @@ package logic import ( "fmt" + "os" "path/filepath" "github.com/iton0/hkup-cli/internal/git" @@ -10,53 +11,55 @@ import ( ) var ( - // LangFlg is an optional flag indicating the programming language to use for - // the hook script. - LangFlg string + // Lang is an optional flag indicating the programming language to use for the hook script. Defaults to sh. + Lang string ) -// Add creates a new Git hook with the specified git hook name and optional -// programming language in the designated .hkup directory. -// -// Returns error if any of the steps fail above. +// Add adds a new Git hook with the specified name and optional programming language. +// It creates a new file in the designated `.hkup` directory, setting the appropriate shebang line based on the provided language. +// Returns an error if any of the steps fail, including directory existence, file creation, or permission setting. func Add(cmd *cobra.Command, args []string) error { - // Makes sure .hkup directory exists in current working directory - if !util.DoesDirectoryExist(util.HkupDirName) { - return fmt.Errorf("failed running \"hkup add\"\n%s does not exist", util.HkupDirName) + var sheBangLine = "#!/bin/sh\n\n\n\n\n" + hook := args[0] + + if Lang != "" { + if _, err := git.GetLang(Lang); err != nil { + return err + } + sheBangLine = fmt.Sprintf("#!/usr/bin/env %s\n\n\n\n\n", Lang) } - hook := args[0] - filePath := filepath.Join(util.HkupDirName, hook) + if !util.DoesDirectoryExist(FullPath) { + return fmt.Errorf("failed running \"hkup add\"\n%s does not exist", FullPath) + } + + filePath := filepath.Join(FullPath, hook) - // Does not add if hook already exists in .hkup directory if util.DoesFileExist(filePath) { return fmt.Errorf("%s hook already exists", hook) } - var fileContent string + file, err := os.Create(filePath) + if err != nil { + return err + } - // Uses the specified language from lang flag; else default to sh - if LangFlg != "" { - // make sure lang is supported - if _, err := git.GetLang(LangFlg); err != nil { - return err + defer func(file *os.File) { + err := file.Close() + if err != nil { + panic(err) } - fileContent = fmt.Sprintf("#!/usr/bin/env %s\n\n\n\n\n", LangFlg) - } else { - fileContent = "#!/bin/sh\n\n\n\n\n" - } + }(file) - file, err := util.CreateFile(filePath) + _, err = file.WriteString(sheBangLine) if err != nil { - return err + return fmt.Errorf("failed writing to file: %w", err) } - defer file.Close() - _, err = file.WriteString(fileContent) + err = os.Chmod(filePath, 0755) if err != nil { - return err + return fmt.Errorf("failed changing permissions of file: %w", err) } - // Either changes the create file's permissions successful or returns error - return util.MakeExecutable(filePath) + return nil } diff --git a/internal/logic/config/get.go b/internal/logic/config/get.go deleted file mode 100644 index 2ee0112..0000000 --- a/internal/logic/config/get.go +++ /dev/null @@ -1,19 +0,0 @@ -package config - -import ( - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -// Get prints out the value of a specified configuration setting. -// -// Returns error if issue with getting the value. -func Get(cmd *cobra.Command, args []string) error { - out, err := util.GetTOMLValue(util.GetConfigFilePath(), args[0]) - if err != nil { - return err - } - - cmd.Println(out) // This may be empty if the value is not set but key is valid - return nil -} diff --git a/internal/logic/config/main.go b/internal/logic/config/main.go deleted file mode 100644 index 2e2ca05..0000000 --- a/internal/logic/config/main.go +++ /dev/null @@ -1,13 +0,0 @@ -/* -Package template provides functionality for getting and setting HkUp config settings. -This package utilizes the cobra library for command-line interaction and is -implemented in the respective commands of the -[github.com/iton0/hkup-cli/cmd/config] package. - -Command: - - Set: Updates HkUp config setting. - - Get: Prints HkUp config setting to terminal. -*/ -package config - -// NOTE: This file is for documentation purposes and should be kept empty. diff --git a/internal/logic/config/set.go b/internal/logic/config/set.go deleted file mode 100644 index 154f07f..0000000 --- a/internal/logic/config/set.go +++ /dev/null @@ -1,15 +0,0 @@ -package config - -import ( - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -// Set updates a configuration setting with a new value. -// -// Returns error if issue with settings the configuration setting. -func Set(cmd *cobra.Command, args []string) error { - // Either setting configuration setting is successful and returns nil or - // returns error - return util.SetTOMLValue(util.GetConfigFilePath(), args[0], args[1]) -} diff --git a/internal/logic/doc.go b/internal/logic/doc.go index f2c9041..ae6321e 100644 --- a/internal/logic/doc.go +++ b/internal/logic/doc.go @@ -11,39 +11,33 @@ import ( // Doc opens the documentation for a specified Git hook in the default web browser. // The command takes a single argument, which is the key (name) of the hook. -// It constructs the URL for the documentation based on the hook name and attempts -// to open it using the appropriate command for the operating system. +// It constructs the URL for the documentation based on the hook name and attempts to open it using the appropriate command for the operating system. // // Returns: -// - error if the hook key is invalid, if the platform is unsupported, or if -// there is an issue starting the command. +// - error: Returns an error if the hook key is invalid, if the platform is unsupported, or if there is an issue starting the command; otherwise, it returns nil. func Doc(cmd *cobra.Command, args []string) error { - // Checks if the key exists and returns the url portion to add - // to the git doc site base - hook, err := git.GetHook(args[0]) - if err != nil { + key := args[0] + var termCmd *exec.Cmd + var url string + + if hook, err := git.GetHook(key); err != nil { return err + } else { + url = git.HookDocSite + hook } - url := "https://git-scm.com/docs/githooks#" + hook // Full url path for the specified git hook - var termCmd *exec.Cmd - switch runtime.GOOS { case "linux": termCmd = exec.Command("xdg-open", url) case "darwin": termCmd = exec.Command("open", url) - // case "windows": - // termCmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) + case "windows": + termCmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) default: return fmt.Errorf("unsupported platform: %s", runtime.GOOS) } - err = termCmd.Start() - if err != nil { - return err - } + termCmd.Start() - // Must be called after successfully starting terminal command above - return termCmd.Wait() // Returns error if command fails + return termCmd.Wait() } diff --git a/internal/logic/init.go b/internal/logic/init.go index f1913c3..a9c9e71 100644 --- a/internal/logic/init.go +++ b/internal/logic/init.go @@ -3,58 +3,62 @@ package logic import ( "fmt" "os/exec" + "path/filepath" "github.com/iton0/hkup-cli/internal/util" "github.com/spf13/cobra" ) var ( - // GitDirFlg is an optional flag that defines the location of the git directory. + // FullPath defines the local repository folder name to hold Git hooks via a + // relative path. + // It is treated as a constant and points to the ".hkup" directory within the + // current working directory. + FullPath = filepath.Join(".", ".hkup") + + // gitCmd defines the terminal command to use to initialize the hkup folder. + gitCmd = []string{"config", "--local", "core.hooksPath", FullPath} + + // GitDir is an optional flag that defines the location of the git directory. // Can be useful for bare repos or custom git setups where git directory and // working directory are not in the same location. - GitDirFlg string + GitDir string - // WorkTreeFlg is an optional flag that defines the location of the working tree + // WorkTree is an optional flag that defines the location of the working tree // of a local git repository. - WorkTreeFlg string + WorkTree string ) -// Init sets the .hkup directory for storing Git hooks in the current repository. +// Init initializes the hkup folder for storing Git hooks in the current repository. +// It checks if there the git directory and worktree flags are provided. +// Else it will check if the current working directory is a Git repository, creates +// the hkup folder if it doesn't exist, and sets the Git configuration for +// `core.hooksPath` to point to the hkup folder. +// Returns an error if the current directory is not a Git repository, if the folder +// creation fails, or if there is an issue setting the Git hooks path. // -// Returns error if: -// - current working directory is not a git repo -// - issue with creating .hkup directory -// - hooksPath is already set -// - issue with setting the hooksPath +// Returns: +// - error: Returns an error if any of the steps fail; otherwise, it returns nil. func Init(cmd *cobra.Command, args []string) error { - // Only runs if current working directory is git repo - err := exec.Command("git", "-C", ".", "rev-parse", "--is-inside-work-tree").Run() - if err != nil { - return err + if GitDir != "" && WorkTree != "" { + gitCmd = []string{"--git-dir=" + GitDir, "--work-tree=" + WorkTree, "config", "--local", "core.hooksPath", FullPath} + } else if err := exec.Command("git", "-C", ".", "rev-parse", "--is-inside-work-tree").Run(); err != nil { + return fmt.Errorf("failed to check if current working directory is git repo: %w", err) } - // Tries to create the .hkup directory if it does not exist - if !util.DoesDirectoryExist(util.HkupDirName) { - if err = util.CreateDirectory(util.HkupDirName); err != nil { + if !util.DoesDirectoryExist(FullPath) { + if err := util.CreateFolder(FullPath); err != nil { return err } - - cmd.Printf("Initialized hkup directory at %s\n", util.HkupDirName) + cmd.Printf("Initialized hkup folder at %s\n", FullPath) } - // Does not override the hooksPath variable if already set - if out, _ := exec.Command("git", "config", "--local", "core.hooksPath").CombinedOutput(); len(out) != 0 { + if out, _ := exec.Command("git", "config", "--local", "core.hooksPath").Output(); len(out) != 0 { return fmt.Errorf("hooksPath already set to %s", out) - } - - // Holds everything after the base 'git' in the command - gitCmd := []string{} - - if GitDirFlg != "" && WorkTreeFlg != "" { - gitCmd = []string{"--git-dir=" + GitDirFlg, "--work-tree=" + WorkTreeFlg, "config", "--local", "core.hooksPath", util.HkupDirName} } else { - gitCmd = []string{"config", "--local", "core.hooksPath", util.HkupDirName} + if err := exec.Command("git", gitCmd...).Run(); err != nil { + return fmt.Errorf("failed to set hooksPath: %w", err) + } + return nil } - - return exec.Command("git", gitCmd...).Run() } diff --git a/internal/logic/list.go b/internal/logic/list.go index e633ef4..ebb75c6 100644 --- a/internal/logic/list.go +++ b/internal/logic/list.go @@ -6,23 +6,24 @@ import ( "github.com/spf13/cobra" ) -// List displays a list of available Git hooks or supported languages based on -// the provided argument. +// List displays a list of available Git hooks or supported languages based on the provided argument. +// It takes a single argument, which determines whether to list hooks or languages. // -// Returns error if the argument is invalid. +// Returns: +// - error: Returns an error if the argument is invalid; otherwise, it returns nil. func List(cmd *cobra.Command, args []string) error { arg := args[0] - out := []string{} + var output []string - // NOTE: Default case is handled by cobra framework + // NOTE: default case is handled by cobra framework switch { case arg == "hook": - out = util.ConvertMapKeysToSlice(git.Hooks()) + output = util.ConvertMapKeysToSlice(git.Hooks()) case arg == "lang": - out = util.ConvertMapKeysToSlice(git.SupportedLangs()) + output = util.ConvertMapKeysToSlice(git.SupportedLangs()) } - for _, key := range out { + for _, key := range output { cmd.Printf(" %s\n", key) } diff --git a/internal/logic/main.go b/internal/logic/main.go index f56dbd5..4a11fb0 100644 --- a/internal/logic/main.go +++ b/internal/logic/main.go @@ -1,16 +1,14 @@ /* -Package logic provides functionality for managing Git hooks, including commands -to add, remove, and list hooks. -This package utilizes the cobra library for command-line interaction and is -implemented in the respective commands of the [github.com/iton0/hkup-cli/cmd] package. +Package logic provides functionality for managing Git hooks, including commands to add, remove, and list hooks. +This package utilizes the cobra library for command-line interaction and is implemented in the respective commands of the [cmd] package. Commands: - - Init: Initializes HkUp. - - Add: Adds a new Git hook with the specified name and optional programming language. - - Remove: Removes an existing Git hook with the specified name. - - List: Lists all available Git hooks or supported Git hook languages. - - Doc: Opens browser for specified Git hook documentation. +- Init: Initialize HkUp. +- Add: Adds a new Git hook with the specified name and optional programming language. +- Remove: Removes an existing Git hook with the specified name. +- List: Lists all available Git hooks or supported Git hook languages. +- Doc: Opens browser for specified Git hook documentation. */ package logic -// NOTE: This file is for documentation purposes and should be kept empty. +// Note: This file should be kept empty. diff --git a/internal/logic/remove.go b/internal/logic/remove.go index cb258f0..a58d8dd 100644 --- a/internal/logic/remove.go +++ b/internal/logic/remove.go @@ -5,38 +5,32 @@ import ( "os" "path/filepath" - "github.com/iton0/hkup-cli/internal/git" "github.com/iton0/hkup-cli/internal/util" "github.com/spf13/cobra" ) -// Remove deletes a specified Git hook from the .hkup directory. +// Remove deletes a specified Git hook from the hkup folder. // It takes a single argument, which is the name of the hook to be removed. // -// Returns error if: -// - the .hkup directory does not exist -// - the specified hook is not found -// - issue deleting the file +// Returns: +// - error: Returns an error if the hkup folder does not exist, if the specified hook is not found, +// or if there is an issue deleting the file; otherwise, it returns nil. func Remove(cmd *cobra.Command, args []string) error { - // cannot remove if .hkup directory does not exist - if !util.DoesDirectoryExist(util.HkupDirName) { - return fmt.Errorf("failed running \"hkup remove\"\n%s directory does not exist", util.HkupDirName) - } - hook := args[0] - // Validates that arg is a supported git hook - _, err := git.GetHook(hook) - if err != nil { - return err + if !util.DoesDirectoryExist(FullPath) { + return fmt.Errorf("failed running \"hkup remove\"\n%s folder does not exist", FullPath) } - filePath := filepath.Join(util.HkupDirName, hook) + filePath := filepath.Join(FullPath, hook) - // Cannot remove if git hook does not exist in the .hkup directory if !util.DoesFileExist(filePath) { - return fmt.Errorf("hook does not exist in current working directory: %s", hook) + return fmt.Errorf("not supported hook: %s", hook) + } + + if err := os.Remove(filePath); err != nil { + return fmt.Errorf("failed deleting file: %w", err) } - return os.Remove(filePath) + return nil } diff --git a/internal/logic/template/copy.go b/internal/logic/template/copy.go deleted file mode 100644 index 5432d95..0000000 --- a/internal/logic/template/copy.go +++ /dev/null @@ -1,107 +0,0 @@ -package template - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -// Copy copies a git hook template to the .hkup directory. -// -// Returns error if: -// - HkUp config directory does not exist -// - .hkup directory does not exist in current working directory -// - arg is not valid template name -// - issue with copying template to .hkup directory -func Copy(cmd *cobra.Command, args []string) error { - configPath := util.GetConfigDirPath() - var templateName string - - // Only start the process of copying if config path and .hkup directory exist - switch { - case !util.DoesDirectoryExist(configPath): - return fmt.Errorf("%s directory does not exist.", configPath) - case !util.DoesDirectoryExist(util.HkupDirName): - return fmt.Errorf("%s directory does not exist in current working directory", util.HkupDirName) - default: - templateName = args[0] - } - - templatePath := util.GetTemplateDirPath() - file, err := doesTemplateExist(templatePath, templateName) - switch { - case err != nil: - return err - case file == "": - return fmt.Errorf("not a valid arg \"%s\" for \"hkup template copy\"", templateName) - default: - return performCopy(file) - } -} - -// doesTemplateExist checks if any file in the directory specified by templatePath -// starts with the given prefix (template name). -// -// Returns: -// - The full file path of the first file that matches the given prefix, or an empty string if no match is found. -// - An error if there is an issue reading the directory. -func doesTemplateExist(templatePath, name string) (string, error) { - files, err := os.ReadDir(templatePath) - if err != nil { - return "", err - } - - for _, file := range files { - if strings.HasPrefix(file.Name(), name) { - return filepath.Join(templatePath, file.Name()), nil - } - } - - return "", nil -} - -// performCopy copies the template file to the current working directory with -// appropiate git hook name. -// -// Returns error if: -// - template does not follow naming convetion -// - issues with copying or making executable -func performCopy(file string) error { - cleanPath, err := cleanPath(file) - if err != nil { - return err - } - - dstPath := filepath.Join(util.HkupDirName, cleanPath) - - err = util.CopyFile(file, dstPath) - if err != nil { - return err - } - - return util.MakeExecutable(dstPath) -} - -// cleanPath takes the template file path and returns the substring of the valid -// git hook file name. If the template path does not follow the convention of -// template path naming it will return an empty string and error. -// -// NOTE: This is automatically done by HkUp when using the CLI but user may -// want to manual add a git hook template to the HkUp config template directory. -// -// The convention should follow the custom name of the hook followed by a "#" -// and then the proper git hook name. -// -// Valid Naming Convention: [custom-name]#[hook-name] -// - ex). foo#post-commit -func cleanPath(path string) (string, error) { - if idx := strings.LastIndex(path, "#"); idx != -1 { - return path[idx+1:], nil - } - - return "", fmt.Errorf("template name must follow convention of \"[custom-name]#[hook-name]\"") -} diff --git a/internal/logic/template/create.go b/internal/logic/template/create.go deleted file mode 100644 index 47c95a0..0000000 --- a/internal/logic/template/create.go +++ /dev/null @@ -1,324 +0,0 @@ -package template - -import ( - "fmt" - "path/filepath" - - "github.com/iton0/hkup-cli/internal/git" - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -var ( - // TemplateLangFlg is an optional flag indicating the language to use. - TemplateLangFlg string - - // TemplateNameFlg is an optional flag that holds the template name to be - // prepended to the template name. - TemplateNameFlg string - - // TemplateCwdFlg is an optional flag indicating to use the git hook from - // the current working directory. - TemplateCwdFlg bool - - // TemplateCopyFlg is an optional flag indicating to copy the created - // template to the current working directory. - TemplateCopyFlg bool - - // TemplateEditFlg is an optional flag indicating to edit the template. - TemplateEditFlg bool - - // template holds the information to create the new template. - // Info includes: - // - git hook name (hook) - // - language (lang) - // - custom template name (name) - // - if to use git hook in the current working directory (useCwd) - // - if to copy created template in the current working directory (copyHook) - // - if to edit the created template by opening editor (edit) - template = struct { - hook, lang, name string - useCwd, copyHook, edit bool - }{} -) - -// Create creates a git hook template from a specific git hook. -// -// Returns error if: -// - issue with creating HkUp config directory or template directory -// - issue with displaying prompt -// - issue with creating the template -func Create(cmd *cobra.Command, args []string) error { - configPath := util.GetConfigDirPath() - templatePath := util.GetTemplateDirPath() - - // Make the HkUp config directory if it does not exist - if !util.DoesDirectoryExist(configPath) { - cmd.Printf("Making HkUp config directory at %s...\n", configPath) - - err := util.CreateDirectory(configPath) - if err != nil { - return err - } - - // Also make the template subdirectory - err = util.CreateDirectory(templatePath) - if err != nil { - return err - } - } - - if len(args) == 1 { - if err := displayPrompt(templatePath, args[0]); err != nil { - return err - } - } else if err := displayPrompt(templatePath); err != nil { // no args given - return err - } - - // Either creating the template is successful and returns nil or unsuccessful - // and returns error - return createTemplate(templatePath) -} - -// createTemplate creates the template based on the given args and flags. -// Returns error if any operation fails. -func createTemplate(templatePath string) error { - fmt.Println() // Makes the output more distinct in regards to spacing - - // Full path to created template - createdTemplate := filepath.Join(templatePath, template.name+"#"+template.hook) - - // Copies git hook from current working directory to template directory - if template.useCwd { - srcPath := filepath.Join(util.HkupDirName, template.hook) - return util.CopyFile(srcPath, createdTemplate) // returns either nil or error - } - - file, err := util.CreateFile(createdTemplate) - if err != nil { - return err - } - defer file.Close() - - var fileContent string - if template.lang == "" { // Default to sh for the template language - fileContent = "#!/bin/sh\n\n\n\n\n" - } else { - fileContent = fmt.Sprintf("#!/usr/bin/env %s\n\n\n\n\n", template.lang) - } - - _, err = file.WriteString(fileContent) - if err != nil { - return err - } - - if template.edit { - if err := editTemplate(createdTemplate); err != nil { - return err - } - fmt.Println("Template successfully edited!") - } - - if template.copyHook { - dstPath := filepath.Join(util.HkupDirName, template.hook) - - err := util.CopyFile(createdTemplate, dstPath) - if err != nil { - return err - } - - err = util.MakeExecutable(dstPath) - if err != nil { - return err - } - - fmt.Println("Template copied to current working directory.") - } - - return nil -} - -// displayPrompt outputs appropiate prompts based on args and flags of command. -// Returns error if issue with displaying any of the sub prompts. -func displayPrompt(templatePath string, arg ...string) error { - fmt.Println() // Makes the output more distinct in regards to spacing - - // Takes user provided arg as hook name or asks for it - if len(arg) == 1 { - template.hook = arg[0] - fmt.Printf("Creating template with %s hook...\n\n", template.hook) - } else if err := displayHookPrompt(); err != nil { - return err - } - - // Takes name if name flag used or asks for it - if TemplateNameFlg != "" { - if out, err := doesTemplateExist(templatePath, TemplateNameFlg); err != nil { - return err - } else if out != "" { - return fmt.Errorf("template %s already exists\n", out) - } - template.name = TemplateNameFlg - } else if err := displayNamePrompt(templatePath); err != nil { - return err - } - - // Uses the current working directory's git hook if flag given or asks to do so - // NOTE: If cwd flag is used then utilizes language of that existing hook - if TemplateCwdFlg { - if !util.DoesFileExist(filepath.Join(util.HkupDirName, template.hook)) { - return fmt.Errorf("git hook %s does not exist in the current working directory", template.hook) - } - template.useCwd = true - } else { - err := displayCwdPrompt() - if err != nil { - return err - } - - // Takes language if lang flag used or asks for it - if TemplateLangFlg != "" { - if _, err = git.GetLang(TemplateLangFlg); err != nil { - return err - } - template.lang = TemplateLangFlg - } else if err = displayLangPrompt(); err != nil { - return err - } - - // Copies created template to cwd if copy flag used or asks to do so - if TemplateCopyFlg { - template.copyHook = true - } else if !template.useCwd { // Does not copy what is already in cwd - if err = displayCopyPrompt(); err != nil { - return err - } - } - - // Created template will be opened in editor or asks to do so - if TemplateEditFlg { - template.edit = true - } else if err = displayEditPrompt(); err != nil { - return err - } - } - - return nil -} - -// displayHookPrompt asks for valid git hook name to use for template. -// Returns error is issue with reading response. -func displayHookPrompt() error { - in, err := util.UserInputPrompt("Git hook name:") - if err != nil { - return err - } - - // Recursively calls this function until supplied with supported git hook - if _, err = git.GetHook(in); err != nil { - fmt.Println("Not a supported Git hook. Please try again") - return displayHookPrompt() - } - - template.hook = in - return nil -} - -// displayCwdPrompt asks whether to use current working directory's git hook as -// template. -// Returns error is issue with reading response. -func displayCwdPrompt() error { - // Does not display if the git hook type does not exist in the cwd - if !util.DoesFileExist(filepath.Join(util.HkupDirName, template.hook)) { - return nil - } - - yes, err := util.YesNoPrompt("Use from current working directory?") - if err != nil { - return err - } - - // useCwd field is false by default so only need to check if "yes" - if yes { - template.useCwd = true - } - return nil -} - -// displayLangPrompt asks what language to use for template. -// Returns error if is issue with reading reponse. -func displayLangPrompt() error { - // Does not display if we are using the existing git hook in cwd - if template.useCwd { - return nil - } - - switch in, err := util.UserInputPrompt("Language (default sh):"); { - case err != nil: - return err - case in == "": // using the default sh as the language for the hook - return nil - default: - // Recursively calls this function until supplied with supported language - if _, err = git.GetLang(in); err != nil { - fmt.Println("Not a supported language. Please try again") - return displayLangPrompt() - } - - template.lang = in - return nil - } -} - -// displayNamePrompt asks for the name of the template. -// Returns error if: -// - issue with reading response -// - issue with checking config template directory -func displayNamePrompt(templatePath string) error { - in, err := util.UserInputPrompt("Template Name:") - if err != nil { - return err - } - - if out, err := doesTemplateExist(templatePath, in); err != nil { - return err - } else if out != "" { // Keeps asking until given a unique template name - fmt.Println("Template name already exists. Please try again") - return displayNamePrompt(templatePath) - } - - template.name = in - return nil -} - -// displayCopyPrompt asks whether to copy the template to the current working -// directory. -// Returns an error if issue with reading reponse. -func displayCopyPrompt() error { - yes, err := util.YesNoPrompt("Copy to current working directory?") - if err != nil { - return err - } - - // copyHook field is false by default so only need to check if "yes" - if yes { - template.copyHook = true - } - return nil -} - -// displayEditPrompt asks whether to edit the created template. -// Returns an error if issue with reading reponse. -func displayEditPrompt() error { - yes, err := util.YesNoPrompt("Edit template?") - if err != nil { - return err - } - - // edit field is false by default so only need to check if "yes" - if yes { - template.edit = true - } - return nil -} diff --git a/internal/logic/template/edit.go b/internal/logic/template/edit.go deleted file mode 100644 index 3722079..0000000 --- a/internal/logic/template/edit.go +++ /dev/null @@ -1,58 +0,0 @@ -package template - -import ( - "fmt" - "os" - "os/exec" - - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -// Edit opens specified template in the default editor for HkUp. -// -// Returns error if: -// - template is not valid -// - editor is not found -func Edit(cmd *cobra.Command, args []string) error { - templatePath := util.GetTemplateDirPath() - - // output (without error) will either give path to template or empty string - out, err := doesTemplateExist(templatePath, args[0]) - switch { - case err != nil: - return err - case out == "": - return fmt.Errorf("%s template does not exist", args[0]) - default: - return editTemplate(out) - } -} - -// editTemplate opens the template file with the default editor for HkUp. -// Returns error if issue with opening editor. -func editTemplate(path string) error { - editor, err := util.GetEditor() - if err != nil { - return err - } - - // Create the command to open the editor with the template file - cmd := exec.Command(editor, path) - - // This allows the editor to be opened in the same terminal - // Source: https://stackoverflow.com/questions/12088138/trying-to-launch-an-external-editor-from-within-a-go-program#12089980 - // NOTE: This only applies to terminal-based editors such as vim, nvim, etc. - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - // Starts the editor - err = cmd.Start() - if err != nil { - return err - } - - // Waits for the user to finish editing - return cmd.Wait() // Either success and returns nil or returns error if issue -} diff --git a/internal/logic/template/main.go b/internal/logic/template/main.go deleted file mode 100644 index 9522264..0000000 --- a/internal/logic/template/main.go +++ /dev/null @@ -1,15 +0,0 @@ -/* -Package template provides functionality for creating and copying git hook templates -This package utilizes the cobra library for command-line interaction and is -implemented in the respective commands of the -[github.com/iton0/hkup-cli/cmd/template] package. - -Commands: - - Copy: Copies a template from template name. - - Create: Creates a git hook template. - - Remove: Removes a template from template name. - - Edit: Edits a template from template name. -*/ -package template - -// NOTE: This file is for documentation purposes and should be kept empty. diff --git a/internal/logic/template/remove.go b/internal/logic/template/remove.go deleted file mode 100644 index 9134078..0000000 --- a/internal/logic/template/remove.go +++ /dev/null @@ -1,34 +0,0 @@ -package template - -import ( - "fmt" - "os" - - "github.com/iton0/hkup-cli/internal/util" - "github.com/spf13/cobra" -) - -// Remove removes the template file from the HkUp config template directory. -// Returns error if: -// - template does not follow naming convetion -// - issues with removing file -func Remove(cmd *cobra.Command, args []string) error { - templatePath := util.GetTemplateDirPath() - - // Cannot remove template if HkUp template config path does not exist - if !util.DoesDirectoryExist(templatePath) { - return fmt.Errorf("%s directory does not exist.", templatePath) - } - - templateName := args[0] - - // Checks for template existence in HkUp template config directory - switch file, err := doesTemplateExist(templatePath, templateName); { - case err != nil: - return err - case file == "": // Specified template does not exist - return fmt.Errorf("not valid arg \"%s\" for \"hkup template remove\"", templateName) - default: // Template exists and will try to remove - return os.Remove(file) // Either success and returns nil or returns error - } -} diff --git a/internal/util/main.go b/internal/util/main.go index 6725adb..04595b6 100644 --- a/internal/util/main.go +++ b/internal/util/main.go @@ -1,133 +1,62 @@ /* -Package util provides utility functions for tasks and operations such as: - - terminal prompt creation - - file/directory operations - - retrieval of related system information - - getting/setting of HkUp configuration settings - - mutation of related data structures +Package util provides utility functions for file and directory operations, including creating folders and files, checking existence of files and directories, and converting map keys to slices. -Additionally, this package holds all constant values used throughout the -application such as: - - Git hook documentation site - - HkUp related directory names - -This package is designed to abstract the above values, tasks, and operations to -be reusable throughout the HkUp application. +This package is designed to simplify common file and directory management tasks for hkup related commands in the [internal/logic] package. */ package util import ( - "bufio" "fmt" - "io" "os" - "os/exec" - "path/filepath" - "strings" -) - -const ( - // HkupDirName defines HkUp directory name within current working directory. - HkupDirName = ".hkup" ) -// CreateDirectory makes a new directory at the specified path. -// Returns an error if the operation fails. -func CreateDirectory(path string) error { - return os.Mkdir(path, os.ModePerm) +// CreateFolder creates a new directory at the specified path. Returns an error if the operation fails. +func CreateFolder(dirPath string) error { + if err := os.Mkdir(dirPath, os.ModePerm); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dirPath, err) + } + return nil } -// CreateFile makes a new file in the specified file path name. -// Returns pointer to the new file and an error if the operation fails. -// NOTE: CreateFile does not close the file. -func CreateFile(path string) (*os.File, error) { - file, err := os.Create(path) +// CreateFile creates a new file in the specified directory with the given name. Returns an error if the operation fails. +func CreateFile(dirPath string, name string) error { + filePath := dirPath + name + file, err := os.Create(filePath) if err != nil { - return nil, err + return fmt.Errorf("failed to create file %s: %w", filePath, err) } - return file, nil + defer func(file *os.File) { + err := file.Close() + if err != nil { + panic(err) + } + }(file) + + return nil } // DoesDirectoryExist reports if a directory exists at the specified path. -func DoesDirectoryExist(path string) bool { - if info, err := os.Stat(path); os.IsNotExist(err) { +func DoesDirectoryExist(dirPath string) bool { + info, err := os.Stat(dirPath) + if os.IsNotExist(err) { return false - } else { - return info.IsDir() } + return info.IsDir() } // DoesFileExist reports if a file exists at the specified path. -func DoesFileExist(path string) bool { - if info, err := os.Stat(path); os.IsNotExist(err) { +func DoesFileExist(filePath string) bool { + info, err := os.Stat(filePath) + if os.IsNotExist(err) { return false - } else { - return !info.IsDir() - } -} - -// GetConfigDirPath returns the available HkUp config directory path. -func GetConfigDirPath() (configPath string) { - if xdgVar, exist := os.LookupEnv("XDG_CONFIG_HOME"); exist && xdgVar != "" { - configPath = filepath.Join(xdgVar, "hkup") - } else { - configPath = filepath.Join(os.Getenv("HOME"), ".config", "hkup") - } - - return configPath -} - -// GetConfigFilePath returns the HkUp file path that holds configuration settings. -func GetConfigFilePath() string { - return filepath.Join(GetConfigDirPath(), ".hkupconfig") -} - -// GetTemplateDirPath returns the HkUp config template directory path. -func GetTemplateDirPath() string { - return filepath.Join(GetConfigDirPath(), "templates") -} - -// CopyFile copies a file (without overwriting) from src file path to dest file path. -// Returns error if: -// - destination path exists -// - issue with any steps of copying -func CopyFile(src, dst string) error { - if _, err := os.Stat(dst); err == nil { - return fmt.Errorf("destination file already exists: %s", dst) - } else if !os.IsNotExist(err) { - return fmt.Errorf("failed to check if destination file exists: %w", err) - } - - srcFile, err := os.Open(src) - if err != nil { - return err - } - defer srcFile.Close() - - dstFile, err := os.Create(dst) - if err != nil { - return err - } - defer dstFile.Close() - - if _, err = io.Copy(dstFile, srcFile); err != nil { - return err } - - return nil + return !info.IsDir() } -// MakeExecutable makes the filePath executable. -// Returns error if issue with making executable. -func MakeExecutable(filePath string) error { - return os.Chmod(filePath, 0755) -} - -// ConvertMapKeysToSlice transforms the map string keys into a returned slice -// of strings. +// ConvertMapKeysToSlice converts the keys of a map into a returned slice of strings. func ConvertMapKeysToSlice[T comparable](m map[string]T) []string { - keys := []string{} + var keys []string for key := range m { keys = append(keys, key) @@ -135,168 +64,3 @@ func ConvertMapKeysToSlice[T comparable](m map[string]T) []string { return keys } - -// YesNoPrompt displays the specified prompt message to the user and asks for a -// yes/no response. -// Returns boolean and error if issue occurred during the input process. -func YesNoPrompt(prompt string) (bool, error) { - fmt.Print(prompt + "(Y/n): ") - - scanner := bufio.NewScanner(os.Stdin) - - if !scanner.Scan() { - return false, fmt.Errorf("failed to read response") - } - - response := strings.TrimSpace(scanner.Text()) - - // Pressing Enter key is equivalent to yes - if response == "" || response == "y" || response == "Y" { - return true, nil - } - - return false, nil -} - -// UserInputPrompt prompts the user with the specified message and waits for -// the user to enter a response. -// Returns reponse and error if issue occurred during the input process. -func UserInputPrompt(prompt string) (string, error) { - fmt.Print(prompt + " ") - - scanner := bufio.NewScanner(os.Stdin) - - if !scanner.Scan() { - return "", fmt.Errorf("failed to read response") - } - - return strings.TrimSpace(scanner.Text()), nil -} - -// GetEditor makes best effort to find default editor for HkUp. -// Returns editor name if found and error if issue with searching for editor. -func GetEditor() (string, error) { - // Check the HkUp config file - editor, err := GetTOMLValue(GetConfigFilePath(), "editor") - if err != nil { - return "", err - } else if editor != "" { - return editor, nil - } - - // Check in global gitconfig file - if out, err := exec.Command("git", "config", "--global", "core.editor").CombinedOutput(); err != nil { - return "", err - } else if len(out) != 0 { - // The out has a newline character at the end so take elements up until the - // "\" of the "\n" - return string(out[0:(len(out) - 1)]), nil // Converts byte slice into string - } - - // Check for EDITOR var - if editor, exist := os.LookupEnv("EDITOR"); exist && editor != "" { - return editor, nil - } - - return "", fmt.Errorf("failed to find an editor") -} - -// GetTOMLValue gets the value of a specific key from a flat TOML file. -// Returns value and error if issue with opening or reading file. -func GetTOMLValue(filePath, key string) (string, error) { - file, err := os.Open(filePath) - if err != nil { - return "", nil - } - defer file.Close() - - // Scanner to read the file line by line - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - line = strings.TrimSpace(line) - - // Skip comments or empty lines - if len(line) == 0 || line[0] == '#' || line[0] == ';' { - continue - } - - // Split the line into key and value - parts := strings.SplitN(line, "=", 2) - if len(parts) != 2 { - continue // Skip malformed lines - } - - keyInFile := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - - // Remove quotes around string values (if any) - if len(value) > 1 && value[0] == '"' && value[len(value)-1] == '"' { - value = value[1 : len(value)-1] - } - - // If the current line matches the key you're looking for, return the value - if keyInFile == key { - return value, nil - } - } - - // Handle the case where the key was not found - if err := scanner.Err(); err != nil { - return "", err - } - return "", fmt.Errorf("%s is not a valid key", key) // Returns empty string if key not found -} - -// SetTOMLValue modifies the value of a key in a flat TOML file. -// Returns error if key not found or issue with reading or wriiting to file. -func SetTOMLValue(filePath, key, newValue string) error { - // Open the TOML file - file, err := os.Open(filePath) - if err != nil { - return err - } - defer file.Close() - - updatedLines := []string{} - var keyFound bool // defaults to false - - // Scanner to read the file line by line - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - - // Skip empty lines or comments - if len(line) == 0 || line[0] == '#' || line[0] == ';' { - updatedLines = append(updatedLines, line) - continue - } - - // Split the line into key and value - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - keyInFile := strings.TrimSpace(parts[0]) - - // If the key matches, update the value - if keyInFile == key { - line = fmt.Sprintf("%s = %s", keyInFile, newValue) - keyFound = true - } - } - - updatedLines = append(updatedLines, line) - } - - // If the key was not found, return an error - if !keyFound { - return fmt.Errorf("key '%s' not found", key) - } - - // Write the updated content back to the file - err = os.WriteFile(filePath, []byte(strings.Join(updatedLines, "\n")), 0644) - if err != nil { - return err - } - - return nil -} diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000..4979386 --- /dev/null +++ b/scripts/build @@ -0,0 +1,7 @@ +#!/bin/bash + +mkdir -p ./bin + +GOOS=linux GOARCH=amd64 go build -o ./bin/hkup-linux -ldflags="-s -w" . +GOOS=darwin GOARCH=amd64 go build -o ./bin/hkup-darwin -ldflags="-s -w" . +# GOOS=windows GOARCH=amd64 go build -o ./bin/hkup.exe -ldflags="-s -w" . diff --git a/scripts/install b/scripts/install index 95f4f6e..80b6486 100755 --- a/scripts/install +++ b/scripts/install @@ -28,31 +28,10 @@ esac LATEST_RELEASE=$(curl -s "https://api.github.com/repos/$REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') echo "Latest version: $LATEST_RELEASE" -# Get the current installed version (if any) -if [ -f "$INSTALL_PATH" ]; then - INSTALLED_VERSION=$($INSTALL_PATH --version 2>/dev/null) - echo "Currently installed version: $INSTALLED_VERSION" -else - INSTALLED_VERSION="None" - echo "No version installed." -fi - -# Check if HkUp is installed and if local version matches latest release version -if [ "$INSTALLED_VERSION" != "None" ] && [ "$(echo "$INSTALLED_VERSION" | cut -d' ' -f3)" = "$(echo "$LATEST_RELEASE" | cut -c2-)" ]; then - echo "You already have the latest version installed." - exit 0 -fi - -echo "" - # Download the binary for the latest release to a temporary location TEMP_PATH=$(mktemp) echo "Downloading $BINARY_NAME version $LATEST_RELEASE..." curl -L "https://github.com/$REPO/releases/download/$LATEST_RELEASE/$BINARY_NAME" -o "$TEMP_PATH" -if [ $? -ne 0 ]; then - echo "Failed to download the binary." - exit 1 -fi # Move the downloaded binary to the installation path sudo mv "$TEMP_PATH" "$INSTALL_PATH" @@ -62,30 +41,5 @@ if [ "$(uname)" != "CYGWIN" ] && [ "$(uname)" != "MINGW" ]; then sudo chmod +x "$INSTALL_PATH" # Use sudo to change permissions fi -# Make the HkUp configuration directory and its contents -# Check if XDG_CONFIG_HOME is set and not empty -if [ -z "$XDG_CONFIG_HOME" ]; then - # If XDG_CONFIG_HOME is not set or empty, use $HOME/.config - CONFIG_DIR="$HOME/.config/hkup/templates" -else - # Otherwise, use $XDG_CONFIG_HOME - CONFIG_DIR="$XDG_CONFIG_HOME/hkup/templates" -fi - -mkdir -p "$CONFIG_DIR" - -# TODO: uncomment when finalized config settings -# # Check if the file exists -# FILE="$CONFIG_DIR/.hkupconfig" -# if [ ! -f "$FILE" ]; then -# # If the file does not exist, create it and add text -# echo "editor = \"\"" > "$FILE" -# fi +echo "hkup installed successfully!" -echo "" - -if [ "$INSTALLED_VERSION" != "None" ]; then - echo "hkup updated successfully!" -else - echo "hkup installed successfully!" -fi