Compare commits
4 Commits
v1.1.0-bet
...
409469350e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
409469350e | ||
|
|
8cb799d378 | ||
|
|
0431425f38 | ||
|
|
cc20c5e6f2 |
@@ -56,6 +56,7 @@ which = "7.0"
|
||||
|
||||
# Colors
|
||||
colored = "2.1"
|
||||
chrono = "0.4.43"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.13"
|
||||
@@ -86,4 +87,4 @@ strip = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
embedded-gradle = [] # Embed gradle-wrapper.jar in binary (run download-gradle-wrapper.sh first)
|
||||
embedded-gradle = [] # Embed gradle-wrapper.jar in binary (run download-gradle-wrapper.sh first)
|
||||
|
||||
168
docs/ROADMAP.md
168
docs/ROADMAP.md
@@ -2,15 +2,24 @@
|
||||
|
||||
This document outlines the planned feature development for Weevil across multiple versions. Features are subject to change based on user feedback, technical constraints, and market needs.
|
||||
|
||||
## Status Key
|
||||
|
||||
- ✅ **Complete** - Feature shipped in a release
|
||||
- ⚠️ **In Progress** - Currently being developed
|
||||
- 🔄 **Deferred** - Planned but postponed to a later version
|
||||
- ❌ **Cancelled** - Feature dropped from roadmap
|
||||
|
||||
---
|
||||
|
||||
## Version 1.1.0 - Core Stability & Team Adoption
|
||||
## Version 1.1.0 - Core Stability & Team Adoption ✅ COMPLETE
|
||||
|
||||
**Theme:** Making Weevil production-ready for FTC teams with essential operational features and reducing friction in existing workflows.
|
||||
|
||||
### System Audit & Diagnostics
|
||||
**Status:** Released as v1.1.0-beta.2 (pending Windows testing)
|
||||
|
||||
**Feature:** `weevil status` or `weevil doctor` command
|
||||
### System Audit & Diagnostics ✅
|
||||
|
||||
**Feature:** `weevil doctor` command
|
||||
|
||||
**Description:** Provides a comprehensive audit of the development environment, showing what's installed and what versions are present. This would display:
|
||||
- FTC SDK versions (current and available)
|
||||
@@ -20,6 +29,8 @@ This document outlines the planned feature development for Weevil across multipl
|
||||
- ADB availability and version
|
||||
- Any other critical dependencies Weevil manages
|
||||
|
||||
**Status:** ✅ Complete - Shipped in v1.1.0
|
||||
|
||||
**Rationale:** Teams need visibility into their environment to troubleshoot issues. Coaches working with multiple machines need to quickly verify setup consistency across laptops. This builds trust by making Weevil's actions transparent.
|
||||
|
||||
**Pros:**
|
||||
@@ -33,13 +44,11 @@ This document outlines the planned feature development for Weevil across multipl
|
||||
- Version detection across platforms may be fragile
|
||||
- Output formatting needs to be clear for non-technical users
|
||||
|
||||
**Priority:** HIGH - Essential for v1.1.0
|
||||
|
||||
---
|
||||
|
||||
### Dependency Cleanup
|
||||
### Dependency Cleanup ✅
|
||||
|
||||
**Feature:** `weevil clean` or `weevil uninstall` command
|
||||
**Feature:** `weevil uninstall` command
|
||||
|
||||
**Description:** Removes dependencies that Weevil installed during setup. This includes:
|
||||
- FTC SDK files
|
||||
@@ -47,7 +56,9 @@ This document outlines the planned feature development for Weevil across multipl
|
||||
- Gradle distributions
|
||||
- Configuration files Weevil created
|
||||
|
||||
Should offer options for selective cleanup (e.g., keep SDK but remove Gradle) or complete removal.
|
||||
Offers options for selective cleanup (e.g., keep SDK but remove Gradle) or complete removal.
|
||||
|
||||
**Status:** ✅ Complete - Shipped in v1.1.0
|
||||
|
||||
**Rationale:** Teams switch machines, need to free disk space, or want to start fresh. Without a clean uninstall, Weevil leaves artifacts behind. This is critical for maintaining system hygiene and building confidence that Weevil doesn't pollute the environment.
|
||||
|
||||
@@ -61,11 +72,9 @@ Should offer options for selective cleanup (e.g., keep SDK but remove Gradle) or
|
||||
- Risk of removing shared dependencies other tools need
|
||||
- Need careful confirmation prompts to prevent accidental deletion
|
||||
|
||||
**Priority:** HIGH - Essential for v1.1.0
|
||||
|
||||
---
|
||||
|
||||
### Corporate/School Proxy Support
|
||||
### Corporate/School Proxy Support ✅
|
||||
|
||||
**Feature:** Transparent proxy configuration for all network operations
|
||||
|
||||
@@ -77,6 +86,10 @@ Should offer options for selective cleanup (e.g., keep SDK but remove Gradle) or
|
||||
|
||||
Handle `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables and write appropriate configuration into Gradle properties, Android SDK manager config, etc.
|
||||
|
||||
**Status:** ✅ Complete - Shipped in v1.1.0
|
||||
|
||||
**Implementation:** `--proxy <url>` and `--no-proxy` global flags, automatic HTTPS_PROXY/HTTP_PROXY env var detection
|
||||
|
||||
**Rationale:** Many FTC teams work in schools or corporate environments with mandatory proxy servers. Without proxy support, Weevil is unusable in these environments, cutting off a significant portion of the potential user base.
|
||||
|
||||
**Pros:**
|
||||
@@ -90,11 +103,9 @@ Handle `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables and write a
|
||||
- SSL/certificate issues in corporate environments
|
||||
- Testing requires access to proxy environments
|
||||
|
||||
**Priority:** HIGH - Essential for v1.1.0
|
||||
|
||||
---
|
||||
|
||||
### Android Studio Integration
|
||||
### Android Studio Integration ✅
|
||||
|
||||
**Feature:** Seamless integration with Android Studio IDE
|
||||
|
||||
@@ -103,10 +114,15 @@ Handle `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` environment variables and write a
|
||||
- Present a clean, minimal file tree to students
|
||||
- Hook Weevil's build and deploy scripts into Android Studio's "Run" button
|
||||
- Properly configure the IDE's indexing and code completion
|
||||
- Support debugging integration
|
||||
|
||||
The goal: students work in Android Studio (the tool they know) but get Weevil's improved project structure and deployment workflow behind the scenes.
|
||||
|
||||
**Status:** ✅ Complete - Shipped in v1.1.0-beta.2
|
||||
|
||||
**Implementation:** Auto-generated `.idea/` run configurations (Build, Deploy auto/USB/WiFi, Test), workspace.xml for clean file tree, cross-platform support (Unix .sh and Windows .bat variants)
|
||||
|
||||
**Note:** Requires Shell Script plugin installation in Android Studio (documented in README)
|
||||
|
||||
**Rationale:** This is the killer feature that bridges the gap between Weevil's better engineering practices and students' existing workflow. Kids already know Android Studio. Making Weevil "just work" with it removes adoption friction and lets them focus on robot code, not tooling.
|
||||
|
||||
**Pros:**
|
||||
@@ -116,16 +132,14 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Makes Weevil invisible in the best way - it just works
|
||||
|
||||
**Cons:**
|
||||
- Android Studio project file format may change
|
||||
- Android Studio project file format may change between versions
|
||||
- Complex to test across different Android Studio versions
|
||||
- May conflict with students' existing Android Studio customizations
|
||||
- Requires deep understanding of IDE configuration
|
||||
|
||||
**Priority:** HIGH - Strong candidate for v1.1.0 (killer feature)
|
||||
- Requires Shell Script plugin (one-time setup)
|
||||
|
||||
---
|
||||
|
||||
### Manual Installation Fallback Documentation
|
||||
### Manual Installation Fallback Documentation 🔄
|
||||
|
||||
**Feature:** Comprehensive manual setup documentation
|
||||
|
||||
@@ -136,6 +150,8 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Checksums for verifying downloads
|
||||
- Fallback download URLs if primary sources are blocked
|
||||
|
||||
**Status:** 🔄 Deferred to v1.2.0 - Basic troubleshooting exists in README, comprehensive guide pending
|
||||
|
||||
**Rationale:** Automation fails. Proxies block downloads. Firewalls interfere. Having a "guaranteed to work" manual path builds confidence and ensures teams aren't stuck. This is about providing an escape hatch and building trust.
|
||||
|
||||
**Pros:**
|
||||
@@ -149,11 +165,9 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Screenshots go stale quickly
|
||||
- Platform variations multiply documentation burden
|
||||
|
||||
**Priority:** MEDIUM-HIGH - Strong candidate for v1.1.0
|
||||
|
||||
---
|
||||
|
||||
### Package Distribution (Debian/Ubuntu)
|
||||
### Package Distribution (Debian/Ubuntu) 🔄
|
||||
|
||||
**Feature:** `.deb` package for easy installation on Debian-based systems
|
||||
|
||||
@@ -163,6 +177,8 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Handle any system dependencies
|
||||
- Support clean uninstallation
|
||||
|
||||
**Status:** 🔄 Deferred - Not essential for initial adoption
|
||||
|
||||
**Rationale:** Provides a "professional" distribution method for Linux users. Makes Weevil feel like real software, not just a script. Easier for schools/teams to deploy across multiple machines.
|
||||
|
||||
**Pros:**
|
||||
@@ -177,14 +193,76 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Most teams will just download the binary anyway
|
||||
- Not essential for functionality
|
||||
|
||||
**Priority:** LOW - Nice to have, but not essential for v1.1.0
|
||||
|
||||
---
|
||||
|
||||
## Version 1.2.0 - Polish & Accessibility
|
||||
|
||||
**Theme:** Making Weevil accessible to non-technical users and expanding platform support.
|
||||
|
||||
**Status:** Planning
|
||||
|
||||
### Android Studio Debugging Support
|
||||
|
||||
**Feature:** Full debugging integration for Android Studio
|
||||
|
||||
**Description:** Extend the Android Studio integration to support breakpoint debugging directly from the IDE:
|
||||
- Generate debug run configurations that attach to the robot
|
||||
- Configure remote debugging for Android/FTC apps
|
||||
- Map source files correctly for breakpoint support
|
||||
- Handle ADB debugging bridge setup automatically
|
||||
- Support both USB and WiFi debugging
|
||||
|
||||
Students should be able to:
|
||||
1. Set breakpoints in their OpMode code
|
||||
2. Select "Debug" configuration from Android Studio
|
||||
3. Click the debug button (🐛)
|
||||
4. Have the debugger attach to the running robot
|
||||
5. Step through code, inspect variables, etc.
|
||||
|
||||
**Rationale:** Debugging is a critical skill, and print-statement debugging (`telemetry.addData()`) is primitive. Teaching students to use a real debugger makes them better engineers. This feature positions Weevil as a complete development environment, not just a build tool.
|
||||
|
||||
**Pros:**
|
||||
- Massive educational value - teaches proper debugging
|
||||
- Competitive advantage (official SDK doesn't make this easy)
|
||||
- Natural extension of existing Android Studio integration
|
||||
- Students already familiar with debuggers from other IDEs
|
||||
|
||||
**Cons:**
|
||||
- Android debugging setup is complex (ADB, JDWP, remote attach)
|
||||
- WiFi debugging is finicky and may be unreliable
|
||||
- Debugging autonomous modes has timing implications
|
||||
- Requires deep understanding of Android debug protocol
|
||||
- May not work reliably on all Control Hub firmware versions
|
||||
|
||||
**Priority:** HIGH - Natural next step after basic Android Studio integration
|
||||
|
||||
**Technical Notes:**
|
||||
- Need to generate Android Studio remote debug configurations
|
||||
- May require modifications to deploy scripts to enable debug mode
|
||||
- JDWP (Java Debug Wire Protocol) over ADB
|
||||
- Consider "Debug (USB)" and "Debug (WiFi)" separate configurations
|
||||
|
||||
---
|
||||
|
||||
### Windows Testing & Stabilization
|
||||
|
||||
**Feature:** Complete Windows support verification
|
||||
|
||||
**Description:** Comprehensive testing of all v1.1.0 features on Windows 10 and Windows 11:
|
||||
- Proxy support with Windows-specific quirks
|
||||
- Android Studio integration with Windows paths
|
||||
- Build and deployment scripts (`.bat` files)
|
||||
- Gradle wrapper on Windows
|
||||
- Path handling (backslashes vs. forward slashes)
|
||||
|
||||
**Status:** Required before v1.1.0 final release
|
||||
|
||||
**Rationale:** Many FTC teams use Windows. Can't ship v1.1.0 final without Windows verification.
|
||||
|
||||
**Priority:** CRITICAL - Blocks v1.1.0 final release
|
||||
|
||||
---
|
||||
|
||||
### Windows Installer (MSI)
|
||||
|
||||
**Feature:** Professional Windows installer package
|
||||
@@ -196,6 +274,8 @@ The goal: students work in Android Studio (the tool they know) but get Weevil's
|
||||
- Appears in "Programs and Features" for clean uninstall
|
||||
- Optionally creates desktop shortcut
|
||||
|
||||
**Status:** Planned for v1.2.0
|
||||
|
||||
**Rationale:** Windows users expect installers, not loose executables. An MSI makes Weevil feel professional and legitimate. Start menu integration makes it discoverable.
|
||||
|
||||
**Pros:**
|
||||
@@ -562,7 +642,40 @@ This makes adding new languages easier and keeps core clean.
|
||||
|
||||
## Unscheduled / Research Needed
|
||||
|
||||
### SOCKS Proxy Support
|
||||
|
||||
**Feature:** Support for SOCKS4/SOCKS5 proxies in addition to HTTP proxies
|
||||
|
||||
**Description:** Extend proxy support to handle SOCKS proxies, which are common in certain corporate and academic environments:
|
||||
- Auto-detect SOCKS proxy from environment variables
|
||||
- Support SOCKS4, SOCKS5, and SOCKS5h protocols
|
||||
- Handle authentication if required
|
||||
- Configure all network operations (Gradle, SDK downloads, Git) to use SOCKS
|
||||
|
||||
**Status:** Research - needs market validation
|
||||
|
||||
**Rationale:** Some environments use SOCKS proxies instead of HTTP proxies. While HTTP proxy support covers most cases, SOCKS support would enable Weevil in additional restricted environments.
|
||||
|
||||
**Pros:**
|
||||
- Broader environment support
|
||||
- Some corporate networks mandate SOCKS
|
||||
- Demonstrates thoroughness
|
||||
|
||||
**Cons:**
|
||||
- More complex than HTTP proxy (different protocols)
|
||||
- Smaller user base than HTTP proxy
|
||||
- Not all tools (Gradle, Git) support SOCKS equally well
|
||||
- May require native library integration (e.g., libcurl with SOCKS support)
|
||||
- Authentication adds complexity
|
||||
|
||||
**Priority:** LOW - HTTP proxy covers most use cases
|
||||
|
||||
**Decision Point:** Wait for user requests. If teams need SOCKS support, prioritize accordingly.
|
||||
|
||||
---
|
||||
|
||||
### Cloud Build Services
|
||||
|
||||
**Description:** Remote build servers for teams with slow computers. Teams push code, Weevil builds in the cloud, streams back APK.
|
||||
|
||||
**Status:** Research - needs cost/benefit analysis, infrastructure planning
|
||||
@@ -570,6 +683,7 @@ This makes adding new languages easier and keeps core clean.
|
||||
---
|
||||
|
||||
### VS Code Extension
|
||||
|
||||
**Description:** Extension for VS Code to provide similar integration as Android Studio.
|
||||
|
||||
**Status:** Research - depends on VS Code adoption in FTC community
|
||||
@@ -577,6 +691,7 @@ This makes adding new languages easier and keeps core clean.
|
||||
---
|
||||
|
||||
### Team Collaboration Features
|
||||
|
||||
**Description:** Features for teams to coordinate across multiple developers - shared configurations, code review integration, task tracking.
|
||||
|
||||
**Status:** Research - needs market validation (do teams want this?)
|
||||
@@ -584,6 +699,7 @@ This makes adding new languages easier and keeps core clean.
|
||||
---
|
||||
|
||||
### Custom Hardware Support
|
||||
|
||||
**Description:** Templates and tools for teams using custom sensors or actuators beyond standard FTC parts.
|
||||
|
||||
**Status:** Research - depends on community need
|
||||
@@ -624,4 +740,4 @@ Features may be accelerated, deferred, or cancelled as the project evolves.
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: January 2026*
|
||||
*Last Updated: February 2026*
|
||||
767
docs/TEMPLATE-PACKAGE-SPEC.md
Normal file
767
docs/TEMPLATE-PACKAGE-SPEC.md
Normal file
@@ -0,0 +1,767 @@
|
||||
# Weevil Template & Package System - Complete Specification
|
||||
|
||||
**Version:** 1.0
|
||||
**Date:** February 2, 2026
|
||||
**Status:** Ready for implementation
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document specifies two complementary features for Weevil:
|
||||
|
||||
1. **`weevil create` (v1.1.0)** - Project scaffolding with templates
|
||||
2. **`weevil add` (v1.2.0)** - Package management system
|
||||
|
||||
Together, these enable teams to start with professional code and extend projects with community-shared components.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: `weevil create` Command
|
||||
|
||||
### Overview
|
||||
|
||||
**Purpose:** Generate complete FTC robot projects from templates
|
||||
|
||||
**Version:** v1.1.0-beta.3
|
||||
**Priority:** HIGH
|
||||
|
||||
### Command Syntax
|
||||
|
||||
```bash
|
||||
weevil create <project-name> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--template <name> Template to use (default: "basic")
|
||||
--path <directory> Where to create project (default: current dir)
|
||||
--force Overwrite existing directory
|
||||
--no-init Don't initialize git repository
|
||||
--dry-run Show what would be created
|
||||
```
|
||||
|
||||
### Templates (v1.1.0)
|
||||
|
||||
#### Template 1: `basic` (Default)
|
||||
|
||||
**Purpose:** Minimal starting point
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
my-robot/
|
||||
├── src/
|
||||
│ ├── main/java/robot/
|
||||
│ │ └── opmodes/
|
||||
│ │ └── BasicOpMode.java
|
||||
│ └── test/java/robot/
|
||||
│ └── .gitkeep
|
||||
├── build.gradle
|
||||
├── settings.gradle
|
||||
├── .weevil.toml
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
└── build.bat / build.sh
|
||||
```
|
||||
|
||||
**Files:** ~10
|
||||
**Code:** ~50 lines
|
||||
|
||||
#### Template 2: `testing` (Showcase)
|
||||
|
||||
**Purpose:** Professional testing demonstration
|
||||
|
||||
**Includes:**
|
||||
- 3 subsystems (MotorCycler, WallApproach, TurnController)
|
||||
- 6 hardware interfaces + FTC implementations
|
||||
- 6 test mocks
|
||||
- 45 passing tests
|
||||
- Complete documentation (6 files)
|
||||
|
||||
**Files:** ~30
|
||||
**Code:** ~2,500 lines
|
||||
**Tests:** 45 (< 2 sec runtime)
|
||||
|
||||
**Documentation:**
|
||||
- README.md
|
||||
- DESIGN_AND_TEST_PLAN.md
|
||||
- TESTING_GUIDE.md
|
||||
- TESTING_SHOWCASE.md
|
||||
- SOLUTION.md
|
||||
- ARCHITECTURE.md
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```bash
|
||||
# Create minimal project
|
||||
weevil create my-robot
|
||||
|
||||
# Create with testing template
|
||||
weevil create my-robot --template testing
|
||||
|
||||
# Create in specific location
|
||||
weevil create my-robot --template testing --path ~/ftc-projects
|
||||
|
||||
# Preview without creating
|
||||
weevil create my-robot --template testing --dry-run
|
||||
```
|
||||
|
||||
### Variable Substitution
|
||||
|
||||
Templates support variables:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{PROJECT_NAME}}` | Project directory name | `my-robot` |
|
||||
| `{{PACKAGE_NAME}}` | Java package name | `myrobot` |
|
||||
| `{{CREATION_DATE}}` | ISO 8601 timestamp | `2026-02-02T10:30:00Z` |
|
||||
| `{{WEEVIL_VERSION}}` | Weevil version | `1.1.0` |
|
||||
| `{{TEMPLATE_NAME}}` | Template used | `testing` |
|
||||
|
||||
**Example:**
|
||||
```java
|
||||
// File: src/main/java/robot/subsystems/Example.java
|
||||
// Generated by Weevil {{WEEVIL_VERSION}} on {{CREATION_DATE}}
|
||||
package robot.{{PACKAGE_NAME}};
|
||||
```
|
||||
|
||||
Becomes:
|
||||
```java
|
||||
// Generated by Weevil 1.1.0 on 2026-02-02T10:30:00Z
|
||||
package robot.myrobot;
|
||||
```
|
||||
|
||||
### Success Output
|
||||
|
||||
```
|
||||
✓ Created FTC project 'my-robot' using template 'testing'
|
||||
|
||||
Next steps:
|
||||
cd my-robot
|
||||
weevil test # Run 45 tests (< 2 seconds)
|
||||
weevil build # Build APK
|
||||
weevil deploy # Deploy to robot
|
||||
|
||||
Documentation:
|
||||
README.md - Project overview
|
||||
DESIGN_AND_TEST_PLAN.md - System architecture
|
||||
TESTING_GUIDE.md - Testing patterns
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
**Storage Location:**
|
||||
```
|
||||
~/.weevil/templates/
|
||||
├── basic/
|
||||
│ ├── template.toml
|
||||
│ └── files/
|
||||
└── testing/
|
||||
├── template.toml
|
||||
└── files/
|
||||
```
|
||||
|
||||
**Effort Estimate:** 2-3 days
|
||||
**Complexity:** MEDIUM
|
||||
|
||||
---
|
||||
|
||||
## Part 2: `weevil add` Command
|
||||
|
||||
### Overview
|
||||
|
||||
**Purpose:** Add components to existing projects
|
||||
|
||||
**Version:** v1.2.0
|
||||
**Priority:** MEDIUM-HIGH
|
||||
|
||||
### Command Syntax
|
||||
|
||||
```bash
|
||||
weevil add <package> [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--force Overwrite conflicting files
|
||||
--merge Attempt to merge conflicts (experimental)
|
||||
--interactive Prompt for each conflict
|
||||
--dry-run Preview without adding
|
||||
--no-deps Don't install dependencies
|
||||
--dev Add as dev dependency
|
||||
```
|
||||
|
||||
### Package Naming
|
||||
|
||||
Hierarchical structure: `scope/category/name/variant`
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
nexus/hardware/dc-motor/core
|
||||
nexus/hardware/dc-motor/mock
|
||||
nexus/hardware/dc-motor/example
|
||||
nexus/hardware/dc-motor/complete
|
||||
|
||||
nexus/subsystems/wall-approach/complete
|
||||
nexus/examples/autonomous/simple-auto
|
||||
|
||||
team1234/sensors/custom-lidar/core
|
||||
```
|
||||
|
||||
**Components:**
|
||||
- **scope**: Publisher (nexus, team1234, etc.)
|
||||
- **category**: Type (hardware, subsystems, examples, testing)
|
||||
- **name**: Component name (dc-motor, wall-approach)
|
||||
- **variant**: Implementation (core, mock, example, complete)
|
||||
|
||||
### Standard Variants
|
||||
|
||||
| Variant | Contents | Dependencies |
|
||||
|---------|----------|--------------|
|
||||
| `core` | Interface + FTC wrapper | None |
|
||||
| `mock` | Test double | Requires core |
|
||||
| `example` | Example OpMode | Requires core |
|
||||
| `complete` | All of above | Includes all variants |
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```bash
|
||||
# Add complete package
|
||||
weevil add nexus/hardware/dc-motor
|
||||
|
||||
# Add specific variant
|
||||
weevil add nexus/hardware/dc-motor/core
|
||||
weevil add nexus/hardware/dc-motor/mock
|
||||
|
||||
# Add subsystem (auto-installs dependencies)
|
||||
weevil add nexus/subsystems/wall-approach/complete
|
||||
|
||||
# Preview
|
||||
weevil add nexus/hardware/servo/core --dry-run
|
||||
|
||||
# Force overwrite
|
||||
weevil add nexus/hardware/gyro/complete --force
|
||||
|
||||
# Interactive resolution
|
||||
weevil add nexus/subsystems/turn-controller/core --interactive
|
||||
```
|
||||
|
||||
### Dependency Resolution
|
||||
|
||||
Packages declare dependencies in manifest:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
"nexus/hardware/distance/core" = "^2.0.0"
|
||||
"nexus/hardware/dc-motor/core" = "^1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
"nexus/testing/mock-base" = "^1.0.0"
|
||||
```
|
||||
|
||||
**Automatic Resolution:**
|
||||
```bash
|
||||
weevil add nexus/subsystems/wall-approach/complete
|
||||
|
||||
Installing:
|
||||
1. nexus/hardware/distance/core (v2.1.0) - dependency
|
||||
2. nexus/hardware/dc-motor/core (v1.2.0) - dependency
|
||||
3. nexus/subsystems/wall-approach/complete (v1.0.0)
|
||||
|
||||
✓ Added 3 packages (15 files)
|
||||
```
|
||||
|
||||
### Conflict Handling
|
||||
|
||||
#### Default (Skip)
|
||||
|
||||
```
|
||||
⚠ File conflicts detected:
|
||||
src/main/java/robot/hardware/MotorController.java (exists)
|
||||
|
||||
Resolution: Skipping conflicting files
|
||||
|
||||
Added:
|
||||
✓ src/main/java/robot/hardware/FtcMotorController.java
|
||||
|
||||
Skipped:
|
||||
⊗ MotorController.java (already exists)
|
||||
```
|
||||
|
||||
#### Force Mode
|
||||
|
||||
```bash
|
||||
weevil add nexus/hardware/dc-motor/core --force
|
||||
|
||||
⚠ Warning: --force will overwrite 2 files
|
||||
Continue? [y/N]: y
|
||||
|
||||
✓ Overwrote 2 files, added 3 files
|
||||
```
|
||||
|
||||
#### Interactive Mode
|
||||
|
||||
```bash
|
||||
weevil add nexus/hardware/dc-motor/core --interactive
|
||||
|
||||
Conflict: MotorController.java
|
||||
|
||||
Options:
|
||||
[s] Skip (keep your file)
|
||||
[o] Overwrite (use package file)
|
||||
[d] Show diff
|
||||
[a] Abort
|
||||
|
||||
Choice [s]: d
|
||||
|
||||
Diff:
|
||||
--- Existing
|
||||
+++ Package
|
||||
@@ -5,3 +5,5 @@
|
||||
public interface MotorController {
|
||||
void setPower(double power);
|
||||
double getPower();
|
||||
+ void setMode(RunMode mode);
|
||||
+ RunMode getMode();
|
||||
}
|
||||
|
||||
Choice [s]:
|
||||
```
|
||||
|
||||
### Package Categories
|
||||
|
||||
#### `hardware/*`
|
||||
Physical hardware abstractions
|
||||
|
||||
**Subcategories:**
|
||||
- `hardware/motors/*` - Motor controllers
|
||||
- `hardware/sensors/*` - Sensor interfaces
|
||||
- `hardware/servos/*` - Servo controllers
|
||||
- `hardware/vision/*` - Camera systems
|
||||
- `hardware/imu/*` - Gyroscopes
|
||||
|
||||
**Standard Variants:** core, mock, example, complete
|
||||
|
||||
#### `subsystems/*`
|
||||
Robot subsystems built on hardware
|
||||
|
||||
**Examples:**
|
||||
- `subsystems/drivetrain/*`
|
||||
- `subsystems/arm/*`
|
||||
- `subsystems/intake/*`
|
||||
|
||||
**Standard Variants:** core, mock, example, complete
|
||||
|
||||
#### `examples/*`
|
||||
Complete working examples
|
||||
|
||||
**Subcategories:**
|
||||
- `examples/autonomous/*`
|
||||
- `examples/teleop/*`
|
||||
- `examples/vision/*`
|
||||
|
||||
**Variants:** Usually standalone (no variants)
|
||||
|
||||
#### `testing/*`
|
||||
Testing utilities and patterns
|
||||
|
||||
**Examples:**
|
||||
- `testing/mock-hardware` - Mock collection
|
||||
- `testing/test-patterns` - Reusable patterns
|
||||
- `testing/assertions` - Custom assertions
|
||||
|
||||
#### `utilities/*`
|
||||
Helper utilities
|
||||
|
||||
**Examples:**
|
||||
- `utilities/math/*`
|
||||
- `utilities/telemetry/*`
|
||||
- `utilities/logging/*`
|
||||
|
||||
### Package Manifest
|
||||
|
||||
**Example (`package.toml`):**
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "dc-motor"
|
||||
scope = "nexus"
|
||||
category = "hardware"
|
||||
version = "1.2.0"
|
||||
description = "DC motor controller interface and FTC implementation"
|
||||
license = "MIT"
|
||||
authors = ["Nexus Workshops <info@nxgit.dev>"]
|
||||
|
||||
[variants]
|
||||
available = ["core", "mock", "example", "complete"]
|
||||
default = "complete"
|
||||
|
||||
[dependencies]
|
||||
# Empty for base hardware
|
||||
|
||||
[files.core]
|
||||
include = [
|
||||
"src/main/java/robot/hardware/MotorController.java",
|
||||
"src/main/java/robot/hardware/FtcMotorController.java"
|
||||
]
|
||||
|
||||
[files.mock]
|
||||
include = ["src/test/java/robot/hardware/MockMotorController.java"]
|
||||
dependencies = ["core"]
|
||||
|
||||
[files.example]
|
||||
include = ["src/main/java/robot/opmodes/examples/MotorExample.java"]
|
||||
dependencies = ["core"]
|
||||
|
||||
[files.complete]
|
||||
dependencies = ["core", "mock", "example"]
|
||||
```
|
||||
|
||||
### Package Repository
|
||||
|
||||
**Location:** https://packages.nxgit.dev
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
packages.nxgit.dev/
|
||||
├── index.json
|
||||
├── nexus/
|
||||
│ ├── hardware/
|
||||
│ │ ├── dc-motor/
|
||||
│ │ │ ├── package.toml
|
||||
│ │ │ ├── core.tar.gz
|
||||
│ │ │ ├── mock.tar.gz
|
||||
│ │ │ └── complete.tar.gz
|
||||
│ │ └── distance/...
|
||||
│ └── subsystems/...
|
||||
└── team1234/...
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
**Effort Estimate:** 2-3 weeks
|
||||
**Complexity:** HIGH
|
||||
|
||||
**Key Components:**
|
||||
1. Package registry (hosted)
|
||||
2. Dependency resolver
|
||||
3. Conflict detector
|
||||
4. File installer
|
||||
5. Supporting commands (remove, search, list)
|
||||
|
||||
---
|
||||
|
||||
## Supporting Commands
|
||||
|
||||
### `weevil remove`
|
||||
|
||||
Remove installed package
|
||||
|
||||
```bash
|
||||
weevil remove <package> [--prune] [--force]
|
||||
```
|
||||
|
||||
### `weevil search`
|
||||
|
||||
Search package registry
|
||||
|
||||
```bash
|
||||
weevil search <query>
|
||||
|
||||
Output:
|
||||
nexus/hardware/mecanum-drive/complete
|
||||
Mecanum drive system with holonomic control
|
||||
★★★★★ (342 downloads)
|
||||
```
|
||||
|
||||
### `weevil list`
|
||||
|
||||
List packages
|
||||
|
||||
```bash
|
||||
weevil list --installed # Show installed
|
||||
weevil list --available # Show all available
|
||||
```
|
||||
|
||||
### `weevil info`
|
||||
|
||||
Show package details
|
||||
|
||||
```bash
|
||||
weevil info nexus/hardware/dc-motor/complete
|
||||
|
||||
Package: nexus/hardware/dc-motor/complete
|
||||
Version: 1.2.0
|
||||
Downloads: 1,523
|
||||
License: MIT
|
||||
|
||||
Description:
|
||||
DC motor controller with interface, FTC implementation,
|
||||
test mocks, and examples.
|
||||
```
|
||||
|
||||
### `weevil update`
|
||||
|
||||
Update packages to latest versions
|
||||
|
||||
```bash
|
||||
weevil update # Update all
|
||||
weevil update <package> # Update specific
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Strategic Benefits
|
||||
|
||||
### For Teams
|
||||
- **Faster start** - Working code immediately
|
||||
- **Best practices** - Learn from examples
|
||||
- **Code reuse** - Don't reinvent wheels
|
||||
- **Community** - Share solutions
|
||||
|
||||
### For Nexus Workshops
|
||||
- **Authority** - Set FTC code standards
|
||||
- **Network effects** - More packages = more value
|
||||
- **Revenue** - Workshops teach patterns
|
||||
- **Differentiation** - Unique offering
|
||||
|
||||
### For FTC Community
|
||||
- **Quality** - Raised bar across teams
|
||||
- **Collaboration** - Build on each other
|
||||
- **Education** - Professional patterns
|
||||
- **Innovation** - Focus on unique solutions
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### v1.1.0 (create)
|
||||
- [ ] 50+ teams use testing template in first month
|
||||
- [ ] Positive feedback on quality
|
||||
- [ ] Used in Nexus Workshops curriculum
|
||||
- [ ] Documentation complete
|
||||
|
||||
### v1.2.0 (add)
|
||||
- [ ] 20+ quality packages at launch
|
||||
- [ ] 100+ downloads first month
|
||||
- [ ] 5+ community packages
|
||||
- [ ] Active ecosystem
|
||||
|
||||
---
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
### Phase 1: v1.1.0 (2-3 days)
|
||||
|
||||
**`weevil create` command:**
|
||||
- [ ] Template storage system
|
||||
- [ ] Variable substitution
|
||||
- [ ] Basic template
|
||||
- [ ] Testing template
|
||||
- [ ] Git initialization
|
||||
- [ ] Success/error messages
|
||||
- [ ] Documentation
|
||||
- [ ] Tests
|
||||
|
||||
### Phase 2: v1.2.0 (2-3 weeks)
|
||||
|
||||
**`weevil add` command:**
|
||||
- [ ] Package registry setup
|
||||
- [ ] Package manifest format
|
||||
- [ ] Dependency resolver
|
||||
- [ ] Conflict detection
|
||||
- [ ] File installation
|
||||
- [ ] Remove command
|
||||
- [ ] Search command
|
||||
- [ ] List command
|
||||
- [ ] 10+ launch packages
|
||||
- [ ] Documentation
|
||||
- [ ] Tests
|
||||
|
||||
---
|
||||
|
||||
## Initial Package Set (v1.2.0 Launch)
|
||||
|
||||
**Must Have (10 packages):**
|
||||
|
||||
1. nexus/hardware/dc-motor/complete
|
||||
2. nexus/hardware/servo/complete
|
||||
3. nexus/hardware/distance/complete
|
||||
4. nexus/hardware/imu/complete
|
||||
5. nexus/hardware/color-sensor/complete
|
||||
6. nexus/subsystems/wall-approach/complete
|
||||
7. nexus/subsystems/turn-controller/complete
|
||||
8. nexus/testing/mock-hardware
|
||||
9. nexus/examples/autonomous/simple-auto
|
||||
10. nexus/examples/teleop/basic-drive
|
||||
|
||||
**Nice to Have (+5):**
|
||||
|
||||
11. nexus/hardware/mecanum-drive/complete
|
||||
12. nexus/subsystems/april-tag/complete
|
||||
13. nexus/examples/autonomous/complex-auto
|
||||
14. nexus/utilities/telemetry/dashboard
|
||||
15. nexus/testing/test-patterns
|
||||
|
||||
---
|
||||
|
||||
## Package Quality Standards
|
||||
|
||||
**Required (All Packages):**
|
||||
- ✅ Valid package.toml
|
||||
- ✅ License specified
|
||||
- ✅ README included
|
||||
- ✅ Compiles without errors
|
||||
|
||||
**Recommended (Verified Badge):**
|
||||
- ✅ Tests included
|
||||
- ✅ Comprehensive docs
|
||||
- ✅ Interface-based design
|
||||
- ✅ No hardcoded values
|
||||
- ✅ Follows naming conventions
|
||||
|
||||
**Nexus Verified:**
|
||||
- All required + recommended
|
||||
- Professional code quality
|
||||
- Full test coverage
|
||||
- Maintained and supported
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### v1.3.0+
|
||||
- Package signing (GPG)
|
||||
- Private registries
|
||||
- `weevil publish` command
|
||||
- Package mirrors
|
||||
- Offline mode
|
||||
|
||||
### v2.0.0+
|
||||
- Binary packages
|
||||
- Pre-built libraries
|
||||
- Cloud builds
|
||||
- Team collaboration features
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Versioning:** How handle breaking changes?
|
||||
2. **Testing:** Require tests in packages?
|
||||
3. **Licensing:** Enforce compliance?
|
||||
4. **Moderation:** Who approves packages?
|
||||
5. **Private packages:** Support team-private?
|
||||
6. **Namespaces:** Use team numbers?
|
||||
7. **Binary support:** Allow compiled code?
|
||||
8. **Update notifications:** Alert on updates?
|
||||
9. **Code signing:** Security/trust model?
|
||||
10. **Monorepo:** Where store templates/packages?
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **npm** - Node package manager (scopes, registry)
|
||||
- **Cargo** - Rust package manager (semver, crates.io)
|
||||
- **FreeBSD Ports** - Package system inspiration
|
||||
- **Maven Central** - Java repository patterns
|
||||
- **Homebrew** - macOS package management
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Complete Examples
|
||||
|
||||
### Example 1: Creating Project
|
||||
|
||||
```bash
|
||||
$ weevil create my-robot --template testing
|
||||
|
||||
Creating FTC project 'my-robot'...
|
||||
|
||||
✓ Created directory structure
|
||||
✓ Generated source files (17 files)
|
||||
✓ Generated test files (4 files)
|
||||
✓ Created build configuration
|
||||
✓ Generated documentation (6 files)
|
||||
✓ Initialized Git repository
|
||||
|
||||
Project created successfully!
|
||||
|
||||
Next steps:
|
||||
cd my-robot
|
||||
weevil test # 45 tests pass in < 2 sec
|
||||
weevil build # Build APK
|
||||
weevil deploy --auto # Deploy to robot
|
||||
|
||||
Documentation:
|
||||
README.md - Overview
|
||||
DESIGN_AND_TEST_PLAN.md - Architecture
|
||||
TESTING_GUIDE.md - How to test
|
||||
```
|
||||
|
||||
### Example 2: Adding Package
|
||||
|
||||
```bash
|
||||
$ cd my-robot
|
||||
$ weevil add nexus/subsystems/mecanum-drive/complete
|
||||
|
||||
Resolving dependencies...
|
||||
|
||||
Package nexus/subsystems/mecanum-drive/complete requires:
|
||||
- nexus/hardware/dc-motor/core (v1.2.0)
|
||||
- nexus/hardware/imu/core (v2.0.0)
|
||||
|
||||
Installing 3 packages:
|
||||
1. nexus/hardware/dc-motor/core (v1.2.0)
|
||||
2. nexus/hardware/imu/core (v2.0.0)
|
||||
3. nexus/subsystems/mecanum-drive/complete (v2.1.0)
|
||||
|
||||
✓ Added 12 source files
|
||||
✓ Added 8 test files
|
||||
✓ Updated build.gradle
|
||||
|
||||
Package installed successfully!
|
||||
|
||||
Next steps:
|
||||
See docs/subsystems/MECANUM_DRIVE.md for usage
|
||||
Run weevil test to verify integration
|
||||
```
|
||||
|
||||
### Example 3: Handling Conflicts
|
||||
|
||||
```bash
|
||||
$ weevil add nexus/hardware/dc-motor/core
|
||||
|
||||
⚠ File conflict detected:
|
||||
src/main/java/robot/hardware/MotorController.java (exists)
|
||||
|
||||
Options:
|
||||
1. Skip conflicting files (safe, default)
|
||||
2. Overwrite conflicting files (--force)
|
||||
3. Interactive resolution (--interactive)
|
||||
4. Abort
|
||||
|
||||
Choice [1]: 1
|
||||
|
||||
Added:
|
||||
✓ src/main/java/robot/hardware/FtcMotorController.java
|
||||
✓ docs/hardware/DC_MOTOR.md
|
||||
|
||||
Skipped:
|
||||
⊗ src/main/java/robot/hardware/MotorController.java
|
||||
|
||||
Package partially installed (1 file skipped)
|
||||
|
||||
Note: Package may not work correctly if required files skipped
|
||||
Consider using --force or --interactive for complete installation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*End of Specification*
|
||||
|
||||
**Status:** Ready for Implementation
|
||||
**Next Steps:**
|
||||
1. Implement `weevil create` for v1.1.0-beta.3
|
||||
2. Use testing showcase as `testing` template
|
||||
3. Plan v1.2.0 package system
|
||||
|
||||
**Contact:** eric@intrepidfusion.com
|
||||
**Organization:** Nexus Workshops LLC
|
||||
241
src/commands/create.rs
Normal file
241
src/commands/create.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use anyhow::{Result, Context, bail};
|
||||
use colored::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use chrono::Utc;
|
||||
use crate::templates::TemplateManager;
|
||||
|
||||
// Use Cargo's version macro instead of importing
|
||||
const WEEVIL_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub fn create_project(
|
||||
name: &str,
|
||||
template: Option<&str>,
|
||||
path: Option<&str>,
|
||||
force: bool,
|
||||
no_init: bool,
|
||||
dry_run: bool,
|
||||
) -> Result<()> {
|
||||
// Validate project name
|
||||
validate_project_name(name)?;
|
||||
|
||||
// Determine output directory
|
||||
let output_dir = if let Some(p) = path {
|
||||
PathBuf::from(p).join(name)
|
||||
} else {
|
||||
PathBuf::from(name)
|
||||
};
|
||||
|
||||
// Check if directory exists
|
||||
if output_dir.exists() && !force {
|
||||
bail!(
|
||||
"Directory '{}' already exists\nUse --force to overwrite",
|
||||
output_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
let template_name = template.unwrap_or("basic");
|
||||
|
||||
println!("{}", format!("Creating FTC project '{}' with template '{}'...", name, template_name).bright_cyan().bold());
|
||||
println!();
|
||||
|
||||
if dry_run {
|
||||
println!("{}", "DRY RUN - No files will be created".yellow().bold());
|
||||
println!();
|
||||
}
|
||||
|
||||
// Load template manager
|
||||
let template_mgr = TemplateManager::new()?;
|
||||
|
||||
// Verify template exists
|
||||
if !template_mgr.template_exists(template_name) {
|
||||
bail!(
|
||||
"Template '{}' not found\n\nAvailable templates:\n{}",
|
||||
template_name,
|
||||
template_mgr.list_templates().join("\n ")
|
||||
);
|
||||
}
|
||||
|
||||
// Prepare template context
|
||||
let context = TemplateContext {
|
||||
project_name: name.to_string(),
|
||||
package_name: name.to_lowercase().replace("-", "").replace("_", ""),
|
||||
creation_date: Utc::now().to_rfc3339(),
|
||||
weevil_version: WEEVIL_VERSION.to_string(),
|
||||
template_name: template_name.to_string(),
|
||||
};
|
||||
|
||||
if !dry_run {
|
||||
// Create output directory
|
||||
if force && output_dir.exists() {
|
||||
println!("{}", "⚠ Removing existing directory...".yellow());
|
||||
fs::remove_dir_all(&output_dir)?;
|
||||
}
|
||||
fs::create_dir_all(&output_dir)?;
|
||||
|
||||
// Extract template
|
||||
template_mgr.extract_template(template_name, &output_dir, &context)?;
|
||||
|
||||
println!("{}", "✓ Created directory structure".green());
|
||||
|
||||
// Initialize git repository
|
||||
if !no_init {
|
||||
init_git_repository(&output_dir, template_name)?;
|
||||
}
|
||||
|
||||
println!();
|
||||
print_success_message(name, template_name);
|
||||
} else {
|
||||
println!("{}", format!("Would create project '{}' using template '{}'", name, template_name).bright_white());
|
||||
println!("{}", format!("Output directory: {}", output_dir.display()).bright_white());
|
||||
template_mgr.show_template_info(template_name)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_templates() -> Result<()> {
|
||||
let template_mgr = TemplateManager::new()?;
|
||||
|
||||
println!("{}", "Available templates:".bright_cyan().bold());
|
||||
println!();
|
||||
|
||||
for info in template_mgr.get_template_info_all()? {
|
||||
println!("{}", format!(" {} {}", info.name, if info.is_default { "(default)" } else { "" }).bright_white().bold());
|
||||
println!(" {}", info.description);
|
||||
println!(" Files: {} | Lines: ~{}", info.file_count, info.line_count);
|
||||
if info.test_count > 0 {
|
||||
println!(" Tests: {} tests", info.test_count);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("Use: weevil create <n> --template <template>");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_template(template_name: &str) -> Result<()> {
|
||||
let template_mgr = TemplateManager::new()?;
|
||||
|
||||
if !template_mgr.template_exists(template_name) {
|
||||
bail!("Template '{}' not found", template_name);
|
||||
}
|
||||
|
||||
template_mgr.show_template_info(template_name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_project_name(name: &str) -> Result<()> {
|
||||
if name.is_empty() {
|
||||
bail!("Project name cannot be empty");
|
||||
}
|
||||
|
||||
if name.len() > 50 {
|
||||
bail!("Project name must be 50 characters or less");
|
||||
}
|
||||
|
||||
// Check for valid characters
|
||||
let valid = name.chars().all(|c| {
|
||||
c.is_alphanumeric() || c == '-' || c == '_'
|
||||
});
|
||||
|
||||
if !valid {
|
||||
bail!(
|
||||
"Invalid project name '{}'\nProject names must:\n - Contain only letters, numbers, hyphens, underscores\n - Start with a letter\n - Be 1-50 characters long\n\nValid examples:\n my-robot\n team1234_robot\n competitionBot2024",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
// Must start with letter
|
||||
if !name.chars().next().unwrap().is_alphabetic() {
|
||||
bail!("Project name must start with a letter");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_git_repository(project_dir: &Path, template_name: &str) -> Result<()> {
|
||||
use std::process::Command;
|
||||
|
||||
// Check if git is available
|
||||
let git_check = Command::new("git")
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
if git_check.is_err() {
|
||||
println!("{}", "⚠ Git not found, skipping repository initialization".yellow());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Initialize repository
|
||||
let status = Command::new("git")
|
||||
.arg("init")
|
||||
.current_dir(project_dir)
|
||||
.output()
|
||||
.context("Failed to initialize git repository")?;
|
||||
|
||||
if !status.status.success() {
|
||||
println!("{}", "⚠ Failed to initialize git repository".yellow());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create initial commit
|
||||
Command::new("git")
|
||||
.args(&["add", "."])
|
||||
.current_dir(project_dir)
|
||||
.output()
|
||||
.ok();
|
||||
|
||||
let commit_message = format!("Initial commit from weevil create --template {}", template_name);
|
||||
Command::new("git")
|
||||
.args(&["commit", "-m", &commit_message])
|
||||
.current_dir(project_dir)
|
||||
.output()
|
||||
.ok();
|
||||
|
||||
println!("{}", "✓ Initialized Git repository".green());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_success_message(project_name: &str, template_name: &str) {
|
||||
println!("{}", "Project created successfully!".bright_green().bold());
|
||||
println!();
|
||||
println!("{}", "Next steps:".bright_cyan().bold());
|
||||
println!(" cd {}", project_name);
|
||||
|
||||
match template_name {
|
||||
"testing" => {
|
||||
println!(" weevil test # Run 45 tests (< 2 seconds)");
|
||||
println!(" weevil build # Build APK for robot");
|
||||
println!(" weevil deploy --auto # Deploy to robot");
|
||||
println!();
|
||||
println!("{}", "Documentation:".bright_cyan().bold());
|
||||
println!(" README.md - Project overview");
|
||||
println!(" DESIGN_AND_TEST_PLAN.md - System architecture");
|
||||
println!(" TESTING_GUIDE.md - How to write tests");
|
||||
}
|
||||
"basic" => {
|
||||
println!(" weevil setup # Setup development environment");
|
||||
println!(" weevil build # Build APK for robot");
|
||||
println!(" weevil deploy # Deploy to robot");
|
||||
}
|
||||
_ => {
|
||||
println!(" weevil setup # Setup development environment");
|
||||
println!(" weevil build # Build your robot code");
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", format!("Learn more: https://docs.weevil.dev/templates/{}", template_name).bright_black());
|
||||
}
|
||||
|
||||
pub struct TemplateContext {
|
||||
pub project_name: String,
|
||||
pub package_name: String,
|
||||
pub creation_date: String,
|
||||
pub weevil_version: String,
|
||||
pub template_name: String,
|
||||
}
|
||||
@@ -5,4 +5,5 @@ pub mod sdk;
|
||||
pub mod config;
|
||||
pub mod setup;
|
||||
pub mod doctor;
|
||||
pub mod uninstall;
|
||||
pub mod uninstall;
|
||||
pub mod create;
|
||||
67
src/main.rs
67
src/main.rs
@@ -3,12 +3,6 @@ use colored::*;
|
||||
use anyhow::Result;
|
||||
use weevil::version::WEEVIL_VERSION;
|
||||
|
||||
// Import ProxyConfig through our own `mod sdk`, not through the `weevil`
|
||||
// library crate. Both re-export the same source, but Rust treats
|
||||
// `weevil::sdk::proxy::ProxyConfig` and `sdk::proxy::ProxyConfig` as
|
||||
// distinct types when a binary and its lib are compiled together.
|
||||
// The command modules already see the local-mod version, so main must match.
|
||||
|
||||
mod commands;
|
||||
mod sdk;
|
||||
mod project;
|
||||
@@ -39,7 +33,41 @@ struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Create a new FTC robot project
|
||||
/// Create a new FTC robot project from a template
|
||||
Create {
|
||||
/// Name of the project to create
|
||||
name: String,
|
||||
|
||||
/// Template to use (basic, testing)
|
||||
#[arg(long, short = 't', value_name = "TEMPLATE")]
|
||||
template: Option<String>,
|
||||
|
||||
/// Parent directory for the project (project will be created in <path>/<name>)
|
||||
#[arg(long, value_name = "PATH")]
|
||||
path: Option<String>,
|
||||
|
||||
/// Overwrite existing directory
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
|
||||
/// Don't initialize git repository
|
||||
#[arg(long)]
|
||||
no_init: bool,
|
||||
|
||||
/// Show what would be created without creating
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// List available templates
|
||||
#[arg(long, exclusive = true)]
|
||||
list_templates: bool,
|
||||
|
||||
/// Show detailed information about a template
|
||||
#[arg(long, value_name = "TEMPLATE", exclusive = true)]
|
||||
show_template: Option<String>,
|
||||
},
|
||||
|
||||
/// Create a new FTC robot project (legacy - use 'create' instead)
|
||||
New {
|
||||
/// Name of the robot project
|
||||
name: String,
|
||||
@@ -139,6 +167,31 @@ fn main() -> Result<()> {
|
||||
let proxy = ProxyConfig::resolve(cli.proxy.as_deref(), cli.no_proxy)?;
|
||||
|
||||
match cli.command {
|
||||
Commands::Create {
|
||||
name,
|
||||
template,
|
||||
path,
|
||||
force,
|
||||
no_init,
|
||||
dry_run,
|
||||
list_templates,
|
||||
show_template
|
||||
} => {
|
||||
if list_templates {
|
||||
commands::create::list_templates()
|
||||
} else if let Some(tmpl) = show_template {
|
||||
commands::create::show_template(&tmpl)
|
||||
} else {
|
||||
commands::create::create_project(
|
||||
&name,
|
||||
template.as_deref(),
|
||||
path.as_deref(),
|
||||
force,
|
||||
no_init,
|
||||
dry_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
Commands::New { name, ftc_sdk, android_sdk } => {
|
||||
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref(), &proxy)
|
||||
}
|
||||
|
||||
@@ -1,101 +1,230 @@
|
||||
use include_dir::{include_dir, Dir};
|
||||
use std::path::Path;
|
||||
use anyhow::{Result, Context};
|
||||
use tera::{Tera, Context as TeraContext};
|
||||
use anyhow::{Result, Context, bail};
|
||||
use tera::Tera;
|
||||
use std::fs;
|
||||
use colored::*;
|
||||
|
||||
// Embed all template files at compile time
|
||||
static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||
// Embed template directories at compile time
|
||||
static BASIC_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/basic");
|
||||
static TESTING_TEMPLATE: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates/testing");
|
||||
|
||||
pub struct TemplateEngine {
|
||||
pub struct TemplateManager {
|
||||
#[allow(dead_code)]
|
||||
tera: Tera,
|
||||
}
|
||||
|
||||
impl TemplateEngine {
|
||||
#[allow(dead_code)]
|
||||
pub struct TemplateInfo {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub file_count: usize,
|
||||
pub line_count: usize,
|
||||
pub test_count: usize,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
impl TemplateManager {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Load all templates from embedded directory
|
||||
for file in TEMPLATES_DIR.files() {
|
||||
let path = file.path().to_string_lossy();
|
||||
let contents = file.contents_utf8()
|
||||
.context("Template must be valid UTF-8")?;
|
||||
tera.add_raw_template(&path, contents)?;
|
||||
}
|
||||
|
||||
let tera = Tera::default();
|
||||
Ok(Self { tera })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn render_to_file(
|
||||
pub fn template_exists(&self, name: &str) -> bool {
|
||||
matches!(name, "basic" | "testing")
|
||||
}
|
||||
|
||||
pub fn list_templates(&self) -> Vec<String> {
|
||||
vec![
|
||||
" basic - Minimal FTC project (default)".to_string(),
|
||||
" testing - Testing showcase with examples".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_template_info_all(&self) -> Result<Vec<TemplateInfo>> {
|
||||
Ok(vec![
|
||||
TemplateInfo {
|
||||
name: "basic".to_string(),
|
||||
description: "Minimal FTC project structure".to_string(),
|
||||
file_count: 10,
|
||||
line_count: 50,
|
||||
test_count: 0,
|
||||
is_default: true,
|
||||
},
|
||||
TemplateInfo {
|
||||
name: "testing".to_string(),
|
||||
description: "Professional testing showcase with examples".to_string(),
|
||||
file_count: 30,
|
||||
line_count: 2500,
|
||||
test_count: 45,
|
||||
is_default: false,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
pub fn show_template_info(&self, name: &str) -> Result<()> {
|
||||
let info = match name {
|
||||
"basic" => TemplateInfo {
|
||||
name: "basic".to_string(),
|
||||
description: "Minimal FTC project with clean structure".to_string(),
|
||||
file_count: 10,
|
||||
line_count: 50,
|
||||
test_count: 0,
|
||||
is_default: true,
|
||||
},
|
||||
"testing" => TemplateInfo {
|
||||
name: "testing".to_string(),
|
||||
description: "Comprehensive testing showcase demonstrating professional robotics software engineering practices.".to_string(),
|
||||
file_count: 30,
|
||||
line_count: 2500,
|
||||
test_count: 45,
|
||||
is_default: false,
|
||||
},
|
||||
_ => bail!("Unknown template: {}", name),
|
||||
};
|
||||
|
||||
println!("{}", format!("Template: {}", info.name).bright_cyan().bold());
|
||||
println!();
|
||||
println!("{}", "Description:".bright_white().bold());
|
||||
println!(" {}", info.description);
|
||||
println!();
|
||||
|
||||
if info.name == "testing" {
|
||||
println!("{}", "Features:".bright_white().bold());
|
||||
println!(" • Three complete subsystems with full test coverage");
|
||||
println!(" • Hardware abstraction layer with mocks");
|
||||
println!(" • 45 passing tests (unit, integration, system)");
|
||||
println!(" • Comprehensive documentation (6 files)");
|
||||
println!(" • Ready to use as learning material");
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("{}", "Files included:".bright_white().bold());
|
||||
println!(" {} files", info.file_count);
|
||||
println!(" ~{} lines of code", info.line_count);
|
||||
if info.test_count > 0 {
|
||||
println!(" {} tests", info.test_count);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("{}", "Example usage:".bright_white().bold());
|
||||
println!(" weevil create my-robot --template {}", info.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn extract_template(
|
||||
&self,
|
||||
template_name: &str,
|
||||
output_path: &Path,
|
||||
context: &TeraContext,
|
||||
output_dir: &Path,
|
||||
context: &crate::commands::create::TemplateContext,
|
||||
) -> Result<()> {
|
||||
let rendered = self.tera.render(template_name, context)?;
|
||||
let template_dir = match template_name {
|
||||
"basic" => &BASIC_TEMPLATE,
|
||||
"testing" => &TESTING_TEMPLATE,
|
||||
_ => bail!("Unknown template: {}", template_name),
|
||||
};
|
||||
|
||||
if let Some(parent) = output_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
// Extract all files from template
|
||||
self.extract_dir_recursively(template_dir, output_dir, "", context)?;
|
||||
|
||||
fs::write(output_path, rendered)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn extract_static_file(&self, template_path: &str, output_path: &Path) -> Result<()> {
|
||||
let file = TEMPLATES_DIR
|
||||
.get_file(template_path)
|
||||
.context(format!("Template not found: {}", template_path))?;
|
||||
|
||||
if let Some(parent) = output_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
fn extract_dir_recursively(
|
||||
&self,
|
||||
source: &Dir,
|
||||
output_base: &Path,
|
||||
relative_path: &str,
|
||||
context: &crate::commands::create::TemplateContext,
|
||||
) -> Result<()> {
|
||||
// Process files in current directory
|
||||
for file in source.files() {
|
||||
let file_path = file.path();
|
||||
let file_name = file_path.file_name().unwrap().to_string_lossy();
|
||||
|
||||
let output_path = if relative_path.is_empty() {
|
||||
output_base.join(&*file_name)
|
||||
} else {
|
||||
output_base.join(relative_path).join(&*file_name)
|
||||
};
|
||||
|
||||
// Create parent directory if needed
|
||||
if let Some(parent) = output_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Process file based on extension
|
||||
if file_name.ends_with(".template") {
|
||||
// Template file - process variables
|
||||
let contents = file.contents_utf8()
|
||||
.context("Template file must be UTF-8")?;
|
||||
|
||||
let processed = self.process_template_string(contents, context)?;
|
||||
|
||||
// Remove .template extension from output
|
||||
let output_name = file_name.trim_end_matches(".template");
|
||||
let final_path = output_path.with_file_name(output_name);
|
||||
|
||||
fs::write(final_path, processed)?;
|
||||
} else {
|
||||
// Regular file - copy as-is
|
||||
fs::write(&output_path, file.contents())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Process subdirectories
|
||||
for dir in source.dirs() {
|
||||
let dir_name = dir.path().file_name().unwrap().to_string_lossy();
|
||||
let new_relative = if relative_path.is_empty() {
|
||||
dir_name.to_string()
|
||||
} else {
|
||||
format!("{}/{}", relative_path, dir_name)
|
||||
};
|
||||
|
||||
self.extract_dir_recursively(dir, output_base, &new_relative, context)?;
|
||||
}
|
||||
|
||||
fs::write(output_path, file.contents())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn list_templates(&self) -> Vec<String> {
|
||||
TEMPLATES_DIR
|
||||
.files()
|
||||
.map(|f| f.path().to_string_lossy().to_string())
|
||||
.collect()
|
||||
fn process_template_string(
|
||||
&self,
|
||||
template: &str,
|
||||
context: &crate::commands::create::TemplateContext,
|
||||
) -> Result<String> {
|
||||
let processed = template
|
||||
.replace("{{PROJECT_NAME}}", &context.project_name)
|
||||
.replace("{{PACKAGE_NAME}}", &context.package_name)
|
||||
.replace("{{CREATION_DATE}}", &context.creation_date)
|
||||
.replace("{{WEEVIL_VERSION}}", &context.weevil_version)
|
||||
.replace("{{TEMPLATE_NAME}}", &context.template_name);
|
||||
|
||||
Ok(processed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_template_engine_creation() {
|
||||
let engine = TemplateEngine::new();
|
||||
assert!(engine.is_ok());
|
||||
fn test_template_manager_creation() {
|
||||
let mgr = TemplateManager::new();
|
||||
assert!(mgr.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_exists() {
|
||||
let mgr = TemplateManager::new().unwrap();
|
||||
assert!(mgr.template_exists("basic"));
|
||||
assert!(mgr.template_exists("testing"));
|
||||
assert!(!mgr.template_exists("nonexistent"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_templates() {
|
||||
let engine = TemplateEngine::new().unwrap();
|
||||
let templates = engine.list_templates();
|
||||
assert!(!templates.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_template() {
|
||||
let _engine = TemplateEngine::new().unwrap();
|
||||
let temp = TempDir::new().unwrap();
|
||||
let _output = temp.path().join("test.txt");
|
||||
|
||||
let mut context = TeraContext::new();
|
||||
context.insert("project_name", "TestRobot");
|
||||
|
||||
// This will fail until we add templates, but shows the pattern
|
||||
// engine.render_to_file("README.md", &output, &context).unwrap();
|
||||
let mgr = TemplateManager::new().unwrap();
|
||||
let templates = mgr.list_templates();
|
||||
assert_eq!(templates.len(), 2);
|
||||
}
|
||||
}
|
||||
1
templates/basic/.gitkeep
Normal file
1
templates/basic/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
templates/testing/.gitkeep
Normal file
1
templates/testing/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user