diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..4ae8be8 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,149 @@ +%{ + configs: [ + %{ + name: "default", + files: %{ + included: [ + "lib/", + "test/" + ], + excluded: [ + ~r"/_build/", + ~r"/deps/", + ~r"/node_modules/" + ] + }, + plugins: [], + requires: [], + strict: true, + parse_timeout: 5000, + color: true, + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + {Credo.Check.Design.TagTODO, [exit_status: 0]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, [max_complexity: 12]}, + {Credo.Check.Refactor.FunctionArity, [max_arity: 8]}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warning Checks + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + ] + } + } + ] +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..74e7239 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Git +.git +.gitignore + +# Build artifacts +/_build +/deps +/*.ez +/stopwatch + +# Documentation +/doc +/docs + +# Coverage +/cover + +# Environment files +.env +.env.* +/config/*.secret.exs + +# Editor/IDE files +.elixir_ls/ +.vscode/ +.idea/ +*.iml + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log + +# Test files (optional - remove if you want to run tests in Docker) +# /test + +# CI/CD +.github + +# Other +README.md +CHANGELOG.md +CONTRIBUTING.md +LICENSE diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..4e8e8f7 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,10 @@ +# Used by "mix format" +[ + inputs: [ + "{mix,.formatter}.exs", + "{config,lib,test}/**/*.{ex,exs}", + "bench/**/*.exs", + "examples/**/*.exs" + ], + line_length: 100 +] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..4e13047 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,78 @@ +name: Bug Report +description: Report a bug or unexpected behavior +title: "[Bug]: " +labels: ["bug", "needs-triage"] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug! Please fill out the information below. + + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of what the bug is. + placeholder: Tell us what you see! + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Run the command '...' + 2. Enter '...' + 3. See error + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect to happen? + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened? + validations: + required: true + + - type: input + id: elixir-version + attributes: + label: Elixir Version + description: Output of `elixir --version` + placeholder: "Elixir 1.16.0 (compiled with Erlang/OTP 26)" + validations: + required: true + + - type: input + id: os + attributes: + label: Operating System + description: What OS are you using? + placeholder: "Ubuntu 22.04, macOS 14, Windows 11" + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Error Logs + description: Please copy and paste any relevant error messages or logs + render: shell + + - type: textarea + id: additional + attributes: + label: Additional Context + description: Add any other context about the problem here diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..c7843f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,64 @@ +name: Feature Request +description: Suggest a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement"] + +body: + - type: markdown + attributes: + value: | + Thanks for suggesting a feature! We appreciate your input. + + - type: textarea + id: problem + attributes: + label: Problem Statement + description: Is your feature request related to a problem? Please describe. + placeholder: I'm always frustrated when... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Describe the solution you'd like + placeholder: I would like to be able to... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Have you considered any alternative solutions or features? + + - type: textarea + id: examples + attributes: + label: Usage Examples + description: Provide examples of how this feature would be used + placeholder: | + ```elixir + # Example code here + ``` + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this feature to you? + options: + - Low - Nice to have + - Medium - Would significantly improve my workflow + - High - Critical for my use case + validations: + required: true + + - type: checkboxes + id: contribution + attributes: + label: Contribution + description: Would you be willing to contribute this feature? + options: + - label: I'd be willing to submit a PR for this feature diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..934e330 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,59 @@ +## Description + + + +## Type of Change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Refactoring (no functional changes) +- [ ] Performance improvement +- [ ] Test improvements + +## Related Issues + + + +Closes # + +## Changes Made + + + +- +- +- + +## Testing + + + +- [ ] Existing tests pass (`mix test`) +- [ ] Added new tests for new functionality +- [ ] Tested manually with the following steps: + 1. + 2. + 3. + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published + +## Screenshots (if applicable) + + + +## Additional Notes + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c619bd4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,169 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + MIX_ENV: test + +jobs: + test: + name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) + runs-on: ubuntu-latest + + strategy: + matrix: + elixir: ['1.14', '1.15', '1.16'] + otp: ['24', '25', '26'] + exclude: + - elixir: '1.14' + otp: '26' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Restore dependencies cache + uses: actions/cache@v3 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}- + + - name: Install dependencies + run: mix deps.get + + - name: Compile code (warnings as errors) + run: mix compile --warnings-as-errors + + - name: Run tests + run: mix test --cover + + - name: Upload coverage to Codecov + if: matrix.elixir == '1.16' && matrix.otp == '26' + uses: codecov/codecov-action@v3 + with: + files: ./cover/excoveralls.json + + format: + name: Code Formatting + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Install dependencies + run: mix deps.get + + - name: Check code formatting + run: mix format --check-formatted + + credo: + name: Code Quality (Credo) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Install dependencies + run: mix deps.get + + - name: Run Credo + run: mix credo --strict + + dialyzer: + name: Static Analysis (Dialyzer) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Restore dependencies cache + uses: actions/cache@v3 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-dialyzer-${{ hashFiles('**/mix.lock') }} + + - name: Install dependencies + run: mix deps.get + + - name: Restore PLT cache + uses: actions/cache@v3 + id: plt-cache + with: + path: priv/plts + key: ${{ runner.os }}-plt-${{ hashFiles('**/mix.lock') }} + + - name: Create PLTs + if: steps.plt-cache.outputs.cache-hit != 'true' + run: mix dialyzer --plt + + - name: Run Dialyzer + run: mix dialyzer --format github + + build-escript: + name: Build Escript + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Install dependencies + run: mix deps.get + + - name: Build escript + run: mix escript.build + + - name: Test escript + run: | + ./stopwatch --version || true + test -f stopwatch + + - name: Upload escript artifact + uses: actions/upload-artifact@v3 + with: + name: stopwatch-escript + path: stopwatch + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8b0d986 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,75 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + build-and-release: + name: Build and Release + runs-on: ${{ matrix.os }} + + strategy: + matrix: + include: + - os: ubuntu-latest + artifact_name: stopwatch-linux-amd64 + - os: macos-latest + artifact_name: stopwatch-macos-amd64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Install dependencies + run: mix deps.get + + - name: Run tests + run: mix test + + - name: Build escript + run: mix escript.build + + - name: Rename artifact + run: mv stopwatch ${{ matrix.artifact_name }} + + - name: Upload to Release + uses: softprops/action-gh-release@v1 + with: + files: ${{ matrix.artifact_name }} + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-hex: + name: Publish to Hex.pm + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: '1.16' + otp-version: '26' + + - name: Install dependencies + run: mix deps.get + + - name: Publish to Hex + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + run: mix hex.publish --yes diff --git a/.gitignore b/.gitignore index b263cd1..28f215b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,39 @@ +# Mix artifacts /_build -/cover /deps -/doc -/.fetch +/*.ez + +# Generate on crash by the VM erl_crash.dump -*.ez -*.beam + +# Static artifacts +/doc + +# Code coverage +/cover + +# Environment files /config/*.secret.exs +.env +.env.* + +# Temporary files +*.swp +*.swo +*~ +.DS_Store + +# Editor/IDE files .elixir_ls/ +.vscode/ +.idea/ +*.iml + +# Build artifacts +/stopwatch + +# Logs +*.log + +# OS generated files +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5c5b338 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,154 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- **Export Functionality** via `Stopwatch.Export` module + - JSON export with full statistics and formatted times + - CSV export for laps and event history + - File I/O operations for saving exports +- **Configuration System** via `Stopwatch.Config` module + - Load/save configuration from `.stopwatch.config.json` + - Customizable default settings + - Support for format preferences and behavior options +- **Session Persistence** + - Save current stopwatch session to file + - Load previously saved sessions (basic implementation) + - Export commands in CLI (`export`, `save`, `load`) +- **Docker Support** + - Multi-stage Dockerfile for minimal production images + - Docker Compose configuration with dev and prod services + - `.dockerignore` for optimized builds + - Comprehensive Docker documentation +- **CI/CD Infrastructure** + - GitHub Actions workflows for testing across multiple Elixir/OTP versions + - Automated code quality checks (formatting, Credo, Dialyzer) + - Automated escript builds + - Release workflow with multi-platform support + - Hex.pm publishing automation +- **Code Quality Tools** + - Credo integration with comprehensive ruleset (`.credo.exs`) + - Dialyzer static analysis with PLT caching + - ExCoveralls for test coverage reporting + - Code formatting configuration (`.formatter.exs`) +- **Developer Experience** + - Makefile with common development commands + - Benchmarking suite (`bench/stopwatch_bench.exs`) + - Example scripts (`examples/basic_usage.exs`, `examples/export_example.exs`) + - Performance benchmarks for all major operations +- **GitHub Templates** + - Bug report template (YAML format) + - Feature request template (YAML format) + - Pull request template with checklist +- **Enhanced Documentation** + - Complete API documentation (`docs/API.md`) + - Docker usage guide (`docs/DOCKER.md`) + - Updated README with all new features + - Inline examples and usage patterns +- **Additional Dependencies** + - Jason for JSON encoding/decoding + - Credo for code quality + - Dialyxir for static analysis + - ExCoveralls for coverage reporting + +### Changed +- Updated `mix.exs` with additional dependencies and configuration +- Enhanced `.gitignore` with more comprehensive exclusions +- Expanded CLI commands to include `export`, `save`, and `load` +- Updated README with comprehensive feature list and examples +- Improved documentation structure and organization + +## [1.0.0] - 2025-11-13 + +### Added +- Complete rewrite and reorganization of the project structure +- Proper Mix project configuration with `mix.exs` +- Core `Stopwatch` module with comprehensive state management + - Start/stop functionality + - Pause/resume capability + - Lap timing with cumulative tracking + - Event history tracking + - High-precision timing using monotonic time +- `Stopwatch.Formatter` module for flexible time display + - Multiple format options: short, long, compact, verbose + - Lap table formatting + - Time component breakdown utilities +- `Stopwatch.CLI` module with rich interactive interface + - Color-coded status indicators + - Real-time elapsed time display + - Lap statistics (fastest, slowest, average) + - User-friendly command system +- Comprehensive test suite + - Core stopwatch functionality tests + - Formatter tests with multiple scenarios + - Test coverage for all major features +- Documentation + - Detailed README with examples and usage guide + - Inline documentation with @doc and @moduledoc + - Type specifications for better IDE support + - CHANGELOG for version tracking + - CONTRIBUTING guidelines +- Project infrastructure + - Proper `.gitignore` for Mix projects + - MIT License + - Escript configuration for standalone executable + +### Changed +- Migrated from simple script (`stopwatch.exs`) to full Mix project structure +- Improved time accuracy using monotonic time instead of system time +- Enhanced user experience with better formatting and colors + +### Technical Details +- Minimum Elixir version: 1.14 +- Uses system monotonic time for accuracy +- Modular architecture with separation of concerns +- Functional programming patterns throughout +- Comprehensive error handling with tagged tuples + +## [0.1.0] - 2025-11-13 (Initial Version) + +### Added +- Initial simple stopwatch script +- Basic start/stop functionality +- Simple command-line interface +- Time display in seconds + +--- + +## Release Notes + +### v1.0.0 - Major Enhancement Release + +This release represents a complete overhaul of the Elixir Stopwatch project, transforming it from a simple script into a professional, feature-rich application with a proper project structure. + +**Highlights:** +- Full Mix project with proper dependency management +- Comprehensive test coverage ensuring reliability +- Multiple time formatting options for different use cases +- Pause/resume functionality for flexible timing +- Lap timing with detailed statistics +- Beautiful, color-coded CLI interface +- Complete documentation and examples + +**Migration Notes:** +If upgrading from the original script version (0.1.0), you'll need to: +1. Run `mix deps.get` to install dependencies +2. Build the escript with `mix escript.build` +3. Use `./stopwatch` to run the application + +**Breaking Changes:** +- The application now requires a proper Mix project setup +- Direct script execution is replaced with escript binary +- API has been completely redesigned (for programmatic use) + +**Future Plans:** +- Add configuration file support +- Implement named timers +- Add export functionality (CSV, JSON) +- Create a web interface option +- Add sound alerts for lap/stop events diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..66f9757 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,334 @@ +# Contributing to Elixir Stopwatch + +First off, thank you for considering contributing to Elixir Stopwatch! It's people like you that make this tool better for everyone. + +## Code of Conduct + +This project and everyone participating in it is governed by mutual respect and professionalism. By participating, you are expected to uphold this standard. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check the existing issues to avoid duplicates. When you create a bug report, include as many details as possible: + +- **Use a clear and descriptive title** +- **Describe the exact steps to reproduce the problem** +- **Provide specific examples** - Include code samples, command-line output, or screenshots +- **Describe the behavior you observed** and what behavior you expected to see +- **Include details about your environment**: + - Elixir version (`elixir --version`) + - Erlang/OTP version + - Operating system and version + +### Suggesting Enhancements + +Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion, include: + +- **A clear and descriptive title** +- **A detailed description of the proposed functionality** +- **Examples of how the enhancement would be used** +- **Why this enhancement would be useful** to most users + +### Pull Requests + +1. Fork the repository and create your branch from `main` +2. If you've added code that should be tested, add tests +3. If you've changed APIs, update the documentation +4. Ensure the test suite passes +5. Make sure your code follows the existing style +6. Write a clear commit message + +## Development Setup + +### Prerequisites + +- Elixir 1.14 or higher +- Erlang/OTP 24 or higher +- Git + +### Setting Up Your Environment + +1. Fork and clone the repository: + ```bash + git clone https://github.com/your-username/elixir-stopwatch.git + cd elixir-stopwatch + ``` + +2. Install dependencies: + ```bash + mix deps.get + ``` + +3. Run tests to ensure everything works: + ```bash + mix test + ``` + +4. Build the escript: + ```bash + mix escript.build + ``` + +## Development Workflow + +### Running Tests + +```bash +# Run all tests +mix test + +# Run tests with coverage +mix test --cover + +# Run a specific test file +mix test test/stopwatch_test.exs + +# Run tests in watch mode (requires mix_test_watch) +mix test.watch +``` + +### Code Formatting + +This project uses Elixir's built-in formatter: + +```bash +# Format all files +mix format + +# Check if files are formatted +mix format --check-formatted +``` + +### Documentation + +Generate and view documentation locally: + +```bash +mix docs +open doc/index.html # or xdg-open on Linux +``` + +### Code Quality + +While not mandatory, we encourage running additional quality checks: + +```bash +# Static analysis (if using Dialyzer) +mix dialyzer + +# Linting (if using Credo) +mix credo --strict +``` + +## Code Style Guidelines + +### General Principles + +- Follow the [Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide) +- Write clear, self-documenting code +- Keep functions small and focused +- Use pattern matching effectively +- Prefer functional patterns over imperative + +### Specific Guidelines + +1. **Documentation** + - Add `@moduledoc` to all modules + - Add `@doc` to all public functions + - Include examples in documentation + - Add `@spec` type specifications + +2. **Testing** + - Write tests for all new functionality + - Maintain or improve test coverage + - Use descriptive test names + - Group related tests with `describe` blocks + +3. **Error Handling** + - Use tagged tuples (`{:ok, result}`, `{:error, reason}`) + - Provide clear error messages + - Handle edge cases explicitly + +4. **Naming Conventions** + - Use descriptive variable names + - Follow Elixir naming conventions + - Use `snake_case` for functions and variables + - Use `PascalCase` for modules + +5. **Module Organization** + ```elixir + defmodule MyModule do + @moduledoc """ + Module documentation + """ + + # Module attributes + @constant_value "value" + + # Type definitions + @type t :: %__MODULE__{} + + # Struct definition + defstruct [:field1, :field2] + + # Public functions + def public_function do + # ... + end + + # Private functions + defp private_function do + # ... + end + end + ``` + +## Commit Message Guidelines + +### Format + +``` +(): + + + +