package cmd import ( "fmt" "os" "path/filepath" "strings" "mach/clipboard" "mach/config" "mach/convert" "github.com/spf13/cobra" "github.com/spf13/viper" ) var Version = "dev" var ( inputType string outputType string outputPath string display bool ) var rootCmd = &cobra.Command{ Use: "mach ", Short: "Convert Markdown to Rich Text and copy to clipboard", Long: "mach reads a Markdown file, converts it to RTF, and copies the result to the system clipboard.", Args: cobra.RangeArgs(0, 1), RunE: runMach, } func Execute() { if err := rootCmd.Execute(); err != nil { os.Exit(1) } } func init() { cobra.OnInitialize(config.Init) rootCmd.Flags().StringVarP(&inputType, "input", "i", "", "input format (default from config or \"md\")") rootCmd.Flags().StringVarP(&outputType, "type", "t", "", "output format (default from config or \"rtf\")") rootCmd.Flags().StringVarP(&outputPath, "output", "o", "", "output to file; use -o or -o=path") rootCmd.Flags().BoolVarP(&display, "display", "d", false, "display converted text in terminal") rootCmd.Flags().BoolP("version", "v", false, "print version and exit") // Allow -o without a value (derives path from input file) rootCmd.Flags().Lookup("output").NoOptDefVal = " " } func runMach(cmd *cobra.Command, args []string) error { // Handle version flag if v, _ := cmd.Flags().GetBool("version"); v { fmt.Printf("mach %s\n", Version) return nil } if len(args) == 0 { return fmt.Errorf("no input file specified (use mach )") } inputFile := args[0] // Resolve input/output types: CLI flag > config > default inType := resolveType(inputType, "input_type", "md") outType := resolveType(outputType, "output_type", "rtf") _ = inType // reserved for future input format support if outType != "rtf" { return fmt.Errorf("unsupported output type: %s (only \"rtf\" is currently supported)", outType) } // Read input file source, err := os.ReadFile(inputFile) if err != nil { return fmt.Errorf("cannot read file: %w", err) } // Convert to RTF (for file output and display) rtf, err := convert.Convert(source) if err != nil { return fmt.Errorf("conversion error: %w", err) } // Convert to HTML (for clipboard — web apps like Slack/Gmail need text/html) html, err := convert.ConvertToHTML(source) if err != nil { return fmt.Errorf("html conversion error: %w", err) } // Display if requested if display { fmt.Println(string(rtf)) } // Write to file if -o was given if cmd.Flags().Changed("output") { outPath := outputPath if strings.TrimSpace(outPath) == "" { outPath = deriveOutputPath(inputFile, outType) } if err := os.WriteFile(outPath, rtf, 0644); err != nil { return fmt.Errorf("cannot write output file: %w", err) } fmt.Printf("wrote %s\n", outPath) } // Copy to clipboard (both RTF for native apps, HTML for web apps) if err := clipboard.Copy(rtf, html); err != nil { return fmt.Errorf("clipboard error: %w", err) } if !display { fmt.Println("copied to clipboard") } return nil } func resolveType(flag, configKey, fallback string) string { if flag != "" { return flag } if v := viper.GetString(configKey); v != "" { return v } return fallback } func deriveOutputPath(inputFile, outType string) string { ext := filepath.Ext(inputFile) base := strings.TrimSuffix(inputFile, ext) return base + "." + outType }