# Directory Structure
```
├── .editorconfig
├── .github
│ └── workflows
│ └── ci.yml
├── .gitignore
├── .golangci.yml
├── build
│ ├── charcount
│ ├── mozisu-mcp-server
│ └── webserver
├── cmd
│ ├── charcount
│ │ ├── charcount
│ │ ├── main.go
│ │ └── output.txt
│ ├── direct_test
│ │ └── main.go
│ ├── mcpserver
│ │ └── main.go
│ ├── test
│ │ ├── mcp_client.go
│ │ └── test_client.go
│ └── webserver
│ ├── main.go
│ └── webserver
├── configs
│ └── config.json
├── go.mod
├── go.sum
├── image-1.png
├── image.png
├── internal
│ └── server
│ └── server.go
├── LICENSE
├── main_test.go
├── main.go
├── pkg
│ └── charcount
│ ├── charcount_test.go
│ └── charcount.go
├── README.md
├── scripts
│ └── build.sh
├── Taskfile.yml
└── test
└── integration_test.go
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | bin/
8 |
9 | # Test binary, built with `go test -c`
10 | *.test
11 |
12 | # Output of the go coverage tool
13 | *.out
14 |
15 | # Dependency directories
16 | vendor/
17 |
18 | # Go workspace file
19 | go.work
20 |
21 | # IDE specific files
22 | .idea/
23 | .vscode/
24 | *.swp
25 | *.swo
26 |
27 | # OS specific files
28 | .DS_Store
29 | Thumbs.db
30 |
31 | # Build output
32 | /charcount
33 | /webserver
34 | /mozisu-mcp-server
35 |
36 | # Debug files
37 | __debug_bin
38 |
```
--------------------------------------------------------------------------------
/cmd/charcount/output.txt:
--------------------------------------------------------------------------------
```
1 | DEBUG: Starting character count
2 |
3 | Text: hello
4 | Total characters: 5
5 | Non-whitespace characters: 5
6 | ----------------------------
7 | DEBUG: Completed character count for 'hello'
8 |
```
--------------------------------------------------------------------------------
/configs/config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "server": {
3 | "mcp": {
4 | "name": "Mozisu MCP Server",
5 | "version": "1.0.0"
6 | },
7 | "web": {
8 | "port": 8080,
9 | "timeouts": {
10 | "read": 10,
11 | "write": 10,
12 | "idle": 120
13 | }
14 | }
15 | },
16 | "features": {
17 | "debug": false,
18 | "logging": true
19 | }
20 | }
21 |
```
--------------------------------------------------------------------------------
/pkg/charcount/charcount.go:
--------------------------------------------------------------------------------
```go
1 | // Package charcount provides functionality for counting characters in text
2 | package charcount
3 |
4 | import (
5 | "unicode"
6 | )
7 |
8 | // Result represents the result of a character count operation
9 | type Result struct {
10 | Text string
11 | TotalCount int
12 | NonWhitespaceCount int
13 | }
14 |
15 | // Count counts the total and non-whitespace characters in the given text
16 | // It properly handles multi-byte characters like Japanese text and emojis
17 | func Count(text string) Result {
18 | // Count total characters (including spaces)
19 | totalCount := len([]rune(text))
20 |
21 | // Count non-whitespace characters
22 | nonWhitespaceCount := 0
23 | for _, r := range text {
24 | if !unicode.IsSpace(r) {
25 | nonWhitespaceCount++
26 | }
27 | }
28 |
29 | return Result{
30 | Text: text,
31 | TotalCount: totalCount,
32 | NonWhitespaceCount: nonWhitespaceCount,
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/cmd/direct_test/main.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "unicode"
6 | )
7 |
8 | func main() {
9 | // Test cases
10 | testCases := []string{
11 | "Hello, World!",
12 | "こんにちは世界!",
13 | "Hello 世界 😊🚀",
14 | "スペースを 含む 日本語 テキスト with English and 絵文字😊",
15 | "1234567890",
16 | " Spaces at the beginning and end ",
17 | "", // Empty string
18 | }
19 |
20 | fmt.Println("Character Count Test Results:")
21 | fmt.Println("============================")
22 |
23 | for _, text := range testCases {
24 | // Count total characters (including spaces)
25 | totalCount := len([]rune(text))
26 |
27 | // Count non-whitespace characters
28 | nonWhitespaceCount := 0
29 | for _, r := range text {
30 | if !unicode.IsSpace(r) {
31 | nonWhitespaceCount++
32 | }
33 | }
34 |
35 | fmt.Printf("\nText: %s\n", text)
36 | fmt.Printf("Total characters: %d\n", totalCount)
37 | fmt.Printf("Non-whitespace characters: %d\n", nonWhitespaceCount)
38 | fmt.Println("----------------------------")
39 | }
40 |
41 | fmt.Println("\nTest completed successfully!")
42 | }
43 |
```
--------------------------------------------------------------------------------
/test/integration_test.go:
--------------------------------------------------------------------------------
```go
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/Atotti/mozisu-mcp-server/pkg/charcount"
7 | )
8 |
9 | // TestIntegration tests the integration between different components
10 | func TestIntegration(t *testing.T) {
11 | // テストケース
12 | testCases := []struct {
13 | name string
14 | input string
15 | expectedTotal int
16 | expectedNonWhitespace int
17 | }{
18 | {
19 | name: "Integration test - ASCII",
20 | input: "Hello, World!",
21 | expectedTotal: 13,
22 | expectedNonWhitespace: 12,
23 | },
24 | {
25 | name: "Integration test - Japanese",
26 | input: "こんにちは世界!",
27 | expectedTotal: 8,
28 | expectedNonWhitespace: 8,
29 | },
30 | }
31 |
32 | for _, tc := range testCases {
33 | t.Run(tc.name, func(t *testing.T) {
34 | // 共通パッケージを使用して文字数をカウント
35 | result := charcount.Count(tc.input)
36 |
37 | // 結果を検証
38 | if result.TotalCount != tc.expectedTotal {
39 | t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
40 | }
41 | if result.NonWhitespaceCount != tc.expectedNonWhitespace {
42 | t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
43 | }
44 | })
45 | }
46 | }
47 |
```
--------------------------------------------------------------------------------
/pkg/charcount/charcount_test.go:
--------------------------------------------------------------------------------
```go
1 | package charcount
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestCount(t *testing.T) {
8 | // テストケース
9 | testCases := []struct {
10 | name string
11 | input string
12 | expectedTotal int
13 | expectedNonWhitespace int
14 | }{
15 | {
16 | name: "ASCII only",
17 | input: "Hello, World!",
18 | expectedTotal: 13,
19 | expectedNonWhitespace: 12,
20 | },
21 | {
22 | name: "Japanese characters",
23 | input: "こんにちは世界!",
24 | expectedTotal: 8,
25 | expectedNonWhitespace: 8,
26 | },
27 | {
28 | name: "Mixed with emojis",
29 | input: "Hello 世界 😊🚀",
30 | expectedTotal: 11,
31 | expectedNonWhitespace: 9,
32 | },
33 | {
34 | name: "Empty string",
35 | input: "",
36 | expectedTotal: 0,
37 | expectedNonWhitespace: 0,
38 | },
39 | {
40 | name: "Whitespace only",
41 | input: " \t\n",
42 | expectedTotal: 5,
43 | expectedNonWhitespace: 0,
44 | },
45 | }
46 |
47 | // 各テストケースを実行
48 | for _, tc := range testCases {
49 | t.Run(tc.name, func(t *testing.T) {
50 | // 文字数をカウント
51 | result := Count(tc.input)
52 |
53 | // 結果を検証
54 | if result.TotalCount != tc.expectedTotal {
55 | t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
56 | }
57 | if result.NonWhitespaceCount != tc.expectedNonWhitespace {
58 | t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
59 | }
60 | })
61 | }
62 | }
63 |
```
--------------------------------------------------------------------------------
/cmd/charcount/main.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "flag"
6 | "fmt"
7 | "os"
8 | "strings"
9 |
10 | "github.com/Atotti/mozisu-mcp-server/pkg/charcount"
11 | )
12 |
13 | func main() {
14 | // Parse command-line flags
15 | interactive := flag.Bool("i", false, "Run in interactive mode")
16 | flag.Parse()
17 |
18 | // Get the text to count
19 | var text string
20 |
21 | if *interactive {
22 | // Interactive mode
23 | fmt.Println("Character Count Tool")
24 | fmt.Println("===================")
25 | fmt.Println("Enter text to count characters (Ctrl+D to exit):")
26 |
27 | scanner := bufio.NewScanner(os.Stdin)
28 | for {
29 | fmt.Print("> ")
30 | if !scanner.Scan() {
31 | break
32 | }
33 |
34 | text = scanner.Text()
35 | printCharacterCounts(text)
36 | }
37 |
38 | if err := scanner.Err(); err != nil {
39 | fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
40 | os.Exit(1)
41 | }
42 | } else {
43 | // Command-line mode
44 | args := flag.Args()
45 | if len(args) == 0 {
46 | fmt.Println("Please provide text to count characters.")
47 | fmt.Println("Usage: charcount [text] or charcount -i for interactive mode")
48 | os.Exit(1)
49 | }
50 |
51 | text = strings.Join(args, " ")
52 | printCharacterCounts(text)
53 | }
54 | }
55 |
56 | func printCharacterCounts(text string) {
57 | fmt.Println("DEBUG: Starting character count")
58 |
59 | // 共通パッケージを使用して文字数をカウント
60 | result := charcount.Count(text)
61 |
62 | fmt.Printf("\nText: %s\n", result.Text)
63 | fmt.Printf("Total characters: %d\n", result.TotalCount)
64 | fmt.Printf("Non-whitespace characters: %d\n", result.NonWhitespaceCount)
65 | fmt.Println("----------------------------")
66 |
67 | // Write to stderr as well for debugging
68 | os.Stderr.WriteString(fmt.Sprintf("DEBUG: Completed character count for '%s'\n", text))
69 | }
70 |
```
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/Atotti/mozisu-mcp-server/pkg/charcount"
7 | )
8 |
9 | // TestCountCharacters tests the character counting functionality
10 | func TestCountCharacters(t *testing.T) {
11 | // テストケース
12 | testCases := []struct {
13 | name string
14 | input string
15 | expectedTotal int
16 | expectedNonWhitespace int
17 | }{
18 | {
19 | name: "ASCII only",
20 | input: "Hello, World!",
21 | expectedTotal: 13,
22 | expectedNonWhitespace: 12,
23 | },
24 | {
25 | name: "Japanese characters",
26 | input: "こんにちは世界!",
27 | expectedTotal: 8,
28 | expectedNonWhitespace: 8,
29 | },
30 | {
31 | name: "Mixed with emojis",
32 | input: "Hello 世界 😊🚀",
33 | expectedTotal: 11,
34 | expectedNonWhitespace: 9,
35 | },
36 | {
37 | name: "Empty string",
38 | input: "",
39 | expectedTotal: 0,
40 | expectedNonWhitespace: 0,
41 | },
42 | {
43 | name: "Whitespace only",
44 | input: " \t\n",
45 | expectedTotal: 5,
46 | expectedNonWhitespace: 0,
47 | },
48 | }
49 |
50 | // 各テストケースを実行
51 | for _, tc := range testCases {
52 | t.Run(tc.name, func(t *testing.T) {
53 | // 共通パッケージを使用して文字数をカウント
54 | result := charcount.Count(tc.input)
55 |
56 | // 結果を検証
57 | if result.TotalCount != tc.expectedTotal {
58 | t.Errorf("Expected total count %d, got %d", tc.expectedTotal, result.TotalCount)
59 | }
60 | if result.NonWhitespaceCount != tc.expectedNonWhitespace {
61 | t.Errorf("Expected non-whitespace count %d, got %d", tc.expectedNonWhitespace, result.NonWhitespaceCount)
62 | }
63 | })
64 | }
65 | }
66 |
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | lint:
11 | name: Lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v3
16 |
17 | - name: Set up Go
18 | uses: actions/setup-go@v4
19 | with:
20 | go-version: '1.23'
21 | cache: true
22 |
23 | - name: Install golangci-lint
24 | run: |
25 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
26 |
27 | - name: Install Task
28 | run: |
29 | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
30 | echo "$HOME/.local/bin" >> $GITHUB_PATH
31 |
32 | - name: Run linter
33 | run: task lint
34 |
35 | test:
36 | name: Test
37 | runs-on: ubuntu-latest
38 | strategy:
39 | matrix:
40 | go-version: ['1.21', '1.22', '1.23']
41 | steps:
42 | - name: Checkout code
43 | uses: actions/checkout@v3
44 |
45 | - name: Set up Go ${{ matrix.go-version }}
46 | uses: actions/setup-go@v4
47 | with:
48 | go-version: ${{ matrix.go-version }}
49 | cache: true
50 |
51 | - name: Install Task
52 | run: |
53 | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
54 | echo "$HOME/.local/bin" >> $GITHUB_PATH
55 |
56 | - name: Run tests
57 | run: task test
58 |
59 | build:
60 | name: Build
61 | runs-on: ubuntu-latest
62 | needs: [lint, test]
63 | steps:
64 | - name: Checkout code
65 | uses: actions/checkout@v3
66 |
67 | - name: Set up Go
68 | uses: actions/setup-go@v4
69 | with:
70 | go-version: '1.23'
71 | cache: true
72 |
73 | - name: Install Task
74 | run: |
75 | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
76 | echo "$HOME/.local/bin" >> $GITHUB_PATH
77 |
78 | - name: Build applications
79 | run: task build
80 |
81 | - name: Upload artifacts
82 | uses: actions/upload-artifact@v3
83 | with:
84 | name: binaries
85 | path: bin/
86 |
```
--------------------------------------------------------------------------------
/cmd/test/test_client.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os/exec"
7 | )
8 |
9 | // JSON-RPC request structure
10 | type Request struct {
11 | JsonRPC string `json:"jsonrpc"`
12 | ID int `json:"id"`
13 | Method string `json:"method"`
14 | Params interface{} `json:"params"`
15 | }
16 |
17 | // CallToolRequest parameters
18 | type CallToolParams struct {
19 | Name string `json:"name"`
20 | Arguments map[string]interface{} `json:"arguments"`
21 | }
22 |
23 | func RunTest() {
24 | // Start the MCP server as a subprocess
25 | cmd := exec.Command("go", "run", "../../main.go")
26 |
27 | // Set up pipes for stdin and stdout
28 | stdin, err := cmd.StdinPipe()
29 | if err != nil {
30 | fmt.Println("Error creating stdin pipe:", err)
31 | return
32 | }
33 |
34 | stdout, err := cmd.StdoutPipe()
35 | if err != nil {
36 | fmt.Println("Error creating stdout pipe:", err)
37 | return
38 | }
39 |
40 | // Start the server
41 | if err := cmd.Start(); err != nil {
42 | fmt.Println("Error starting server:", err)
43 | return
44 | }
45 |
46 | // Create a request to call the count_characters tool
47 | request := Request{
48 | JsonRPC: "2.0",
49 | ID: 1,
50 | Method: "call_tool",
51 | Params: CallToolParams{
52 | Name: "count_characters",
53 | Arguments: map[string]interface{}{
54 | "text": "こんにちは世界!Hello World! 😊🚀",
55 | },
56 | },
57 | }
58 |
59 | // Marshal the request to JSON
60 | requestJSON, err := json.Marshal(request)
61 | if err != nil {
62 | fmt.Println("Error marshaling request:", err)
63 | return
64 | }
65 |
66 | // Add a newline to the request
67 | requestJSON = append(requestJSON, '\n')
68 |
69 | // Send the request to the server
70 | _, err = stdin.Write(requestJSON)
71 | if err != nil {
72 | fmt.Println("Error writing to stdin:", err)
73 | return
74 | }
75 |
76 | // Read the response
77 | buf := make([]byte, 4096)
78 | n, err := stdout.Read(buf)
79 | if err != nil {
80 | fmt.Println("Error reading from stdout:", err)
81 | return
82 | }
83 |
84 | // Print the response
85 | fmt.Println("Response from server:")
86 | fmt.Println(string(buf[:n]))
87 |
88 | // Test with another example
89 | request.ID = 2
90 | request.Method = "call_tool"
91 | request.Params = CallToolParams{
92 | Name: "count_characters",
93 | Arguments: map[string]interface{}{
94 | "text": "スペースを 含む 日本語 テキスト with English and 絵文字😊",
95 | },
96 | }
97 |
98 | // Marshal the request to JSON
99 | requestJSON, err = json.Marshal(request)
100 | if err != nil {
101 | fmt.Println("Error marshaling request:", err)
102 | return
103 | }
104 |
105 | // Add a newline to the request
106 | requestJSON = append(requestJSON, '\n')
107 |
108 | // Send the request to the server
109 | _, err = stdin.Write(requestJSON)
110 | if err != nil {
111 | fmt.Println("Error writing to stdin:", err)
112 | return
113 | }
114 |
115 | // Read the response
116 | n, err = stdout.Read(buf)
117 | if err != nil {
118 | fmt.Println("Error reading from stdout:", err)
119 | return
120 | }
121 |
122 | // Print the response
123 | fmt.Println("\nResponse from server (second test):")
124 | fmt.Println(string(buf[:n]))
125 |
126 | // Kill the server process
127 | cmd.Process.Kill()
128 | }
129 |
```
--------------------------------------------------------------------------------
/cmd/test/mcp_client.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "os/exec"
9 | "time"
10 | )
11 |
12 | // MCP protocol request
13 | type MCPRequest struct {
14 | JsonRPC string `json:"jsonrpc"`
15 | ID int `json:"id"`
16 | Method string `json:"method"`
17 | Params interface{} `json:"params"`
18 | }
19 |
20 | // MCP protocol response
21 | type MCPResponse struct {
22 | JsonRPC string `json:"jsonrpc"`
23 | ID int `json:"id"`
24 | Result json.RawMessage `json:"result,omitempty"`
25 | Error *struct {
26 | Code int `json:"code"`
27 | Message string `json:"message"`
28 | } `json:"error,omitempty"`
29 | }
30 |
31 | func main() {
32 | fmt.Println("Starting MCP protocol test...")
33 |
34 | // Start the MCP server
35 | cmd := exec.Command("go", "run", "../../main.go")
36 |
37 | stdin, _ := cmd.StdinPipe()
38 | stdout, _ := cmd.StdoutPipe()
39 |
40 | cmd.Start()
41 |
42 | // Give the server a moment to start up
43 | time.Sleep(500 * time.Millisecond)
44 |
45 | // Create a reader for the stdout
46 | reader := bufio.NewReader(stdout)
47 |
48 | // First, try to list available tools
49 | listToolsRequest := MCPRequest{
50 | JsonRPC: "2.0",
51 | ID: 1,
52 | Method: "listTools",
53 | Params: struct{}{},
54 | }
55 |
56 | // Send the request
57 | requestJSON, _ := json.Marshal(listToolsRequest)
58 | fmt.Printf("Request: %s\n", requestJSON)
59 | io.WriteString(stdin, string(requestJSON)+"\n")
60 |
61 | // Read the response
62 | responseStr, _ := reader.ReadString('\n')
63 | fmt.Printf("Response: %s\n\n", responseStr)
64 |
65 | // Try to parse the response
66 | var response MCPResponse
67 | json.Unmarshal([]byte(responseStr), &response)
68 |
69 | // If we got an error, try some other common method names
70 | if response.Error != nil {
71 | methods := []string{
72 | "list_tools",
73 | "tools.list",
74 | "get_tools",
75 | "tools",
76 | }
77 |
78 | for i, method := range methods {
79 | listToolsRequest.ID = i + 2
80 | listToolsRequest.Method = method
81 |
82 | requestJSON, _ := json.Marshal(listToolsRequest)
83 | fmt.Printf("Trying method: %s\n", method)
84 | fmt.Printf("Request: %s\n", requestJSON)
85 |
86 | io.WriteString(stdin, string(requestJSON)+"\n")
87 | responseStr, _ := reader.ReadString('\n')
88 | fmt.Printf("Response: %s\n\n", responseStr)
89 | }
90 | }
91 |
92 | // Now try to call our count_characters tool directly
93 | callToolRequest := MCPRequest{
94 | JsonRPC: "2.0",
95 | ID: 10,
96 | Method: "callTool",
97 | Params: map[string]interface{}{
98 | "name": "count_characters",
99 | "arguments": map[string]interface{}{
100 | "text": "こんにちは世界!Hello World! 😊🚀",
101 | },
102 | },
103 | }
104 |
105 | // Try different method names for calling a tool
106 | callMethods := []string{
107 | "callTool",
108 | "call_tool",
109 | "tool.call",
110 | "execute",
111 | "run",
112 | }
113 |
114 | for i, method := range callMethods {
115 | callToolRequest.ID = 10 + i
116 | callToolRequest.Method = method
117 |
118 | requestJSON, _ := json.Marshal(callToolRequest)
119 | fmt.Printf("Trying method: %s\n", method)
120 | fmt.Printf("Request: %s\n", requestJSON)
121 |
122 | io.WriteString(stdin, string(requestJSON)+"\n")
123 | responseStr, _ := reader.ReadString('\n')
124 | fmt.Printf("Response: %s\n\n", responseStr)
125 | }
126 |
127 | // Kill the server
128 | cmd.Process.Kill()
129 | fmt.Println("Test completed.")
130 | }
131 |
```
--------------------------------------------------------------------------------
/cmd/webserver/main.go:
--------------------------------------------------------------------------------
```go
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net/http"
7 |
8 | "github.com/Atotti/mozisu-mcp-server/internal/server"
9 | "github.com/Atotti/mozisu-mcp-server/pkg/charcount"
10 | )
11 |
12 | func main() {
13 | // Define HTTP handlers
14 | mux := http.NewServeMux()
15 | mux.HandleFunc("/", handleHome)
16 | mux.HandleFunc("/count", handleCount)
17 |
18 | // サーバー設定
19 | config := server.DefaultConfig()
20 | config.Port = 8080
21 |
22 | // サーバーを起動
23 | log.Println("Starting character count web server...")
24 | if err := server.RunHTTPServer(mux, config); err != nil {
25 | log.Fatalf("Server error: %v\n", err)
26 | }
27 | }
28 |
29 | // handleHome serves the home page with a simple form
30 | func handleHome(w http.ResponseWriter, r *http.Request) {
31 | html := `
32 | <!DOCTYPE html>
33 | <html>
34 | <head>
35 | <title>Character Count Tool</title>
36 | <style>
37 | body {
38 | font-family: Arial, sans-serif;
39 | max-width: 800px;
40 | margin: 0 auto;
41 | padding: 20px;
42 | }
43 | textarea {
44 | width: 100%;
45 | height: 150px;
46 | margin-bottom: 10px;
47 | }
48 | button {
49 | padding: 8px 16px;
50 | background-color: #4CAF50;
51 | color: white;
52 | border: none;
53 | cursor: pointer;
54 | }
55 | #result {
56 | margin-top: 20px;
57 | padding: 10px;
58 | border: 1px solid #ddd;
59 | border-radius: 4px;
60 | display: none;
61 | }
62 | </style>
63 | </head>
64 | <body>
65 | <h1>Character Count Tool</h1>
66 | <p>Enter text below to count characters:</p>
67 |
68 | <textarea id="text" placeholder="Enter text here..."></textarea>
69 | <button onclick="countCharacters()">Count Characters</button>
70 |
71 | <div id="result"></div>
72 |
73 | <script>
74 | function countCharacters() {
75 | const text = document.getElementById('text').value;
76 |
77 | fetch('/count', {
78 | method: 'POST',
79 | headers: {
80 | 'Content-Type': 'application/json',
81 | },
82 | body: JSON.stringify({ text: text }),
83 | })
84 | .then(response => response.json())
85 | .then(data => {
86 | const resultDiv = document.getElementById('result');
87 | resultDiv.innerHTML =
88 | '<h2>Results:</h2>' +
89 | '<p><strong>Text:</strong> ' + data.text + '</p>' +
90 | '<p><strong>Total characters:</strong> ' + data.totalCount + '</p>' +
91 | '<p><strong>Non-whitespace characters:</strong> ' + data.nonWhitespaceCount + '</p>';
92 | resultDiv.style.display = 'block';
93 | })
94 | .catch(error => {
95 | console.error('Error:', error);
96 | alert('An error occurred while counting characters.');
97 | });
98 | }
99 | </script>
100 | </body>
101 | </html>
102 | `
103 | w.Header().Set("Content-Type", "text/html")
104 | if _, err := w.Write([]byte(html)); err != nil {
105 | log.Printf("Error writing response: %v", err)
106 | }
107 | }
108 |
109 | // handleCount processes the character count request
110 | func handleCount(w http.ResponseWriter, r *http.Request) {
111 | // Only allow POST requests
112 | if r.Method != http.MethodPost {
113 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
114 | return
115 | }
116 |
117 | // Parse the request body
118 | var request struct {
119 | Text string `json:"text"`
120 | }
121 |
122 | decoder := json.NewDecoder(r.Body)
123 | if err := decoder.Decode(&request); err != nil {
124 | http.Error(w, "Invalid request body", http.StatusBadRequest)
125 | return
126 | }
127 |
128 | // 共通パッケージを使用して文字数をカウント
129 | result := charcount.Count(request.Text)
130 |
131 | // Create the response
132 | response := struct {
133 | Text string `json:"text"`
134 | TotalCount int `json:"totalCount"`
135 | NonWhitespaceCount int `json:"nonWhitespaceCount"`
136 | }{
137 | Text: result.Text,
138 | TotalCount: result.TotalCount,
139 | NonWhitespaceCount: result.NonWhitespaceCount,
140 | }
141 |
142 | // Send the response
143 | w.Header().Set("Content-Type", "application/json")
144 | if err := json.NewEncoder(w).Encode(response); err != nil {
145 | log.Printf("Error encoding JSON response: %v", err)
146 | }
147 | }
148 |
```