diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e243392 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + go-version: [ '1.19', '1.20', '1.21', '1.22' ] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-${{ matrix.go-version }}- + + - name: Download dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Build + run: go build -v . + + - name: Run go vet + run: go vet . + + - name: Run tests + run: go test -v -race -coverprofile=coverage.txt -covermode=atomic . + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.txt + flags: unittests + name: codecov-umbrella + continue-on-error: true + + lint: + name: Lint + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + + example: + name: Test Example + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Run CLI example + run: cd examples && go run demo.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18b66d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories +vendor/ + +# Go workspace file +go.work + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS specific files +.DS_Store +Thumbs.db + +# Build artifacts +bin/ +dist/ +build/ + +# Coverage files +coverage.txt +coverage.html +*.coverprofile diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b7c8dcd --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,33 @@ +linters-settings: + govet: + enable-all: true + gocyclo: + min-complexity: 15 + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - gofmt + - goimports + - misspell + - revive + - stylecheck + +run: + timeout: 5m + tests: true + +issues: + exclude-use-default: false + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..96fd5d0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Snider + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6e87cc6 --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +.PHONY: help build test test-coverage test-bench lint fmt vet clean example example-ui install-tools + +# Default target +help: + @echo "Available targets:" + @echo " make build - Build the project" + @echo " make test - Run tests" + @echo " make test-coverage - Run tests with coverage" + @echo " make test-bench - Run benchmark tests" + @echo " make lint - Run golangci-lint" + @echo " make fmt - Format code with gofmt" + @echo " make vet - Run go vet" + @echo " make clean - Clean build artifacts" + @echo " make example - Run the example program (CLI demo)" + @echo " make example-ui - Run the Wails UI example" + @echo " make install-tools - Install development tools" + +# Build the project +build: + @echo "Building..." + @go build -v . + +# Run tests +test: + @echo "Running tests..." + @go test -v -race . + +# Run tests with coverage +test-coverage: + @echo "Running tests with coverage..." + @go test -v -race -coverprofile=coverage.txt -covermode=atomic . + @go tool cover -html=coverage.txt -o coverage.html + @echo "Coverage report generated: coverage.html" + +# Run benchmark tests +test-bench: + @echo "Running benchmark tests..." + @go test -bench=. -benchmem . + +# Run golangci-lint (requires installation) +lint: + @echo "Running golangci-lint..." + @which golangci-lint > /dev/null || (echo "golangci-lint not installed. Run 'make install-tools'" && exit 1) + @golangci-lint run . + +# Format code +fmt: + @echo "Formatting code..." + @go fmt . + +# Run go vet +vet: + @echo "Running go vet..." + @go vet . + +# Clean build artifacts +clean: + @echo "Cleaning..." + @rm -f coverage.txt coverage.html + @go clean -cache -testcache -modcache + @rm -rf bin/ + +# Run the example program +example: + @echo "Running example..." + @cd examples && go run demo.go + +# Run the Wails UI example (requires Wails v3 with UI support) +example-ui: + @echo "Running Wails UI example..." + @cd examples && go run main.go + +# Install development tools +install-tools: + @echo "Installing development tools..." + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + @echo "Tools installed successfully" diff --git a/README.md b/README.md index e8217fe..7466d06 100644 --- a/README.md +++ b/README.md @@ -1 +1,272 @@ -# Core \ No newline at end of file +# Core + +[](https://golang.org/) +[](https://wails.io) +[](LICENSE) + +A Wails v3 service library starter project providing core utilities and helper functions. This library can be integrated into any Wails v3 application to provide backend functionality accessible from the frontend. + +## Features + +- ๐ Ready-to-use Wails v3 service structure +- โ Comprehensive test coverage +- ๐ Well-documented code with examples +- ๐ง Makefile for common development tasks +- ๐ฏ GitHub Actions CI/CD pipeline +- ๐ฆ Example Wails application included +- ๐ Interactive frontend demo + +## What is a Wails v3 Service? + +Wails v3 services are Go packages that can be registered with a Wails application and called from the frontend JavaScript/TypeScript code. This allows you to write backend logic in Go and expose it to your web-based UI seamlessly. + +## Installation + +```bash +go get github.com/Snider/Core +``` + +### Prerequisites + +- Go 1.19 or higher +- Wails v3 CLI (for running the example): + ```bash + go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.36 + ``` + +## Usage + +### Creating a New Wails Application + +If you're starting fresh, create a new Wails v3 application: + +```bash +# Install Wails v3 CLI (if not already installed) +go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.36 + +# Create a new Wails application +wails3 init -n myfirstapp + +# Navigate to your new project +cd myfirstapp +``` + +Then add this service library to your project: + +```bash +go get github.com/Snider/Core +``` + +### Integrating into Your Wails Application + +```go +package main + +import ( + "github.com/Snider/Core" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + // Create the Core service + coreService := core.NewCoreService() + + // Create Wails application with the service + app := application.New(application.Options{ + Name: "My App", + Description: "My Wails Application", + Services: []application.Service{ + application.NewService(coreService), + }, + }) + + // Create and run your application... + app.Run() +} +``` + +### Calling from Frontend + +Once registered, you can call the service methods from your frontend JavaScript: + +```javascript +// Get version +const version = await wails.Call.ByName('github.com.Snider.Core.GetVersion'); + +// Greet user +const greeting = await wails.Call.ByName('github.com.Snider.Core.Greet', 'World'); +console.log(greeting); // "Hello, World!" + +// Perform calculations +const sum = await wails.Call.ByName('github.com.Snider.Core.Add', 10, 20); +const product = await wails.Call.ByName('github.com.Snider.Core.Multiply', 5, 6); + +// Complex calculations with error handling +try { + const result = await wails.Call.ByName('github.com.Snider.Core.Calculate', 'divide', 100, 4); + console.log(result); // 25 +} catch (error) { + console.error('Calculation error:', error); +} +``` + +## Available Service Methods + +The `CoreService` provides the following methods: + +- **`ServiceName() string`** - Returns the service identifier +- **`GetVersion() string`** - Returns the current version +- **`Greet(name string) string`** - Returns a greeting message +- **`Add(a, b int) int`** - Adds two integers +- **`Multiply(a, b int) int`** - Multiplies two integers +- **`Calculate(operation string, a, b int) (int, error)`** - Performs calculations + - Supported operations: `"add"`, `"subtract"`, `"multiply"`, `"divide"` + +## Running the Example + +The example demonstrates a complete Wails application using the Core service: + +```bash +cd examples +go run main.go +``` + +This will launch a Wails application window with an interactive UI that calls the Core service methods. + +## Development + +### Building + +```bash +make build +``` + +### Testing + +Run all tests: + +```bash +make test +``` + +Run tests with coverage: + +```bash +make test-coverage +``` + +Run benchmark tests: + +```bash +make test-bench +``` + +### Code Quality + +Format code: + +```bash +make fmt +``` + +Run static analysis: + +```bash +make vet +``` + +Run linter (requires golangci-lint): + +```bash +make lint +``` + +### Installing Development Tools + +```bash +make install-tools +``` + +## Project Structure + +``` +. +โโโ .github/ +โ โโโ workflows/ +โ โโโ ci.yml # GitHub Actions CI/CD +โโโ examples/ +โ โโโ frontend/ +โ โ โโโ index.html # Example UI +โ โโโ main.go # Example Wails application +โโโ .gitignore # Git ignore rules +โโโ .golangci.yml # Linter configuration +โโโ core.go # Core service implementation +โโโ core_test.go # Tests +โโโ go.mod # Go module definition +โโโ LICENSE # MIT License +โโโ Makefile # Development tasks +โโโ README.md # This file +``` + +## Creating Your Own Service + +Use this project as a template to create your own Wails v3 service: + +1. Fork or clone this repository +2. Update the module name in `go.mod` +3. Modify `core.go` to implement your service logic +4. Update the `ServiceName()` method to return your service identifier +5. Add your methods (they will be automatically exposed to the frontend) +6. Write tests for your methods +7. Update the example to demonstrate your service + +### Service Method Requirements + +For methods to be callable from the frontend: + +- Must be exported (start with uppercase letter) +- Must belong to a struct that implements the service interface +- Can accept basic types and structs as parameters +- Can return values and errors + +## API Documentation + +Full API documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/Snider/Core). + +Generate documentation locally: + +```bash +go doc -all +``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## Testing Guidelines + +- Write tests for all new functionality +- Maintain or improve code coverage +- Run `make test` before submitting PRs +- Include benchmark tests for performance-critical code + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Resources + +- [Wails v3 Documentation](https://wails.io) +- [Wails v3 GitHub Repository](https://github.com/wailsapp/wails) +- [Go Documentation](https://golang.org/doc/) + +## Acknowledgments + +- Built with โค๏ธ using Go and Wails v3 +- Inspired by Wails best practices and community standards \ No newline at end of file diff --git a/core.go b/core.go new file mode 100644 index 0000000..1650ef7 --- /dev/null +++ b/core.go @@ -0,0 +1,66 @@ +// Package core provides a Wails v3 service with core utilities and helper functions. +// This is a starter service library that can be extended with additional functionality. +package core + +import ( + "fmt" +) + +// Version represents the current version of the service. +const Version = "0.1.0" + +// CoreService provides core utilities and helper functions as a Wails v3 service. +// This service can be registered with a Wails application and called from the frontend. +type CoreService struct { +} + +// NewCoreService creates a new CoreService instance. +func NewCoreService() *CoreService { + return &CoreService{} +} + +// ServiceName returns the name of the service. +// You should use the go module format e.g. github.com/myuser/myservice +func (s *CoreService) ServiceName() string { + return "github.com/Snider/Core" +} + +// GetVersion returns the current service version. +func (s *CoreService) GetVersion() string { + return Version +} + +// Greet returns a greeting message for the given name. +func (s *CoreService) Greet(name string) string { + return fmt.Sprintf("Hello, %s!", name) +} + +// Add adds two integers and returns the result. +func (s *CoreService) Add(a, b int) int { + return a + b +} + +// Multiply multiplies two integers and returns the result. +func (s *CoreService) Multiply(a, b int) int { + return a * b +} + +// Calculate performs a calculation based on the operation string. +// Supported operations: "add", "subtract", "multiply", "divide" +func (s *CoreService) Calculate(operation string, a, b int) (int, error) { + switch operation { + case "add": + return a + b, nil + case "subtract": + return a - b, nil + case "multiply": + return a * b, nil + case "divide": + if b == 0 { + return 0, fmt.Errorf("division by zero") + } + return a / b, nil + default: + return 0, fmt.Errorf("unknown operation: %s", operation) + } +} diff --git a/core_test.go b/core_test.go new file mode 100644 index 0000000..cf8ba5f --- /dev/null +++ b/core_test.go @@ -0,0 +1,265 @@ +package core + +import ( + "fmt" + "testing" +) + +func TestCoreService_Greet(t *testing.T) { + service := NewCoreService() + + tests := []struct { + name string + input string + expected string + }{ + { + name: "simple greeting", + input: "World", + expected: "Hello, World!", + }, + { + name: "greeting with name", + input: "Alice", + expected: "Hello, Alice!", + }, + { + name: "empty name", + input: "", + expected: "Hello, !", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.Greet(tt.input) + if result != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, result) + } + }) + } +} + +func TestCoreService_GetVersion(t *testing.T) { + service := NewCoreService() + version := service.GetVersion() + if version == "" { + t.Error("expected non-empty version string") + } + if version != Version { + t.Errorf("expected version %q, got %q", Version, version) + } +} + +func TestCoreService_ServiceName(t *testing.T) { + service := NewCoreService() + name := service.ServiceName() + expectedName := "github.com/Snider/Core" + if name != expectedName { + t.Errorf("expected service name %q, got %q", expectedName, name) + } +} + +func TestCoreService_Add(t *testing.T) { + service := NewCoreService() + + tests := []struct { + name string + a int + b int + expected int + }{ + { + name: "positive numbers", + a: 2, + b: 3, + expected: 5, + }, + { + name: "negative numbers", + a: -5, + b: 3, + expected: -2, + }, + { + name: "zero values", + a: 0, + b: 0, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.Add(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected) + } + }) + } +} + +func TestCoreService_Multiply(t *testing.T) { + service := NewCoreService() + + tests := []struct { + name string + a int + b int + expected int + }{ + { + name: "positive numbers", + a: 2, + b: 3, + expected: 6, + }, + { + name: "negative and positive", + a: -5, + b: 3, + expected: -15, + }, + { + name: "multiplication by zero", + a: 5, + b: 0, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.Multiply(tt.a, tt.b) + if result != tt.expected { + t.Errorf("Multiply(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected) + } + }) + } +} + +func TestCoreService_Calculate(t *testing.T) { + service := NewCoreService() + + tests := []struct { + name string + operation string + a int + b int + expected int + wantErr bool + }{ + { + name: "add operation", + operation: "add", + a: 10, + b: 5, + expected: 15, + wantErr: false, + }, + { + name: "subtract operation", + operation: "subtract", + a: 10, + b: 5, + expected: 5, + wantErr: false, + }, + { + name: "multiply operation", + operation: "multiply", + a: 10, + b: 5, + expected: 50, + wantErr: false, + }, + { + name: "divide operation", + operation: "divide", + a: 10, + b: 5, + expected: 2, + wantErr: false, + }, + { + name: "divide by zero", + operation: "divide", + a: 10, + b: 0, + expected: 0, + wantErr: true, + }, + { + name: "unknown operation", + operation: "modulo", + a: 10, + b: 5, + expected: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := service.Calculate(tt.operation, tt.a, tt.b) + if (err != nil) != tt.wantErr { + t.Errorf("Calculate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && result != tt.expected { + t.Errorf("Calculate(%q, %d, %d) = %d; expected %d", tt.operation, tt.a, tt.b, result, tt.expected) + } + }) + } +} + +// Benchmark tests +func BenchmarkCoreService_Greet(b *testing.B) { + service := NewCoreService() + for i := 0; i < b.N; i++ { + _ = service.Greet("World") + } +} + +func BenchmarkCoreService_Add(b *testing.B) { + service := NewCoreService() + for i := 0; i < b.N; i++ { + _ = service.Add(42, 24) + } +} + +func BenchmarkCoreService_Multiply(b *testing.B) { + service := NewCoreService() + for i := 0; i < b.N; i++ { + _ = service.Multiply(42, 24) + } +} + +func BenchmarkCoreService_Calculate(b *testing.B) { + service := NewCoreService() + for i := 0; i < b.N; i++ { + _, _ = service.Calculate("add", 42, 24) + } +} + +// Example tests +func ExampleCoreService_Greet() { + service := NewCoreService() + greeting := service.Greet("World") + fmt.Println(greeting) + // Output: Hello, World! +} + +func ExampleCoreService_Add() { + service := NewCoreService() + result := service.Add(2, 3) + fmt.Println(result) + // Output: 5 +} + +func ExampleCoreService_Calculate() { + service := NewCoreService() + result, _ := service.Calculate("multiply", 6, 7) + fmt.Println(result) + // Output: 42 +} diff --git a/examples/demo.go b/examples/demo.go new file mode 100644 index 0000000..0b96e76 --- /dev/null +++ b/examples/demo.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + + "github.com/Snider/Core" +) + +func main() { + // Create the Core service + coreService := core.NewCoreService() + + // Display service information + fmt.Printf("Service: %s\n", coreService.ServiceName()) + fmt.Printf("Version: %s\n\n", coreService.GetVersion()) + + // Demonstrate service methods + fmt.Println("=== Core Service Demo ===\n") + + // Test Greet + fmt.Println("1. Greeting:") + fmt.Printf(" Greet('Wails Developer'): %s\n", coreService.Greet("Wails Developer")) + fmt.Printf(" Greet('Go Programmer'): %s\n\n", coreService.Greet("Go Programmer")) + + // Test Add + fmt.Println("2. Addition:") + fmt.Printf(" Add(15, 27): %d\n", coreService.Add(15, 27)) + fmt.Printf(" Add(100, 200): %d\n\n", coreService.Add(100, 200)) + + // Test Multiply + fmt.Println("3. Multiplication:") + fmt.Printf(" Multiply(8, 9): %d\n", coreService.Multiply(8, 9)) + fmt.Printf(" Multiply(12, 12): %d\n\n", coreService.Multiply(12, 12)) + + // Test Calculate with various operations + fmt.Println("4. Calculator:") + operations := []struct { + op string + a int + b int + }{ + {"add", 10, 5}, + {"subtract", 20, 8}, + {"multiply", 7, 6}, + {"divide", 100, 4}, + } + + for _, test := range operations { + result, err := coreService.Calculate(test.op, test.a, test.b) + if err != nil { + fmt.Printf(" Calculate('%s', %d, %d): Error - %v\n", test.op, test.a, test.b, err) + } else { + fmt.Printf(" Calculate('%s', %d, %d): %d\n", test.op, test.a, test.b, result) + } + } + + // Test error handling + fmt.Println("\n5. Error Handling:") + _, err := coreService.Calculate("divide", 10, 0) + if err != nil { + fmt.Printf(" Calculate('divide', 10, 0): Error - %v โ\n", err) + } + + _, err = coreService.Calculate("modulo", 10, 3) + if err != nil { + fmt.Printf(" Calculate('modulo', 10, 3): Error - %v โ\n", err) + } + + fmt.Println("\n=== Demo Complete ===") + fmt.Println("\nThis service can be integrated into any Wails v3 application!") + fmt.Println("For a full UI example, see examples/main.go (requires Wails v3 with UI support)") +} diff --git a/examples/frontend/index.html b/examples/frontend/index.html new file mode 100644 index 0000000..4370675 --- /dev/null +++ b/examples/frontend/index.html @@ -0,0 +1,230 @@ + + +
+ + +