635 lines
12 KiB
Markdown
635 lines
12 KiB
Markdown
# Ruby Gem Scaffolder Skill
|
|
|
|
Intelligent gem creation and scaffolding following Ruby best practices.
|
|
|
|
## When to Activate
|
|
|
|
This skill activates when:
|
|
- User requests to create a new gem
|
|
- User asks to scaffold a Ruby library
|
|
- User wants to extract code into a gem
|
|
- User mentions "gemspec" or "gem structure"
|
|
|
|
## Core Capabilities
|
|
|
|
### 1. Create New Gem
|
|
|
|
**Using Bundler (Recommended):**
|
|
```bash
|
|
bundle gem gem_name
|
|
|
|
# With RSpec
|
|
bundle gem gem_name --test=rspec
|
|
|
|
# With MIT license
|
|
bundle gem gem_name --mit
|
|
|
|
# With code of conduct
|
|
bundle gem gem_name --coc
|
|
|
|
# All together
|
|
bundle gem gem_name --test=rspec --mit --coc
|
|
```
|
|
|
|
**Interactive Creation:**
|
|
|
|
When user requests: "Create a new gem called my_awesome_gem"
|
|
|
|
Ask clarifying questions:
|
|
1. Test framework? (rspec/minitest)
|
|
2. License? (MIT/Apache-2.0/GPL-3.0)
|
|
3. CI? (GitHub Actions/CircleCI/None)
|
|
4. Code of Conduct? (yes/no)
|
|
|
|
Then scaffold appropriately.
|
|
|
|
### 2. Standard Gem Structure
|
|
|
|
```
|
|
my_gem/
|
|
├── .github/
|
|
│ └── workflows/
|
|
│ └── ci.yml # GitHub Actions CI
|
|
├── lib/
|
|
│ ├── my_gem/
|
|
│ │ └── version.rb # Version constant
|
|
│ └── my_gem.rb # Main entry point
|
|
├── spec/
|
|
│ ├── spec_helper.rb # RSpec configuration
|
|
│ └── my_gem_spec.rb # Tests
|
|
├── .gitignore # Git ignores
|
|
├── .rubocop.yml # RuboCop config
|
|
├── CHANGELOG.md # Version history
|
|
├── CODE_OF_CONDUCT.md # Community guidelines
|
|
├── Gemfile # Development dependencies
|
|
├── LICENSE.txt # License text
|
|
├── README.md # Documentation
|
|
├── Rakefile # Rake tasks
|
|
└── my_gem.gemspec # Gem specification
|
|
```
|
|
|
|
### 3. Generate Gemspec
|
|
|
|
**Template gemspec:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
require_relative "lib/my_gem/version"
|
|
|
|
Gem::Specification.new do |spec|
|
|
spec.name = "my_gem"
|
|
spec.version = MyGem::VERSION
|
|
spec.authors = ["Your Name"]
|
|
spec.email = ["your.email@example.com"]
|
|
|
|
spec.summary = "Brief description of your gem"
|
|
spec.description = "Longer description of what your gem does"
|
|
spec.homepage = "https://github.com/username/my_gem"
|
|
spec.license = "MIT"
|
|
spec.required_ruby_version = ">= 3.0.0"
|
|
|
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
spec.metadata["source_code_uri"] = "https://github.com/username/my_gem"
|
|
spec.metadata["changelog_uri"] = "https://github.com/username/my_gem/blob/main/CHANGELOG.md"
|
|
|
|
# Specify which files should be added to the gem when it is released.
|
|
spec.files = Dir.glob("lib/**/*") + %w[
|
|
README.md
|
|
LICENSE.txt
|
|
CHANGELOG.md
|
|
]
|
|
spec.require_paths = ["lib"]
|
|
|
|
# Runtime dependencies
|
|
# spec.add_dependency "example-gem", "~> 1.0"
|
|
|
|
# Development dependencies
|
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
spec.add_development_dependency "rspec", "~> 3.12"
|
|
spec.add_development_dependency "rubocop", "~> 1.50"
|
|
end
|
|
```
|
|
|
|
### 4. Create Main Entry Point
|
|
|
|
**lib/my_gem.rb:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
require_relative "my_gem/version"
|
|
|
|
module MyGem
|
|
class Error < StandardError; end
|
|
|
|
# Your code goes here...
|
|
|
|
# Optional: Configuration
|
|
class << self
|
|
attr_accessor :configuration
|
|
end
|
|
|
|
def self.configure
|
|
self.configuration ||= Configuration.new
|
|
yield(configuration)
|
|
end
|
|
|
|
class Configuration
|
|
attr_accessor :option1, :option2
|
|
|
|
def initialize
|
|
@option1 = "default_value"
|
|
@option2 = "default_value"
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
**lib/my_gem/version.rb:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
module MyGem
|
|
VERSION = "0.1.0"
|
|
end
|
|
```
|
|
|
|
### 5. Set Up Testing
|
|
|
|
**spec/spec_helper.rb:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
require "my_gem"
|
|
|
|
RSpec.configure do |config|
|
|
# Enable flags like --only-failures and --next-failure
|
|
config.example_status_persistence_file_path = ".rspec_status"
|
|
|
|
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
config.disable_monkey_patching!
|
|
|
|
config.expect_with :rspec do |c|
|
|
c.syntax = :expect
|
|
end
|
|
end
|
|
```
|
|
|
|
**spec/my_gem_spec.rb:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
RSpec.describe MyGem do
|
|
it "has a version number" do
|
|
expect(MyGem::VERSION).not_to be nil
|
|
end
|
|
|
|
describe ".configure" do
|
|
it "yields configuration block" do
|
|
MyGem.configure do |config|
|
|
config.option1 = "custom_value"
|
|
end
|
|
|
|
expect(MyGem.configuration.option1).to eq("custom_value")
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 6. Add Rake Tasks
|
|
|
|
**Rakefile:**
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
require "bundler/gem_tasks"
|
|
require "rspec/core/rake_task"
|
|
require "rubocop/rake_task"
|
|
|
|
RSpec::Core::RakeTask.new(:spec)
|
|
RuboCop::RakeTask.new
|
|
|
|
task default: %i[spec rubocop]
|
|
```
|
|
|
|
**Usage:**
|
|
```bash
|
|
rake spec # Run tests
|
|
rake rubocop # Run linter
|
|
rake # Run both (default)
|
|
rake build # Build gem
|
|
rake install # Install gem locally
|
|
rake release # Release to RubyGems
|
|
```
|
|
|
|
### 7. CI Configuration
|
|
|
|
**GitHub Actions (.github/workflows/ci.yml):**
|
|
```yaml
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
pull_request:
|
|
branches: [ main ]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
ruby-version: ['3.0', '3.1', '3.2']
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Set up Ruby
|
|
uses: ruby/setup-ruby@v1
|
|
with:
|
|
ruby-version: ${{ matrix.ruby-version }}
|
|
bundler-cache: true
|
|
|
|
- name: Run tests
|
|
run: bundle exec rake spec
|
|
|
|
- name: Run RuboCop
|
|
run: bundle exec rake rubocop
|
|
```
|
|
|
|
### 8. Documentation Templates
|
|
|
|
**README.md structure:**
|
|
```markdown
|
|
# MyGem
|
|
|
|
Brief description of what your gem does.
|
|
|
|
## Installation
|
|
|
|
Add this line to your application's Gemfile:
|
|
|
|
```ruby
|
|
gem 'my_gem'
|
|
```
|
|
|
|
And then execute:
|
|
|
|
$ bundle install
|
|
|
|
Or install it yourself as:
|
|
|
|
$ gem install my_gem
|
|
|
|
## Usage
|
|
|
|
```ruby
|
|
require 'my_gem'
|
|
|
|
# Basic usage
|
|
MyGem.do_something
|
|
|
|
# With configuration
|
|
MyGem.configure do |config|
|
|
config.option1 = "value"
|
|
end
|
|
```
|
|
|
|
## Development
|
|
|
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
|
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
|
## Contributing
|
|
|
|
Bug reports and pull requests are welcome on GitHub at https://github.com/username/my_gem.
|
|
|
|
## License
|
|
|
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
```
|
|
|
|
**CHANGELOG.md structure:**
|
|
```markdown
|
|
# 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
|
|
- Initial release
|
|
- Core functionality
|
|
|
|
## [0.1.0] - 2025-01-15
|
|
|
|
### Added
|
|
- Initial release
|
|
```
|
|
|
|
### 9. Common Gem Patterns
|
|
|
|
**Pattern: CLI Gem**
|
|
```ruby
|
|
# lib/my_gem/cli.rb
|
|
require 'optparse'
|
|
|
|
module MyGem
|
|
class CLI
|
|
def self.start(args)
|
|
new(args).execute
|
|
end
|
|
|
|
def initialize(args)
|
|
@args = args
|
|
@options = {}
|
|
end
|
|
|
|
def execute
|
|
parse_options
|
|
# Your CLI logic here
|
|
end
|
|
|
|
private
|
|
|
|
def parse_options
|
|
OptionParser.new do |opts|
|
|
opts.banner = "Usage: my_gem [options]"
|
|
|
|
opts.on("-v", "--version", "Print version") do
|
|
puts MyGem::VERSION
|
|
exit
|
|
end
|
|
|
|
opts.on("-h", "--help", "Print help") do
|
|
puts opts
|
|
exit
|
|
end
|
|
end.parse!(@args)
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
**Pattern: Library with Data Objects**
|
|
```ruby
|
|
# lib/my_gem/data_object.rb
|
|
module MyGem
|
|
class DataObject
|
|
attr_reader :attribute1, :attribute2
|
|
|
|
def self.build(attributes)
|
|
new(
|
|
attribute1: attributes[:attribute1],
|
|
attribute2: attributes[:attribute2]
|
|
)
|
|
end
|
|
|
|
def initialize(attribute1:, attribute2:)
|
|
@attribute1 = attribute1
|
|
@attribute2 = attribute2
|
|
end
|
|
|
|
def to_h
|
|
{
|
|
attribute1: attribute1,
|
|
attribute2: attribute2
|
|
}
|
|
end
|
|
|
|
def to_json(*args)
|
|
to_h.to_json(*args)
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
**Pattern: Plugin System**
|
|
```ruby
|
|
# lib/my_gem/plugin.rb
|
|
module MyGem
|
|
module Plugin
|
|
def self.included(base)
|
|
base.extend(ClassMethods)
|
|
end
|
|
|
|
module ClassMethods
|
|
def register_plugin(name, klass)
|
|
plugins[name] = klass
|
|
end
|
|
|
|
def plugins
|
|
@plugins ||= {}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
### 10. Interactive Workflows
|
|
|
|
**Workflow: Create New Gem**
|
|
|
|
**User:** "Create a new gem called data_processor"
|
|
|
|
**Response:**
|
|
```
|
|
I'll help you create the data_processor gem. Let me ask a few questions:
|
|
|
|
1. Test framework: RSpec or Minitest? [RSpec]
|
|
2. License: MIT, Apache-2.0, GPL-3.0? [MIT]
|
|
3. Include GitHub Actions CI? [yes]
|
|
4. Include Code of Conduct? [yes]
|
|
|
|
Creating gem with:
|
|
- Test framework: RSpec
|
|
- License: MIT
|
|
- CI: GitHub Actions
|
|
- Code of Conduct: Yes
|
|
|
|
Running: bundle gem data_processor --test=rspec --mit --coc
|
|
|
|
✓ Created gem structure
|
|
✓ Initialized git repository
|
|
✓ Created initial commit
|
|
|
|
Next steps:
|
|
1. cd data_processor
|
|
2. Edit lib/data_processor.rb to add your code
|
|
3. Update data_processor.gemspec with description
|
|
4. Add tests in spec/
|
|
5. Run: bundle exec rake spec
|
|
|
|
Would you like me to:
|
|
- Generate a sample data object class?
|
|
- Set up CI configuration?
|
|
- Create example tests?
|
|
```
|
|
|
|
**Workflow: Add Class to Existing Gem**
|
|
|
|
**User:** "Add a Parser class to my gem"
|
|
|
|
**Response:**
|
|
```
|
|
I'll add a Parser class following POODR principles.
|
|
|
|
Creating:
|
|
1. lib/my_gem/parser.rb
|
|
2. spec/my_gem/parser_spec.rb
|
|
|
|
lib/my_gem/parser.rb:
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
module MyGem
|
|
class Parser
|
|
def initialize(input)
|
|
@input = input
|
|
end
|
|
|
|
def parse
|
|
# Implementation here
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :input
|
|
end
|
|
end
|
|
```
|
|
|
|
spec/my_gem/parser_spec.rb:
|
|
```ruby
|
|
# frozen_string_literal: true
|
|
|
|
RSpec.describe MyGem::Parser do
|
|
describe '#parse' do
|
|
it 'parses input correctly' do
|
|
parser = described_class.new("input")
|
|
result = parser.parse
|
|
|
|
expect(result).to eq("expected_output")
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
Don't forget to require it in lib/my_gem.rb:
|
|
```ruby
|
|
require_relative "my_gem/parser"
|
|
```
|
|
|
|
Would you like me to add the require statement?
|
|
```
|
|
|
|
### 11. Version Management
|
|
|
|
**Semantic Versioning Guidelines:**
|
|
|
|
```
|
|
MAJOR.MINOR.PATCH
|
|
|
|
MAJOR: Breaking changes
|
|
MINOR: New features (backward compatible)
|
|
PATCH: Bug fixes
|
|
```
|
|
|
|
**Update version:**
|
|
```ruby
|
|
# lib/my_gem/version.rb
|
|
module MyGem
|
|
VERSION = "1.2.3" # Update this
|
|
end
|
|
```
|
|
|
|
**Add to CHANGELOG.md:**
|
|
```markdown
|
|
## [1.2.3] - 2025-01-15
|
|
|
|
### Fixed
|
|
- Bug in parser when handling edge cases
|
|
```
|
|
|
|
### 12. Publishing Checklist
|
|
|
|
Before `rake release`:
|
|
|
|
- [ ] All tests passing
|
|
- [ ] RuboCop clean
|
|
- [ ] README updated
|
|
- [ ] CHANGELOG updated
|
|
- [ ] Version bumped
|
|
- [ ] Committed and pushed to GitHub
|
|
- [ ] RubyGems.org account configured
|
|
|
|
```bash
|
|
# First time setup
|
|
curl -u username https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials
|
|
chmod 0600 ~/.gem/credentials
|
|
|
|
# Release
|
|
rake release
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Follow Semantic Versioning** strictly
|
|
2. **Write comprehensive README** with examples
|
|
3. **Maintain CHANGELOG** for all versions
|
|
4. **Keep dependencies minimal** for gems
|
|
5. **Test on multiple Ruby versions** in CI
|
|
6. **Use frozen_string_literal** in all files
|
|
7. **Namespace your gem** to avoid conflicts
|
|
8. **Document public API** thoroughly
|
|
9. **Keep gemspec metadata** up to date
|
|
10. **Use pessimistic versioning** for dependencies
|
|
|
|
## Error Prevention
|
|
|
|
**Common Mistakes:**
|
|
|
|
1. **Missing files in gemspec**
|
|
```ruby
|
|
# Bad
|
|
spec.files = `git ls-files`.split("\n")
|
|
|
|
# Good
|
|
spec.files = Dir.glob("lib/**/*") + %w[README.md LICENSE.txt]
|
|
```
|
|
|
|
2. **Not specifying Ruby version**
|
|
```ruby
|
|
# Always specify
|
|
spec.required_ruby_version = ">= 3.0.0"
|
|
```
|
|
|
|
3. **Including development gems in gemspec**
|
|
```ruby
|
|
# Don't do this
|
|
spec.add_dependency "rspec" # This is for dev only!
|
|
|
|
# Do this
|
|
spec.add_development_dependency "rspec"
|
|
```
|
|
|
|
## Response Format
|
|
|
|
When scaffolding:
|
|
|
|
**Files Created:**
|
|
- List each file with brief description
|
|
|
|
**Next Steps:**
|
|
1. Specific actions to take
|
|
2. Commands to run
|
|
3. Files to edit
|
|
|
|
**Suggestions:**
|
|
- Patterns that might be useful
|
|
- Additional features to consider
|
|
- Testing strategies
|