Skip to content

Commit 6c9d419

Browse files
authored
Merge pull request #26 from 2222-42/feat/issue-#25
feat: Implement interactive prompting for `add-bib`, `add-review`, and `update-review` commands.
2 parents d3cbabe + 022d231 commit 6c9d419

File tree

6 files changed

+160
-14
lines changed

6 files changed

+160
-14
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ go.work.sum
3131
# .idea/
3232
# .vscode/
3333
.go-version
34-
biblog
34+
/biblog

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ The system is built using Go and follows Domain-Driven Design (DDD) principles.
2727

2828
The system provides a CLI for managing bibliographies and classifications.
2929

30+
### Interactive Mode
31+
32+
Commands `add-bib`, `add-review`, and `update-review` support interactive mode. If you omit the required flags, the CLI will prompt you for input.
33+
34+
3035
### 1. Add a Classification
3136

3237
Before adding a bibliography, you must ensure the classification exists.
@@ -217,6 +222,8 @@ go test ./internal/...
217222
The data is stored in CSV files in the `data/` directory:
218223
- `data/bibliographies.csv`: Stores bibliography entries.
219224
- `data/classifications.csv`: Stores classification codes.
225+
- `data/reviews.csv`: Stores reviews for bibliographies.
226+
220227

221228
## Performance Limitations
222229

cmd/biblog/interactive.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
"strconv"
9+
"strings"
10+
"sync"
11+
)
12+
13+
var (
14+
reader *bufio.Reader
15+
readerOnce sync.Once
16+
)
17+
18+
func getReader() *bufio.Reader {
19+
readerOnce.Do(func() {
20+
reader = bufio.NewReader(os.Stdin)
21+
})
22+
return reader
23+
}
24+
25+
// promptString prompts the user for a string input.
26+
// If required is true, it loops until a non-empty string is provided.
27+
func promptString(label string, required bool) string {
28+
r := getReader()
29+
for {
30+
if required {
31+
fmt.Printf("%s (*required): ", label)
32+
} else {
33+
fmt.Printf("%s (optional): ", label)
34+
}
35+
input, err := r.ReadString('\n')
36+
if err != nil {
37+
if err == io.EOF {
38+
// If we got some input before EOF, use it
39+
if strings.TrimSpace(input) != "" {
40+
fmt.Println() // Add newline for cleaner output
41+
return strings.TrimSpace(input)
42+
}
43+
// If EOF with no input and optional, return empty
44+
if !required {
45+
return ""
46+
}
47+
// If EOF with no input and required, exit with error
48+
fmt.Printf("\nError: required input for '%s' not provided before EOF\n", label)
49+
os.Exit(1)
50+
}
51+
fmt.Printf("\nError reading input: %v\n", err)
52+
os.Exit(1)
53+
}
54+
input = strings.TrimSpace(input)
55+
if input != "" {
56+
return input
57+
}
58+
if !required {
59+
return ""
60+
}
61+
}
62+
}
63+
64+
// promptInt prompts the user for an integer input.
65+
// If required is true, it loops until a valid integer is provided.
66+
// If not required and input is empty, it returns 0.
67+
func promptInt(label string, required bool) int {
68+
r := getReader()
69+
for {
70+
if required {
71+
fmt.Printf("%s (*required): ", label)
72+
} else {
73+
fmt.Printf("%s (optional): ", label)
74+
}
75+
input, err := r.ReadString('\n')
76+
if err != nil {
77+
if err == io.EOF {
78+
if strings.TrimSpace(input) != "" {
79+
fmt.Println()
80+
val, convErr := strconv.Atoi(strings.TrimSpace(input))
81+
if convErr == nil {
82+
return val
83+
}
84+
}
85+
if !required {
86+
return 0
87+
}
88+
// If required and EOF with no valid input, exit with error
89+
fmt.Printf("\nError: required integer input for '%s' but reached end of input.\n", label)
90+
os.Exit(1)
91+
}
92+
fmt.Printf("\nError reading input: %v\n", err)
93+
os.Exit(1)
94+
}
95+
input = strings.TrimSpace(input)
96+
if input == "" && !required {
97+
return 0
98+
}
99+
val, err := strconv.Atoi(input)
100+
if err == nil {
101+
return val
102+
}
103+
fmt.Println("Invalid number, please try again.")
104+
}
105+
}

cmd/biblog/main.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,30 @@ func main() {
7171
case "add-bib":
7272
_ = addBibCmd.Parse(os.Args[2:])
7373
if *addBibTitle == "" || *addBibAuthor == "" || *addBibType == "" || *addBibClass == 0 || *addBibYear == 0 {
74-
fmt.Println("Please provide required fields: -title, -author, -type, -class, -year")
75-
addBibCmd.PrintDefaults()
76-
os.Exit(1)
74+
if *addBibTitle == "" {
75+
*addBibTitle = promptString("Title", true)
76+
}
77+
if *addBibAuthor == "" {
78+
*addBibAuthor = promptString("Author", true)
79+
}
80+
if *addBibPublisher == "" {
81+
*addBibPublisher = promptString("Publisher", false)
82+
}
83+
if *addBibType == "" {
84+
*addBibType = promptString("Type", true)
85+
}
86+
if *addBibClass == 0 {
87+
*addBibClass = promptInt("Classification Code Number", true)
88+
}
89+
if *addBibYear == 0 {
90+
*addBibYear = promptInt("Published Year", true)
91+
}
92+
if *addBibISBN == "" {
93+
*addBibISBN = promptString("ISBN", false)
94+
}
95+
if *addBibIndex == "" {
96+
*addBibIndex = promptString("BibIndex", false)
97+
}
7798
}
7899
// Construct date from year
79100
publishedDate := time.Date(*addBibYear, 1, 1, 0, 0, 0, 0, time.UTC)
@@ -88,9 +109,15 @@ func main() {
88109
case "add-review":
89110
_ = addReviewCmd.Parse(os.Args[2:])
90111
if *addReviewBibIndex == "" || *addReviewGoals == "" {
91-
fmt.Println("Please provide required fields: -bib-index, -goals")
92-
addReviewCmd.PrintDefaults()
93-
os.Exit(1)
112+
if *addReviewBibIndex == "" {
113+
*addReviewBibIndex = promptString("BibIndex", true)
114+
}
115+
if *addReviewGoals == "" {
116+
*addReviewGoals = promptString("Goals", true)
117+
}
118+
if *addReviewSummary == "" {
119+
*addReviewSummary = promptString("Summary", false)
120+
}
94121
}
95122

96123
// Resolve BibIndex to ID efficiently
@@ -114,9 +141,16 @@ func main() {
114141
case "update-review":
115142
_ = updateReviewCmd.Parse(os.Args[2:])
116143
if *updateReviewID == "" {
117-
fmt.Println("Please provide required field: -review-id")
118-
updateReviewCmd.PrintDefaults()
119-
os.Exit(1)
144+
*updateReviewID = promptString("Review UUID", true)
145+
}
146+
if *updateReviewGoals == "" && *updateReviewSummary == "" {
147+
// If neither is provided via flags, prompt for them
148+
if *updateReviewGoals == "" {
149+
*updateReviewGoals = promptString("New goals", false)
150+
}
151+
if *updateReviewSummary == "" {
152+
*updateReviewSummary = promptString("New summary", false)
153+
}
120154
}
121155

122156
// Parse UUID
@@ -136,10 +170,10 @@ func main() {
136170
summary = updateReviewSummary
137171
}
138172

139-
// Validate at least one field is provided
173+
// Validate at least one field is provided (after prompting)
140174
if goals == nil && summary == nil {
141175
fmt.Println("Please provide at least one field to update: -goals or -summary")
142-
updateReviewCmd.PrintDefaults()
176+
// If interactive mode was used, they might have skipped both optional fields
143177
os.Exit(1)
144178
}
145179

data/bibliographies.csv

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
ID,BibIndex,Code,Type,Title,Author,Publisher,ISBN,PublishedDate
2-
f792718c-c789-48b8-8d89-0d8650d4fe35,B56EE03DDD,B56,Book,Domain Driven Design,Eric Evans,Addison-Wesley,978-0321125217,2003-01-01T00:00:00Z
32
d52ee692-f8ee-49fb-ad30-4e3da68059a8,B16MS24MM,B16,Book,マネジメント神話 現代ビジネス哲学の真実に迫る,マシュー スチュワート(稲岡大志訳),,978-4750356884,2024-01-01T00:00:00Z
4-
f2978362-43da-4465-862e-a638a3c0598e,B56TA24TB,B56,Book,Test Book,Test Author,Test Publisher,,2024-01-01T00:00:00Z
3+
b91f1280-2a4c-4707-be97-0bffac6db35b,B56SK24DMD,B56,Book,データモデリングでドメインを駆動する,杉本啓,技術評論社,,2024-01-01T00:00:00Z

data/reviews.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
ID,BookID,Goals,Summary,CreatedAt,UpdatedAt
22
2d8a26ef-64e4-4718-b913-085fef527d71,d52ee692-f8ee-49fb-ad30-4e3da68059a8,現代ビジネスの哲学・思想を知ることで、自分の仕事と哲学の折り合いを見つける,80年代〜90年代の米国コンサルティングの筆者の体験と、4人のマネジメント思想家の話を織り交ぜ、哲学としてのマネジメント教育の可能性を広げようとしつつ、そのことに対してメタ批判をしている。また、筆者のその後のアメリカ歴史研究を踏まえると、マネジメントに意味がないという主張よりかは、マネジメントに意味をもたせるアメリカとその歴史に注目してそうである。【更新後のサマリー】,2025-11-22T19:52:17+09:00,2025-11-23T04:48:04+09:00
3+
c80b53e4-6036-401b-8104-f632896f7c73,b91f1280-2a4c-4707-be97-0bffac6db35b,Domain Modelingについて学ぶ。設計について学ぶ。ドメインを駆動する力を得る。これらについて教える力を得る。世界を記述する方法を学ぶ。デザインの背後にある思考の言語化。,残をどうモデリングするか、というのが重要。分散・非同期・疎結合なシステムを作ることにより、弾性があり、かつ、可変性のあるもの、つまり、ビジネスの変化に対応し、必要な役割を果たすことができるようにする。,2025-11-23T07:49:03+09:00,2025-11-23T07:51:21+09:00

0 commit comments

Comments
 (0)