diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..a81e442 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,30 @@ +## Pull Request Checklist + + + +- [ ] I have read and followed the [contribution guidelines](https://github.com/DylanDevelops/tmpo/blob/main/CONTRIBUTING.md). +- [ ] My pull request targets the `main` branch of tmpo. +- [ ] I have tested these changes locally on my machine. + + + +Closes #[issue number] + +## Description + + + +This pull request does x, y, and z... + +## Screenshots + + diff --git a/README.md b/README.md index df5f2d9..77846ed 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,10 @@ go build -o tmpo . ## Quick Start ```bash -# Initialize an optional configuration file -tmpo init - # Start tracking (auto-detects project) tmpo start -# Add a description -tmpo start "Fixing auth bug" - -# Check current status +# Check status tmpo status # Stop tracking @@ -64,10 +58,14 @@ For detailed usage and all commands, see the [Usage Guide](docs/usage.md). ## Configuration -Optionally, but highly recommended, create a `.tmporc` file in your project to customize the project name and hourly rate: +Optionally create a `.tmporc` file in your project to customize settings: ```bash -tmpo init --name "My Project" --rate 25.50 +# Interactive form (prompts for name, rate, description) +tmpo init + +# Or skip prompts and use defaults +tmpo init --accept-defaults ``` See the [Configuration Guide](docs/configuration.md) for details. diff --git a/cmd/init.go b/cmd/init.go index 90019f6..24ebd11 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -4,69 +4,159 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "strings" "github.com/DylanDevelops/tmpo/internal/config" "github.com/DylanDevelops/tmpo/internal/project" + "github.com/manifoldco/promptui" "github.com/spf13/cobra" ) var ( - hourlyRate float64 - projectName string + acceptDefaults bool ) -// initCmd represents the init command var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a .tmporc config file", - Long: `Create a .tmporc configuration file in the current directory.`, + Long: `Create a .tmporc configuration file in the current directory using an interactive form.`, Run: func(cmd *cobra.Command, args []string) { if _, err := os.Stat(".tmporc"); err == nil { fmt.Println("Error: .tmporc already exists in this directory") - os.Exit(1) } - name := projectName - if name == "" { - cwd, err := os.Getwd() + // Detect default project name + defaultName := detectDefaultProjectName() + + var name string + var hourlyRate float64 + var description string + + if acceptDefaults { + // Use all defaults without prompting + name = defaultName + hourlyRate = 0 + description = "" + } else { + // Interactive form + fmt.Println("\n[tmpo] Initialize Project Configuration") + + // Project Name prompt + namePrompt := promptui.Prompt{ + Label: fmt.Sprintf("Project name (%s)", defaultName), + AllowEdit: true, + } + + nameInput, err := namePrompt.Run() if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + name = strings.TrimSpace(nameInput) + if name == "" { + name = defaultName + } + // Hourly Rate prompt + ratePrompt := promptui.Prompt{ + Label: "Hourly rate (press Enter to skip)", + Validate: validateHourlyRate, + } + + rateInput, err := ratePrompt.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } - if project.IsInGitRepo() { - gitName, _ := project.GetGitRoot() - if gitName != "" { - name = filepath.Base(gitName) + rateInput = strings.TrimSpace(rateInput) + if rateInput != "" { + hourlyRate, err = strconv.ParseFloat(rateInput, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing hourly rate: %v\n", err) + os.Exit(1) } } - if name == "" { - name = filepath.Base(cwd) + // Description prompt + descPrompt := promptui.Prompt{ + Label: "Description (press Enter to skip)", } + + descInput, err := descPrompt.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + description = strings.TrimSpace(descInput) } - err := config.CreateWithTemplate(name, hourlyRate) + // Create the .tmporc file + err := config.CreateWithTemplate(name, hourlyRate, description) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) } - fmt.Printf("[tmpo] Created .tmporc for project '%s'\n", name) + fmt.Printf("\n[tmpo] Created .tmporc for project '%s'\n", name) if hourlyRate > 0 { fmt.Printf(" Hourly Rate: $%.2f\n", hourlyRate) } + if description != "" { + fmt.Printf(" Description: %s\n", description) + } fmt.Println("\nYou can edit .tmporc to customize your project settings.") }, } +// detectDefaultProjectName returns the auto-detected project name +func detectDefaultProjectName() string { + cwd, err := os.Getwd() + if err != nil { + return "my-project" + } + + name := "" + if project.IsInGitRepo() { + gitName, _ := project.GetGitRoot() + if gitName != "" { + name = filepath.Base(gitName) + } + } + + if name == "" { + name = filepath.Base(cwd) + } + + return name +} + +// validateHourlyRate validates that the input is empty or a valid positive number +func validateHourlyRate(input string) error { + input = strings.TrimSpace(input) + if input == "" { + return nil // Allow empty for optional field + } + + rate, err := strconv.ParseFloat(input, 64) + if err != nil { + return fmt.Errorf("must be a valid number") + } + + if rate < 0 { + return fmt.Errorf("hourly rate cannot be negative") + } + + return nil +} + func init() { rootCmd.AddCommand(initCmd) - initCmd.Flags().Float64VarP(&hourlyRate, "rate", "r", 0, "Hourly rate for this project") - initCmd.Flags().StringVarP(&projectName, "name", "n", "", "Project name (defaults to directory/repo name)") + initCmd.Flags().BoolVarP(&acceptDefaults, "accept-defaults", "a", false, "Accept all defaults and skip interactive prompts") } diff --git a/docs/configuration.md b/docs/configuration.md index 90a086b..c41f9cb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,11 +21,22 @@ Place a `.tmporc` file in your project root to customize tracking settings for t ### Creating a Configuration File -Use `tmpo init` to create a `.tmporc` file interactively: +Use `tmpo init` to create a `.tmporc` file using an interactive form: ```bash cd ~/projects/my-project -tmpo init --name "My Project" --rate 150 +tmpo init +# You'll be prompted for: +# - Project name (defaults to auto-detected name) +# - Hourly rate (optional, press Enter to skip) +# - Description (optional, press Enter to skip) +``` + +For quick setup without prompts, use the `--accept-defaults` flag: + +```bash +tmpo init --accept-defaults +# Creates .tmporc with auto-detected project name and default values ``` This creates a `.tmporc` file in the current directory. @@ -130,28 +141,34 @@ tmpo start ### Separate Projects with Different Rates -Create a `.tmporc` in each project directory: +Create a `.tmporc` in each project directory using `tmpo init`: ```bash # Client A - $150/hour cd ~/projects/client-a -cat > .tmporc << EOF -project_name: Client A - Web Development -hourly_rate: 150.00 -EOF +tmpo init +# Project name: Client A - Web Development +# Hourly rate: 150 +# Description: [press Enter to skip] # Client B - $175/hour cd ~/projects/client-b -cat > .tmporc << EOF -project_name: Client B - Game Development -hourly_rate: 175.00 -EOF +tmpo init +# Project name: Client B - Game Development +# Hourly rate: 175 +# Description: [press Enter to skip] # Personal project - no billing cd ~/projects/my-app -cat > .tmporc << EOF -project_name: My App -hourly_rate: 0 +tmpo init --accept-defaults # Quick setup with defaults +``` + +Alternatively, you can manually create `.tmporc` files: + +```bash +cat > ~/projects/client-a/.tmporc << EOF +project_name: Client A - Web Development +hourly_rate: 150.00 EOF ``` diff --git a/docs/usage.md b/docs/usage.md index 258b915..83ca7b0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -77,22 +77,36 @@ tmpo stats --week # This week's stats ### `tmpo init` -Create a `.tmporc` configuration file for the current project. +Create a `.tmporc` configuration file for the current project using an interactive form. You'll be prompted to enter: -**Options:** +- **Project name** - Defaults to auto-detected name from Git repo or directory +- **Hourly rate** - Optional billing rate (press Enter to skip) +- **Description** - Optional project description (press Enter to skip) -- `--name "Project Name"` - Specify custom project name -- `--rate 150` - Set hourly rate for billing calculations +**Interactive Mode (default):** -**Examples:** +```bash +tmpo init +# [tmpo] Initialize Project Configuration +# Project name (my-project): [Enter custom name or press Enter for default] +# Hourly rate (press Enter to skip): 150 +# Description (press Enter to skip): Client website redesign +``` + +**Quick Mode:** + +Use the `--accept-defaults` flag to skip all prompts and use auto-detected defaults: ```bash -tmpo init # Auto-detect project name -tmpo init --name "My Project" # Specify name -tmpo init --name "Client Work" --rate 150 # Set hourly rate +tmpo init --accept-defaults # Creates .tmporc with defaults, no prompts ``` -See [Configuration Guide](configuration.md) for details on the `.tmporc` file format. +This creates a `.tmporc` file with: +- Project name from Git repo or directory name +- Hourly rate of 0 (disabled) +- Empty description + +See [Configuration Guide](configuration.md) for details on the `.tmporc` file format and manual editing. ## Advanced Features diff --git a/internal/config/config.go b/internal/config/config.go index a78b73d..7601fa0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,6 +28,7 @@ type Config struct { // Format placeholders: // %s - project name (string) // %.2f - hourly rate (float64, 2 decimal places) +// %s - description (string) // // ! IMPORTANT: When adding new fields to the Config struct above, update this template. ! const configTemplate = `# tmpo project configuration @@ -40,7 +41,7 @@ project_name: %s hourly_rate: %.2f # [OPTIONAL] Description for this project -description: "" +description: "%s" ` // Load reads a YAML configuration file from the provided path and unmarshals it into a Config. @@ -98,13 +99,13 @@ func Create(projectName string, hourlyRate float64) error { // CreateWithTemplate creates a new .tmporc file with a user-friendly format that includes // all fields (even if empty) and helpful comments. This provides a better user experience // by showing all available configuration options. -func CreateWithTemplate(projectName string, hourlyRate float64) error { +func CreateWithTemplate(projectName string, hourlyRate float64, description string) error { tmporc := filepath.Join(".", ".tmporc") if _, err := os.Stat(tmporc); err == nil { return fmt.Errorf(".tmporc already exists") } - content := fmt.Sprintf(configTemplate, projectName, hourlyRate) + content := fmt.Sprintf(configTemplate, projectName, hourlyRate, description) if err := os.WriteFile(tmporc, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write config: %w", err)