Skip to content

Commit f88cc2c

Browse files
authored
Merge pull request #16 from yannick2009/master
Feature and Refactor: Custom Text Support, Logging Cleanup, and CLI Refinement
2 parents 33afb7f + dffca73 commit f88cc2c

22 files changed

+711
-569
lines changed

cmd/fetch.go

Lines changed: 54 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,35 @@ package cmd
55
import (
66
"encoding/json"
77
"fmt"
8-
"github.com/spf13/cobra"
9-
"io"
10-
"net/http"
118
"strings"
12-
"time"
13-
)
14-
15-
func formatText(text string) string {
16-
text = strings.TrimSpace(text)
17-
18-
text = strings.Join(strings.Fields(text), " ")
199

20-
text = strings.Map(func(r rune) rune {
21-
if r < 32 || r > 126 {
22-
return -1
23-
}
24-
return r
25-
}, text)
10+
"github.com/prime-run/go-typer/types"
11+
"github.com/prime-run/go-typer/utils"
12+
"github.com/spf13/cobra"
13+
)
2614

27-
return text
28-
}
15+
const (
16+
ModeDefault types.Mode = "default" // default mode
17+
ModeWords types.Mode = "words" // word mode
18+
ModeSentences types.Mode = "sentences" // sentence mode
2919

30-
func formatForGameMode(text string, mode string) string {
31-
text = formatText(text)
20+
zenQuotesAPIURL = "https://zenquotes.io/api/random" // ZenQuotes API URL
21+
bibleAPIURL = "https://bible-api.com/john+3:16" // Bible API URL
22+
)
3223

33-
switch mode {
34-
case "words":
35-
words := strings.Fields(text)
36-
return strings.Join(words, "\n")
37-
case "sentences":
38-
text = strings.ReplaceAll(text, ".", ".\n")
39-
text = strings.ReplaceAll(text, "!", "!\n")
40-
text = strings.ReplaceAll(text, "?", "?\n")
41-
lines := strings.Split(text, "\n")
42-
var cleanLines []string
43-
for _, line := range lines {
44-
if clean := strings.TrimSpace(line); clean != "" {
45-
cleanLines = append(cleanLines, clean)
46-
}
47-
}
48-
return strings.Join(cleanLines, "\n")
49-
default:
50-
return text
51-
}
24+
func init() {
25+
rootCmd.AddCommand(fetchCmd)
5226
}
5327

5428
var fetchCmd = &cobra.Command{
5529
Use: "fetch",
5630
Short: "Test text fetching from APIs",
5731
Run: func(cmd *cobra.Command, args []string) {
58-
modes := []string{"default", "words", "sentences"}
32+
modes := []types.Mode{ModeDefault, ModeWords, ModeSentences}
5933

6034
fmt.Println("Trying ZenQuotes API...")
61-
zenQuotes := &TextSource{
62-
URL: "https://zenquotes.io/api/random",
35+
zenQuotes := &types.TextSource{
36+
URL: zenQuotesAPIURL,
6337
Parser: func(body []byte) (string, error) {
6438
var result []struct {
6539
Quote string `json:"q"`
@@ -72,11 +46,11 @@ var fetchCmd = &cobra.Command{
7246
return "", fmt.Errorf("no quotes found in response")
7347
}
7448

75-
quote := formatText(result[0].Quote)
76-
author := formatText(result[0].Author)
49+
quote := utils.FormatText(result[0].Quote)
50+
author := utils.FormatText(result[0].Author)
7751

7852
formattedQuote := quote
79-
if !strings.HasSuffix(quote, ".") && !strings.HasSuffix(quote, "!") && !strings.HasSuffix(quote, "?") {
53+
if utils.HasPonctuationSuffix(quote) {
8054
formattedQuote += "."
8155
}
8256
return fmt.Sprintf("%s - %s", formattedQuote, author), nil
@@ -88,27 +62,23 @@ var fetchCmd = &cobra.Command{
8862
fmt.Printf("\nZenQuotes API success:\n")
8963
for _, mode := range modes {
9064
formatted := formatForGameMode(text, mode)
91-
fmt.Printf("\nMode: %s\n", mode)
92-
fmt.Printf("Text:\n%s\n", formatted)
93-
fmt.Printf("Character count: %d\n", len(formatted))
94-
fmt.Printf("Line count: %d\n", len(strings.Split(formatted, "\n")))
65+
utils.PrintTextStats(mode, formatted)
9566
}
9667
}
9768

9869
fmt.Println("\nTrying Bible API...")
99-
bible := &TextSource{
100-
URL: "https://bible-api.com/john+3:16",
70+
bible := &types.TextSource{
71+
URL: bibleAPIURL,
10172
Parser: func(body []byte) (string, error) {
10273
var result struct {
103-
Text string `json:"text"`
104-
Reference string `json:"reference"`
74+
Text string `json:"text"`
10575
}
10676
if err := json.Unmarshal(body, &result); err != nil {
10777
return "", fmt.Errorf("failed to parse JSON: %w", err)
10878
}
10979

110-
verse := formatText(result.Text)
111-
// WARN:don't include the reference for typing practice
80+
verse := utils.FormatText(result.Text)
81+
// !WARN: don't include the reference for typing practice
11282
return verse, nil
11383
},
11484
}
@@ -118,47 +88,44 @@ var fetchCmd = &cobra.Command{
11888
fmt.Printf("\nBible API success:\n")
11989
for _, mode := range modes {
12090
formatted := formatForGameMode(text, mode)
121-
fmt.Printf("\nMode: %s\n", mode)
122-
fmt.Printf("Text:\n%s\n", formatted)
123-
fmt.Printf("Character count: %d\n", len(formatted))
124-
fmt.Printf("Line count: %d\n", len(strings.Split(formatted, "\n")))
91+
utils.PrintTextStats(mode, formatted)
12592
}
12693
}
12794
},
12895
}
12996

130-
type TextSource struct {
131-
URL string
132-
Parser func([]byte) (string, error)
133-
}
134-
135-
func (s *TextSource) FetchText() (string, error) {
136-
client := &http.Client{
137-
Timeout: 10 * time.Second,
138-
}
97+
// formatForGameMode formats the text based on the specified game mode.
98+
func formatForGameMode(text string, mode types.Mode) string {
99+
text = utils.FormatText(text)
139100

140-
fmt.Printf("Fetching from URL: %s\n", s.URL)
141-
resp, err := client.Get(s.URL)
142-
if err != nil {
143-
return "", fmt.Errorf("failed to fetch: %w", err)
101+
switch mode {
102+
case ModeWords:
103+
return formatForWords(text)
104+
case ModeSentences:
105+
return formatForSentences(text)
106+
default:
107+
return text
144108
}
145-
defer resp.Body.Close()
109+
}
146110

147-
if resp.StatusCode != http.StatusOK {
148-
return "", fmt.Errorf("API returned status %d", resp.StatusCode)
149-
}
111+
// formatForWords formats the text for word mode by splitting it into lines based on spaces.
112+
// Each word is placed on a new line.
113+
func formatForWords(text string) string {
114+
words := strings.Fields(text)
115+
return strings.Join(words, "\n")
116+
}
150117

151-
body, err := io.ReadAll(resp.Body)
152-
if err != nil {
153-
return "", fmt.Errorf("failed to read response: %w", err)
154-
}
118+
// formatForSentences formats the text for sentence mode by splitting it into lines based on punctuation.
119+
func formatForSentences(text string) string {
120+
text = strings.ReplaceAll(text, ".", ".\n")
121+
text = strings.ReplaceAll(text, "!", "!\n")
122+
text = strings.ReplaceAll(text, "?", "?\n")
155123

156-
if s.Parser != nil {
157-
return s.Parser(body)
124+
var cleanLines []string
125+
for line := range strings.Lines(text) {
126+
if clean := strings.TrimSpace(line); clean != "" {
127+
cleanLines = append(cleanLines, clean)
128+
}
158129
}
159-
return string(body), nil
160-
}
161-
162-
func init() {
163-
rootCmd.AddCommand(fetchCmd)
130+
return strings.Join(cleanLines, "\n")
164131
}

cmd/main.go

Lines changed: 48 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,40 @@ package cmd
22

33
import (
44
"fmt"
5-
"github.com/prime-run/go-typer/ui"
6-
"github.com/spf13/cobra"
75
"os"
86
"path/filepath"
97
"strings"
8+
9+
devlog "github.com/prime-run/go-typer/log"
10+
"github.com/prime-run/go-typer/ui"
11+
"github.com/prime-run/go-typer/utils"
12+
"github.com/spf13/cobra"
1013
)
1114

12-
var cursorType string
13-
var themeName string
14-
var listThemes bool
15-
var debugMode bool
15+
var (
16+
cursorType string // Cursor type (block or underline)
17+
themeName string // Theme name or path to custom theme file
18+
listThemes bool // List available themes and exit
19+
debugMode bool // Enable debug mode for performance analysis
20+
customText string // Custom text to display in the game
21+
filePath string // Custom text file to read from
22+
)
1623

1724
var startCmd = &cobra.Command{
1825
Use: "start",
1926
Short: "Start a new game",
20-
Long: `Start a new game of Go Typer. This command will initialize a new game session.`,
27+
Long: "Start a new game of Go Typer. This command will initialize a new game session.",
2128
Run: func(cmd *cobra.Command, args []string) {
29+
// check if the debug mode is enabled and set the log level accordingly
2230
if debugMode {
23-
ui.DebugEnabled = true
24-
ui.InitDebugLog()
25-
defer ui.CloseDebugLog()
31+
devlog.DebugEnabled = true
32+
devlog.InitLog()
33+
defer devlog.CloseLog()
2634

27-
cmd.Printf("Debug mode enabled, logging to %s\n", filepath.Join(getConfigDirPath(), "debug.log"))
35+
cmd.Printf("Debug mode enabled, logging to %s\n", filepath.Join(utils.GetConfigDirPath(), "debug.log"))
2836
}
2937

38+
// check if listThemes is enabled and list available themes
3039
if listThemes {
3140
themes := ui.ListAvailableThemes()
3241
fmt.Println("Available themes:")
@@ -36,91 +45,61 @@ var startCmd = &cobra.Command{
3645
return
3746
}
3847

48+
// check for theme name and load the theme
3949
if themeName != "" {
50+
fmt.Printf("Theme name provided: %s", themeName)
4051
if strings.HasPrefix(themeName, "-") {
4152
cmd.Printf("Warning: Invalid theme name '%s'. Theme names cannot start with '-'.\n", themeName)
4253
cmd.Println("Using saved settings")
43-
} else if isValidThemeName(themeName) {
54+
} else if utils.IsValidThemeName(themeName) {
4455
if err := ui.LoadTheme(themeName); err != nil {
4556
cmd.Printf("Warning: Could not load theme '%s': %v\n", themeName, err)
4657
cmd.Println("Using saved settings")
4758
} else {
4859
ui.UpdateStyles()
49-
cmd.Printf("Using theme: %s\n", getDisplayThemeName(themeName))
50-
60+
cmd.Printf("Using theme: %s\n", utils.GetDisplayThemeName(themeName))
5161
ui.CurrentSettings.ThemeName = themeName
5262
}
5363
} else {
5464
cmd.Printf("Warning: Invalid theme name '%s'. Using saved settings.\n", themeName)
5565
}
5666
}
5767

68+
// check for custom text and set it
69+
if filePath != "" {
70+
data, err := os.ReadFile(filePath)
71+
if err != nil {
72+
cmd.Printf("Warning: Could not read custom text file '%s': %v\n", filePath, err)
73+
} else {
74+
customText = string(data)
75+
}
76+
}
77+
78+
// check for cursor type and set it
5879
if cursorType != "" {
5980
ui.CurrentSettings.CursorType = cursorType
6081
}
6182

83+
// apply settings and start loading
6284
ui.ApplySettings()
63-
64-
ui.StartLoadingWithOptions(ui.CurrentSettings.CursorType)
85+
ui.StartLoadingWithOptions(ui.CurrentSettings.CursorType, customText)
6586
},
6687
}
6788

68-
func getConfigDirPath() string {
69-
configDir, err := ui.GetConfigDir()
70-
if err != nil {
71-
return os.TempDir()
72-
}
73-
return configDir
74-
}
75-
76-
func isValidThemeName(name string) bool {
77-
if strings.Contains(name, ".") && !strings.HasSuffix(name, ".yml") {
78-
return false
79-
}
80-
81-
if strings.Contains(name, "/") || strings.Contains(name, "\\") {
82-
_, err := os.Stat(name)
83-
return err == nil
84-
}
85-
86-
for _, c := range name {
87-
if !isValidThemeNameChar(c) {
88-
return false
89-
}
90-
}
91-
92-
return true
93-
}
94-
95-
func isValidThemeNameChar(c rune) bool {
96-
return (c >= 'a' && c <= 'z') ||
97-
(c >= 'A' && c <= 'Z') ||
98-
(c >= '0' && c <= '9') ||
99-
c == '_' || c == '-'
100-
}
101-
102-
func getDisplayThemeName(themeName string) string {
103-
if strings.Contains(themeName, "/") || strings.Contains(themeName, "\\") {
104-
themeName = filepath.Base(themeName)
105-
}
106-
107-
themeName = strings.TrimSuffix(themeName, ".yml")
108-
109-
words := strings.Split(themeName, "_")
110-
for i, word := range words {
111-
if len(word) > 0 {
112-
words[i] = strings.ToUpper(word[0:1]) + word[1:]
113-
}
114-
}
115-
116-
return strings.Join(words, " ")
117-
}
118-
11989
func init() {
120-
rootCmd.AddCommand(startCmd)
121-
startCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
90+
// Cursor and theme configuration
12291
startCmd.Flags().StringVarP(&cursorType, "cursor", "c", "block", "Cursor type (block or underline)")
12392
startCmd.Flags().StringVarP(&themeName, "theme", "t", "", "Theme name or path to custom theme file (default: default)")
12493
startCmd.Flags().BoolVar(&listThemes, "list-themes", false, "List available themes and exit")
94+
95+
// Debug and logging options
96+
startCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
12597
startCmd.Flags().BoolVar(&debugMode, "debug", false, "Enable debug mode for performance analysis")
98+
99+
// Custom text option
100+
startCmd.Flags().StringVarP(&customText, "text", "x", "", "Custom text to display in the game (default: random text)")
101+
startCmd.Flags().StringVarP(&filePath, "file", "f", "", "Custom text file to read from")
102+
103+
// Add command to root
104+
rootCmd.AddCommand(startCmd)
126105
}

cmd/root.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ func Execute() {
2828

2929
func init() {
3030
ui.InitSettings()
31-
32-
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
31+
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
3332
}

0 commit comments

Comments
 (0)