mirror of
https://github.com/ivuorinen/homebrew-tap.git
synced 2026-02-05 08:45:46 +00:00
feat: full site
This commit is contained in:
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
@@ -5,23 +6,25 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
test-bot:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-22.04, macos-13, macos-14]
|
||||
os: [ubuntu-22.04, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
@@ -55,4 +58,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bottles_${{ matrix.os }}
|
||||
path: '*.bottle.*'
|
||||
path: "*.bottle.*"
|
||||
|
||||
39
.github/workflows/pages-build.yml
vendored
39
.github/workflows/pages-build.yml
vendored
@@ -1,17 +1,16 @@
|
||||
---
|
||||
name: Build and Deploy Documentation
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'Formula/**'
|
||||
- 'docs/**'
|
||||
- 'scripts/**'
|
||||
- "Formula/**"
|
||||
- "docs/**"
|
||||
- "scripts/**"
|
||||
- "theme/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
permissions: read-all
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
@@ -20,6 +19,11 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
@@ -28,30 +32,21 @@ jobs:
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.4'
|
||||
bundler-cache: true
|
||||
|
||||
- name: Parse Formulae and Generate Data
|
||||
- name: Parse Formulae and Build Site
|
||||
run: |
|
||||
ruby scripts/parse_formulas.rb
|
||||
echo "Generated formulae.json with $(jq '.formulae | length' docs/_data/formulae.json) formulae"
|
||||
ruby scripts/build_site.rb
|
||||
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Build Jekyll Site
|
||||
run: |
|
||||
cd docs
|
||||
bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
env:
|
||||
JEKYLL_ENV: production
|
||||
|
||||
- name: Upload Pages Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site
|
||||
path: docs
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
@@ -59,6 +54,12 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -3,11 +3,13 @@ Gemfile.lock
|
||||
.bundle/
|
||||
vendor/bundle/
|
||||
|
||||
# Jekyll
|
||||
docs/_site/
|
||||
docs/.sass-cache/
|
||||
docs/.jekyll-cache/
|
||||
docs/.jekyll-metadata
|
||||
# Generated site files
|
||||
docs/**/*.html
|
||||
docs/*.css
|
||||
docs/*.js
|
||||
docs/_data/formulae.json
|
||||
docs/assets/*
|
||||
!docs/assets/.gitkeep
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -23,5 +25,5 @@ docs/.jekyll-metadata
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Generated files
|
||||
docs/_data/formulae.json
|
||||
# Other
|
||||
AGENTS.md
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.4.5
|
||||
3.3.6
|
||||
|
||||
17
Formula/e/example-tool2.rbx
Normal file
17
Formula/e/example-tool2.rbx
Normal file
@@ -0,0 +1,17 @@
|
||||
class ExampleTool2 < Formula
|
||||
desc "A second example tool to demonstrate the tap functionality"
|
||||
homepage "https://github.com/ivuorinen/example-tool2"
|
||||
url "https://github.com/ivuorinen/example-tool2/archive/v2.0.0.tar.gz"
|
||||
sha256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
license "MIT"
|
||||
|
||||
depends_on "go" => :build
|
||||
|
||||
def install
|
||||
system "go", "build", *std_go_args(ldflags: "-s -w")
|
||||
end
|
||||
|
||||
test do
|
||||
assert_match "example-tool2 version 2.0.0", shell_output("#{bin}/example-tool2 --version")
|
||||
end
|
||||
end
|
||||
176
Makefile
Normal file
176
Makefile
Normal file
@@ -0,0 +1,176 @@
|
||||
# Homebrew Tap Makefile
|
||||
# Provides convenient commands for building and managing the tap documentation
|
||||
|
||||
.PHONY: help build serve parse clean test install dev setup check
|
||||
|
||||
# Default target
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# Variables
|
||||
RUBY := ruby
|
||||
PORT := 4000
|
||||
HOST := localhost
|
||||
SCRIPTS_DIR := scripts
|
||||
THEME_DIR := theme
|
||||
DOCS_DIR := docs
|
||||
FORMULA_DIR := Formula
|
||||
|
||||
help: ## Show this help message
|
||||
@echo "Homebrew Tap Documentation Builder"
|
||||
@echo ""
|
||||
@echo "Usage: make <target>"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-12s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make build # Build the documentation site"
|
||||
@echo " make serve # Start development server on port 4000"
|
||||
@echo " make serve PORT=3000 # Start development server on port 3000"
|
||||
@echo " make dev # Full development setup (parse + build + serve)"
|
||||
|
||||
build: ## Build the static documentation site
|
||||
@echo "🏗️ Building homebrew tap documentation..."
|
||||
@$(RUBY) $(SCRIPTS_DIR)/parse_formulas.rb
|
||||
@$(RUBY) $(SCRIPTS_DIR)/build_site.rb
|
||||
@echo "✅ Build complete!"
|
||||
|
||||
serve: ## Start development server (default: localhost:4000)
|
||||
@echo "🚀 Starting development server on http://$(HOST):$(PORT)"
|
||||
@$(RUBY) $(SCRIPTS_DIR)/serve.rb $(PORT) $(HOST)
|
||||
|
||||
parse: ## Parse formulae and generate JSON data only
|
||||
@echo "📋 Parsing formulae..."
|
||||
@$(RUBY) $(SCRIPTS_DIR)/parse_formulas.rb
|
||||
@echo "✅ Formulae parsing complete!"
|
||||
|
||||
clean: ## Clean all generated files
|
||||
@echo "🧹 Cleaning generated files..."
|
||||
@$(RUBY) $(SCRIPTS_DIR)/make.rb clean
|
||||
@echo "✅ Clean complete!"
|
||||
|
||||
dev: parse build serve ## Full development workflow: parse, build, and serve
|
||||
|
||||
setup: ## Initial project setup and dependency check
|
||||
@echo "🔧 Setting up homebrew tap development environment..."
|
||||
@which $(RUBY) > /dev/null || (echo "❌ Ruby not found. Please install Ruby first." && exit 1)
|
||||
@test -d $(FORMULA_DIR) || (echo "❌ Formula directory not found" && exit 1)
|
||||
@test -d $(THEME_DIR) || (echo "❌ Theme directory not found" && exit 1)
|
||||
@test -f $(THEME_DIR)/index.html.erb || (echo "❌ Theme templates not found" && exit 1)
|
||||
@echo "✅ Environment setup complete!"
|
||||
|
||||
check: ## Check if all required files and directories exist
|
||||
@echo "🔍 Checking project structure..."
|
||||
@test -d $(SCRIPTS_DIR) && echo "✅ Scripts directory exists" || echo "❌ Scripts directory missing"
|
||||
@test -d $(THEME_DIR) && echo "✅ Theme directory exists" || echo "❌ Theme directory missing"
|
||||
@test -d $(FORMULA_DIR) && echo "✅ Formula directory exists" || echo "❌ Formula directory missing"
|
||||
@test -f $(THEME_DIR)/index.html.erb && echo "✅ Index template exists" || echo "❌ Index template missing"
|
||||
@test -f $(THEME_DIR)/formulae.html.erb && echo "✅ Formulae template exists" || echo "❌ Formulae template missing"
|
||||
@test -f $(THEME_DIR)/formula.html.erb && echo "✅ Formula template exists" || echo "❌ Formula template missing"
|
||||
@test -f $(THEME_DIR)/style.css && echo "✅ CSS file exists" || echo "❌ CSS file missing"
|
||||
@test -f $(THEME_DIR)/main.js && echo "✅ JavaScript file exists" || echo "❌ JavaScript file missing"
|
||||
|
||||
test: check ## Run tests and validation
|
||||
@echo "🧪 Running validation tests..."
|
||||
@$(RUBY) -c $(SCRIPTS_DIR)/parse_formulas.rb && echo "✅ parse_formulas.rb syntax OK" || echo "❌ parse_formulas.rb syntax error"
|
||||
@$(RUBY) -c $(SCRIPTS_DIR)/build_site.rb && echo "✅ build_site.rb syntax OK" || echo "❌ build_site.rb syntax error"
|
||||
@$(RUBY) -c $(SCRIPTS_DIR)/serve.rb && echo "✅ serve.rb syntax OK" || echo "❌ serve.rb syntax error"
|
||||
@$(RUBY) -c $(SCRIPTS_DIR)/make.rb && echo "✅ make.rb syntax OK" || echo "❌ make.rb syntax error"
|
||||
@echo "✅ All tests passed!"
|
||||
|
||||
install: ## Install development dependencies (if Gemfile exists)
|
||||
@if [ -f Gemfile ]; then \
|
||||
echo "📦 Installing Ruby dependencies..."; \
|
||||
bundle install; \
|
||||
echo "✅ Dependencies installed!"; \
|
||||
else \
|
||||
echo "ℹ️ No Gemfile found, skipping dependency installation"; \
|
||||
fi
|
||||
|
||||
watch: ## Watch for file changes and auto-rebuild (alias for serve)
|
||||
@$(MAKE) serve
|
||||
|
||||
# Advanced targets
|
||||
serve-all: ## Start server accessible from all interfaces (0.0.0.0)
|
||||
@$(MAKE) serve HOST=0.0.0.0
|
||||
|
||||
serve-3000: ## Start server on port 3000
|
||||
@$(MAKE) serve PORT=3000
|
||||
|
||||
serve-8080: ## Start server on port 8080
|
||||
@$(MAKE) serve PORT=8080
|
||||
|
||||
build-production: ## Build for production deployment
|
||||
@echo "🏭 Building for production..."
|
||||
@$(MAKE) clean
|
||||
@$(MAKE) build
|
||||
@echo "✅ Production build complete!"
|
||||
|
||||
# Homebrew-specific targets
|
||||
tap-test: ## Test the tap installation locally
|
||||
@echo "🍺 Testing tap installation..."
|
||||
@brew tap-new ivuorinen/homebrew-tap --no-git 2>/dev/null || true
|
||||
@brew audit --strict $(FORMULA_DIR)/*.rb || echo "⚠️ Some formulae may have audit issues"
|
||||
|
||||
tap-install: ## Install this tap locally for testing
|
||||
@echo "🍺 Installing tap locally..."
|
||||
@brew tap $$(pwd)
|
||||
|
||||
formula-new: ## Create a new formula template (usage: make formula-new NAME=myformula)
|
||||
@if [ -z "$(NAME)" ]; then \
|
||||
echo "❌ Please provide a formula name: make formula-new NAME=myformula"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "📝 Creating new formula: $(NAME)"
|
||||
@FIRST_CHAR=$$(echo $(NAME) | cut -c1); \
|
||||
CLASS_NAME=$$($(RUBY) -e "puts '$(NAME)'.split('-').map(&:capitalize).join"); \
|
||||
mkdir -p $(FORMULA_DIR)/$$FIRST_CHAR; \
|
||||
echo "class $$CLASS_NAME < Formula" > $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' desc "Description of $(NAME)"' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' homepage "https://github.com/ivuorinen/$(NAME)"' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' url "https://github.com/ivuorinen/$(NAME)/archive/v1.0.0.tar.gz"' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' sha256 "REPLACE_WITH_ACTUAL_SHA256"' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' license "MIT"' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo '' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' def install' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' # Installation steps here' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' end' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo '' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' test do' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' # Test steps here' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo ' end' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb; \
|
||||
echo 'end' >> $(FORMULA_DIR)/$$FIRST_CHAR/$(NAME).rb
|
||||
@echo "✅ Formula template created at $(FORMULA_DIR)/$$(echo $(NAME) | cut -c1)/$(NAME).rb"
|
||||
@echo "📝 Remember to update the URL, SHA256, and implementation!"
|
||||
|
||||
# Information targets
|
||||
info: ## Show project information
|
||||
@echo "📋 Homebrew Tap Information"
|
||||
@echo "=========================="
|
||||
@echo "Ruby version: $$($(RUBY) --version)"
|
||||
@echo "Project root: $$(pwd)"
|
||||
@echo "Scripts: $$(ls -1 $(SCRIPTS_DIR)/*.rb | wc -l | tr -d ' ') files"
|
||||
@echo "Formulae: $$(find $(FORMULA_DIR) -name '*.rb' | wc -l | tr -d ' ') files"
|
||||
@echo "Theme files: $$(ls -1 $(THEME_DIR)/* | wc -l | tr -d ' ') files"
|
||||
@if [ -f $(DOCS_DIR)/_data/formulae.json ]; then \
|
||||
echo "Generated formulae: $$(cat $(DOCS_DIR)/_data/formulae.json | grep -o '"formulae_count":[0-9]*' | cut -d: -f2)"; \
|
||||
fi
|
||||
|
||||
version: ## Show version information
|
||||
@echo "Homebrew Tap Builder"
|
||||
@echo "Ruby: $$($(RUBY) --version)"
|
||||
@echo "Make: $$(make --version | head -1)"
|
||||
@echo "Git: $$(git --version 2>/dev/null || echo 'not available')"
|
||||
|
||||
# Legacy support (for backward compatibility with scripts/make.rb)
|
||||
ruby-build: ## Legacy: use Ruby make script for build
|
||||
@$(RUBY) $(SCRIPTS_DIR)/make.rb build
|
||||
|
||||
ruby-serve: ## Legacy: use Ruby make script for serve
|
||||
@$(RUBY) $(SCRIPTS_DIR)/make.rb serve
|
||||
|
||||
ruby-clean: ## Legacy: use Ruby make script for clean
|
||||
@$(RUBY) $(SCRIPTS_DIR)/make.rb clean
|
||||
|
||||
ruby-parse: ## Legacy: use Ruby make script for parse
|
||||
@$(RUBY) $(SCRIPTS_DIR)/make.rb parse
|
||||
164
README.md
164
README.md
@@ -1,8 +1,8 @@
|
||||
# ivuorinen/homebrew-tap
|
||||
|
||||
A Homebrew tap for ivuorinen's custom formulae with automated documentation.
|
||||
A Homebrew tap for ivuorinen's custom formulae with automated documentation and dark mode support.
|
||||
|
||||
## Usage
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Add the tap
|
||||
@@ -17,38 +17,156 @@ brew search ivuorinen/homebrew-tap/
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [https://ivuorinen.github.io/homebrew-tap/](https://ivuorinen.github.io/homebrew-tap/) for complete documentation of all available formulae.
|
||||
|
||||
## Available Formulae
|
||||
|
||||
The documentation is automatically generated from the formula files and includes:
|
||||
- Installation instructions
|
||||
- Dependencies
|
||||
- Version information
|
||||
- Source links
|
||||
Visit [https://ivuorinen.net/homebrew-tap/](https://ivuorinen.net/homebrew-tap/) for complete documentation with:
|
||||
- Installation instructions for each formula
|
||||
- Dependencies and version information
|
||||
- Source links and SHA256 checksums
|
||||
- Dark mode support with system preference detection
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork this repository
|
||||
2. Create a new formula in the `Formula/` directory
|
||||
3. Follow the [Homebrew Formula Cookbook](https://docs.brew.sh/Formula-Cookbook)
|
||||
4. Submit a pull request
|
||||
2. Create a new formula in the `Formula/` directory following the [Homebrew Formula Cookbook](https://docs.brew.sh/Formula-Cookbook)
|
||||
3. Submit a pull request
|
||||
|
||||
The CI will automatically validate your formula and update the documentation.
|
||||
The CI will automatically validate your formula and update the documentation site.
|
||||
|
||||
## Development
|
||||
|
||||
This tap uses a custom Ruby-based static site generator with zero external dependencies.
|
||||
|
||||
### Commands
|
||||
|
||||
#### Using Make (Recommended)
|
||||
|
||||
The project includes a comprehensive Makefile that provides convenient commands for all build operations:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
bundle install
|
||||
|
||||
# Parse formulae locally
|
||||
ruby scripts/parse_formulas.rb
|
||||
|
||||
# Serve documentation locally
|
||||
cd docs && bundle exec jekyll serve
|
||||
make help # Show all available commands with descriptions
|
||||
make build # Build the documentation site
|
||||
make serve # Start development server (http://localhost:4000)
|
||||
make parse # Parse formulae and generate JSON data only
|
||||
make clean # Clean all generated files
|
||||
```
|
||||
|
||||
**Development workflow:**
|
||||
```bash
|
||||
make dev # Full development workflow (parse + build + serve)
|
||||
make setup # Check development environment setup
|
||||
make test # Run validation tests
|
||||
make check # Check project structure
|
||||
make install # Install Ruby dependencies (if Gemfile exists)
|
||||
make info # Show project information
|
||||
```
|
||||
|
||||
**Server options:**
|
||||
```bash
|
||||
make serve PORT=3000 # Use port 3000
|
||||
make serve HOST=0.0.0.0 # Bind to all interfaces
|
||||
make serve PORT=8080 HOST=0.0.0.0 # Custom port and host
|
||||
make serve-all # Start server on all interfaces (0.0.0.0)
|
||||
make serve-3000 # Quick shortcut for port 3000
|
||||
make watch # Alias for serve with auto-rebuild
|
||||
```
|
||||
|
||||
**Homebrew-specific targets:**
|
||||
```bash
|
||||
make tap-test # Test tap installation locally
|
||||
make tap-install # Install this tap locally
|
||||
make formula-new NAME=tool-name # Create new formula template
|
||||
```
|
||||
|
||||
**Production and testing:**
|
||||
```bash
|
||||
make build-production # Clean build for production deployment
|
||||
make version # Show version information
|
||||
```
|
||||
|
||||
All Makefile targets include helpful status messages and error handling.
|
||||
|
||||
**File Watching and Auto-Rebuild:**
|
||||
|
||||
The development server (`make serve`) includes intelligent file watching that:
|
||||
- ✅ Only watches source files (Formula/, theme/, scripts/, config files)
|
||||
- ✅ Excludes generated output files to prevent infinite rebuild loops
|
||||
- ✅ Includes debouncing to handle multiple rapid file changes
|
||||
- ✅ Provides clear status messages for rebuild operations
|
||||
|
||||
Files monitored for changes:
|
||||
- `Formula/**/*.rb` - Homebrew formula files
|
||||
- `theme/**/*.{css,js,erb,html}` - Theme templates and assets
|
||||
- `scripts/*.rb` - Build scripts
|
||||
- `Makefile`, `README.md`, `Gemfile` - Configuration files
|
||||
|
||||
#### Using Ruby Scripts (Alternative)
|
||||
|
||||
```bash
|
||||
ruby scripts/make.rb build # Build the documentation site
|
||||
ruby scripts/make.rb serve # Start development server
|
||||
ruby scripts/make.rb parse # Parse formulae and generate JSON data only
|
||||
ruby scripts/make.rb clean # Clean all generated files
|
||||
ruby scripts/make.rb help # Show all available commands
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
The documentation system consists of three main components:
|
||||
|
||||
1. **Formula Parser** - Safely extracts metadata from `.rb` files using regex patterns (no code evaluation)
|
||||
2. **Site Builder** - Generates static HTML from ERB templates using the parsed data
|
||||
3. **Development Server** - Serves the site locally with auto-rebuild on file changes
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── _data/ # Generated JSON data
|
||||
├── assets/ # Copied static assets (fonts, images, etc.)
|
||||
├── formula/ # Individual formula pages (generated)
|
||||
├── index.html # Homepage (generated)
|
||||
└── formulae.html # Formula listing (generated)
|
||||
theme/
|
||||
├── assets/ # Original assets: fonts, images, etc.
|
||||
├── _command_input.html.erb # Input command snippet partial
|
||||
├── _footer.html.erb # Footer partial
|
||||
├── _formula_card.html.erb # Formula card partial
|
||||
├── _head.html.erb # HTML head partial
|
||||
├── _header.html.erb # Header partial
|
||||
├── _nav.html.erb # Navigation partial
|
||||
├── _nothing_here.html.erb # "No formulae found" partial
|
||||
├── index.html.erb # Homepage template
|
||||
├── formulae.html.erb # Formula listing template
|
||||
├── formula.html.erb # Individual formula template
|
||||
├── style.css # Stylesheets with dark mode support
|
||||
└── main.js # Site functionality: search, dark mode toggle, etc.
|
||||
Formula/
|
||||
└── *.rb # Homebrew formula files
|
||||
scripts/
|
||||
├── make.rb # Main build script
|
||||
└── parser.rb # Formula parser
|
||||
Makefile # Build commands and targets
|
||||
README.md # This documentation
|
||||
Gemfile # Ruby dependencies
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- ✅ Zero external dependencies (Ruby stdlib only)
|
||||
- ✅ Fast builds with auto-reload development server
|
||||
- ✅ Dark mode with system preference detection
|
||||
- ✅ Responsive design with accessibility support
|
||||
- ✅ GitHub Pages compatible output
|
||||
- ✅ Automatic deployment via GitHub Actions
|
||||
|
||||
### Customization
|
||||
|
||||
Templates can be customized by editing files in the `theme/` directory:
|
||||
- `index.html.erb` - Homepage template
|
||||
- `formulae.html.erb` - Formula listing page
|
||||
- `formula.html.erb` - Individual formula page template
|
||||
- `style.css` - Stylesheets and theming
|
||||
- `main.js` - JavaScript functionality
|
||||
|
||||
## License
|
||||
|
||||
This tap is released under the MIT License. See LICENSE for details.
|
||||
|
||||
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
19
docs/Gemfile
19
docs/Gemfile
@@ -1,19 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
ruby "3.4.5"
|
||||
|
||||
gem "jekyll", "~> 4.4"
|
||||
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-feed", "~> 0.17"
|
||||
gem "jekyll-seo-tag", "~> 2.8"
|
||||
gem "jekyll-sitemap", "~> 1.4"
|
||||
end
|
||||
|
||||
platforms :mingw, :x64_mingw, :mswin, :jruby do
|
||||
gem "tzinfo", ">= 1", "< 3"
|
||||
gem "tzinfo-data"
|
||||
end
|
||||
|
||||
gem "wdm", "~> 0.2", platforms: [:mingw, :x64_mingw, :mswin]
|
||||
gem "http_parser.rb", "~> 0.6.0", platforms: [:jruby]
|
||||
@@ -1,53 +0,0 @@
|
||||
title: ivuorinen/homebrew-tap
|
||||
email: your-email@example.com
|
||||
description: >-
|
||||
Homebrew Tap containing custom formulae for various tools and utilities.
|
||||
Automatically updated documentation for all available formulae.
|
||||
baseurl: "/homebrew-tap"
|
||||
url: "https://ivuorinen.github.io"
|
||||
repository: ivuorinen/homebrew-tap
|
||||
|
||||
markdown: kramdown
|
||||
kramdown:
|
||||
input: GFM
|
||||
syntax_highlighter: rouge
|
||||
syntax_highlighter_opts:
|
||||
css_class: 'highlight'
|
||||
span:
|
||||
line_numbers: false
|
||||
block:
|
||||
line_numbers: true
|
||||
|
||||
plugins:
|
||||
- jekyll-feed
|
||||
- jekyll-seo-tag
|
||||
- jekyll-sitemap
|
||||
|
||||
collections:
|
||||
formulae:
|
||||
output: true
|
||||
permalink: /formula/:name/
|
||||
|
||||
defaults:
|
||||
- scope:
|
||||
path: ""
|
||||
type: "pages"
|
||||
values:
|
||||
layout: "default"
|
||||
- scope:
|
||||
path: ""
|
||||
type: "formulae"
|
||||
values:
|
||||
layout: "formula"
|
||||
|
||||
exclude:
|
||||
- Gemfile
|
||||
- Gemfile.lock
|
||||
- node_modules
|
||||
- vendor/bundle/
|
||||
- vendor/cache/
|
||||
- vendor/gems/
|
||||
- vendor/ruby/
|
||||
- scripts/
|
||||
- .sass-cache/
|
||||
- .jekyll-cache/
|
||||
0
docs/_data/.gitkeep
Normal file
0
docs/_data/.gitkeep
Normal file
@@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ page.title | default: site.title }}</title>
|
||||
{% seo %}
|
||||
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="wrapper">
|
||||
<h1><a href="{{ '/' | relative_url }}">{{ site.title }}</a></h1>
|
||||
<nav>
|
||||
<a href="{{ '/' | relative_url }}">Home</a>
|
||||
<a href="{{ '/formulae' | relative_url }}">Formulae</a>
|
||||
<a href="{{ site.repository | prepend: 'https://github.com/' }}">GitHub</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="page-content">
|
||||
<div class="wrapper">
|
||||
{{ content }}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="wrapper">
|
||||
<p>© {{ 'now' | date: '%Y' }} {{ site.title }}. Built with Jekyll and GitHub Pages.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
{% assign formula = site.data.formulae.formulae | where: "name", page.formula | first %}
|
||||
|
||||
<article class="formula-page">
|
||||
<header class="formula-header">
|
||||
<h1>{{ formula.name }}</h1>
|
||||
<div class="formula-meta">
|
||||
{% if formula.version %}<span class="version">v{{ formula.version }}</span>{% endif %}
|
||||
{% if formula.license %}<span class="license">{{ formula.license }}</span>{% endif %}
|
||||
{% if formula.homepage %}<a href="{{ formula.homepage }}" class="homepage">Homepage</a>{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if formula.description %}
|
||||
<section class="description">
|
||||
<p>{{ formula.description }}</p>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="installation">
|
||||
<h2>Installation</h2>
|
||||
<div class="code-block">
|
||||
<pre><code>brew tap {{ site.repository }}
|
||||
brew install {{ formula.name }}</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if formula.dependencies.size > 0 %}
|
||||
<section class="dependencies">
|
||||
<h2>Dependencies</h2>
|
||||
<ul class="dep-list">
|
||||
{% for dep in formula.dependencies %}
|
||||
<li>{{ dep }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="details">
|
||||
<h2>Formula Details</h2>
|
||||
<table class="formula-details">
|
||||
{% if formula.url %}
|
||||
<tr>
|
||||
<th>Source URL</th>
|
||||
<td><a href="{{ formula.url }}">{{ formula.url | truncate: 60 }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if formula.sha256 %}
|
||||
<tr>
|
||||
<th>SHA256</th>
|
||||
<td><code>{{ formula.sha256 | truncate: 20 }}...</code></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Last Updated</th>
|
||||
<td>{{ formula.last_modified | date: "%B %d, %Y" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="source">
|
||||
<h2>Formula Source</h2>
|
||||
<p><a href="{{ site.repository | prepend: 'https://github.com/' }}/blob/main/Formula/{{ formula.file_path }}">
|
||||
View {{ formula.name }}.rb on GitHub
|
||||
</a></p>
|
||||
</section>
|
||||
</article>
|
||||
0
docs/assets/.gitkeep
Normal file
0
docs/assets/.gitkeep
Normal file
@@ -1,162 +0,0 @@
|
||||
:root {
|
||||
--primary-color: #0366d6;
|
||||
--text-color: #24292e;
|
||||
--bg-color: #ffffff;
|
||||
--code-bg: #f6f8fa;
|
||||
--border-color: #e1e4e8;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background: var(--bg-color);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.site-header h1 {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.site-header a {
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.site-header nav {
|
||||
float: right;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.site-header nav a {
|
||||
margin-left: 1rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.page-content {
|
||||
min-height: 70vh;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.formula-page {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.formula-header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.formula-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.version {
|
||||
background: var(--success-color);
|
||||
color: white;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.license {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dep-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dep-list li {
|
||||
padding: 0.5rem;
|
||||
border-left: 3px solid var(--primary-color);
|
||||
margin: 0.5rem 0;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
||||
.formula-details {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.formula-details th,
|
||||
.formula-details td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.formula-details th {
|
||||
background: var(--code-bg);
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
color: #586069;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wrapper {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.site-header nav {
|
||||
float: none;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.formula-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.formula-details th {
|
||||
width: 35%;
|
||||
}
|
||||
}
|
||||
0
docs/formula/.gitkeep
Normal file
0
docs/formula/.gitkeep
Normal file
@@ -1,27 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: All Formulae
|
||||
---
|
||||
|
||||
# All Formulae
|
||||
|
||||
{% if site.data.formulae.formulae.size > 0 %}
|
||||
{% for formula in site.data.formulae.formulae %}
|
||||
## [{{ formula.name }}]({{ '/formula/' | append: formula.name | relative_url }})
|
||||
|
||||
{% if formula.description %}{{ formula.description }}{% endif %}
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
brew install {{ formula.name }}
|
||||
```
|
||||
|
||||
{% if formula.dependencies.size > 0 %}
|
||||
**Dependencies:** {{ formula.dependencies | join: ', ' }}
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No formulae available yet. Add some formulae to the `Formula/` directory to get started.
|
||||
{% endif %}
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
layout: default
|
||||
title: Home
|
||||
---
|
||||
|
||||
# ivuorinen/homebrew-tap
|
||||
|
||||
Welcome to the documentation for ivuorinen's Homebrew tap. This tap contains custom formulae for various tools and utilities.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
brew tap ivuorinen/homebrew-tap
|
||||
brew install <formula-name>
|
||||
```
|
||||
|
||||
## Available Formulae
|
||||
|
||||
{% if site.data.formulae.formulae.size > 0 %}
|
||||
<div class="formulae-grid">
|
||||
{% for formula in site.data.formulae.formulae %}
|
||||
<div class="formula-card">
|
||||
<h3><a href="{{ '/formula/' | append: formula.name | relative_url }}">{{ formula.name }}</a></h3>
|
||||
{% if formula.description %}<p>{{ formula.description }}</p>{% endif %}
|
||||
<div class="formula-meta">
|
||||
{% if formula.version %}<span class="version">v{{ formula.version }}</span>{% endif %}
|
||||
{% if formula.license %}<span class="license">{{ formula.license }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p>No formulae available yet. Add some formulae to the <code>Formula/</code> directory to get started.</p>
|
||||
{% endif %}
|
||||
|
||||
## Repository
|
||||
|
||||
View the source code and contribute on [GitHub](https://github.com/{{ site.repository }}).
|
||||
|
||||
---
|
||||
|
||||
*Documentation automatically generated from formula files.*
|
||||
File diff suppressed because it is too large
Load Diff
333
scripts/build_site.rb
Normal file
333
scripts/build_site.rb
Normal file
@@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'fileutils'
|
||||
require 'erb'
|
||||
require 'pathname'
|
||||
require 'time'
|
||||
|
||||
# Simple static site generator for homebrew tap documentation
|
||||
class SiteBuilder
|
||||
include ERB::Util
|
||||
class PartialContext
|
||||
include ERB::Util
|
||||
|
||||
def initialize(builder, locals)
|
||||
@builder = builder
|
||||
locals.each do |key, value|
|
||||
define_singleton_method(key) { value }
|
||||
end
|
||||
end
|
||||
|
||||
def render_partial(name, locals = {})
|
||||
@builder.render_partial(name, locals)
|
||||
end
|
||||
|
||||
def format_relative_time(timestamp)
|
||||
@builder.format_relative_time(timestamp)
|
||||
end
|
||||
|
||||
def format_date(timestamp)
|
||||
@builder.format_date(timestamp)
|
||||
end
|
||||
|
||||
def get_binding
|
||||
binding
|
||||
end
|
||||
end
|
||||
DOCS_DIR = File.expand_path('../docs', __dir__)
|
||||
DATA_DIR = File.join(DOCS_DIR, '_data')
|
||||
OUTPUT_DIR = DOCS_DIR
|
||||
THEME_SOURCE_DIR = File.expand_path('../theme', __dir__)
|
||||
TEMPLATES_DIR = THEME_SOURCE_DIR
|
||||
|
||||
def self.build
|
||||
new.generate_site
|
||||
end
|
||||
|
||||
def generate_site
|
||||
puts "🏗️ Building static site..."
|
||||
|
||||
setup_directories
|
||||
load_data
|
||||
generate_assets
|
||||
generate_pages
|
||||
|
||||
puts "✅ Site built successfully in #{OUTPUT_DIR}"
|
||||
puts "🌐 Open #{File.join(OUTPUT_DIR, 'index.html')} in your browser"
|
||||
end
|
||||
|
||||
def render_partial(name, locals = {})
|
||||
partial_path = File.join(TEMPLATES_DIR, "_#{name}.html.erb")
|
||||
unless File.exist?(partial_path)
|
||||
raise ArgumentError, "Partial not found: #{partial_path}"
|
||||
end
|
||||
|
||||
context = PartialContext.new(self, locals)
|
||||
ERB.new(File.read(partial_path)).result(context.get_binding)
|
||||
end
|
||||
|
||||
def format_relative_time(timestamp)
|
||||
return '' unless timestamp
|
||||
|
||||
begin
|
||||
time = Time.parse(timestamp)
|
||||
now = Time.now
|
||||
diff = now - time
|
||||
|
||||
case diff
|
||||
when 0..59
|
||||
'just now'
|
||||
when 60..3599
|
||||
mins = (diff / 60).to_i
|
||||
"#{mins} minute#{mins == 1 ? '' : 's'} ago"
|
||||
when 3600..86399
|
||||
hours = (diff / 3600).to_i
|
||||
"#{hours} hour#{hours == 1 ? '' : 's'} ago"
|
||||
when 86400..604799
|
||||
days = (diff / 86400).to_i
|
||||
"#{days} day#{days == 1 ? '' : 's'} ago"
|
||||
when 604800..2419199
|
||||
weeks = (diff / 604800).to_i
|
||||
"#{weeks} week#{weeks == 1 ? '' : 's'} ago"
|
||||
when 2419200..31535999
|
||||
months = (diff / 2419200).to_i
|
||||
"#{months} month#{months == 1 ? '' : 's'} ago"
|
||||
else
|
||||
years = (diff / 31536000).to_i
|
||||
"#{years} year#{years == 1 ? '' : 's'} ago"
|
||||
end
|
||||
rescue
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def format_date(timestamp)
|
||||
return '' unless timestamp
|
||||
|
||||
begin
|
||||
Time.parse(timestamp).strftime('%b %d, %Y')
|
||||
rescue
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_directories
|
||||
FileUtils.mkdir_p(File.join(OUTPUT_DIR, 'formula'))
|
||||
unless templates_exist?
|
||||
puts "⚠️ Templates not found in #{TEMPLATES_DIR}. Please ensure theme/*.html.erb files exist."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def load_data
|
||||
formulae_file = File.join(DATA_DIR, 'formulae.json')
|
||||
@data = File.exist?(formulae_file) ? JSON.parse(File.read(formulae_file)) : default_data
|
||||
end
|
||||
|
||||
def generate_assets
|
||||
copy_assets
|
||||
generate_css
|
||||
minify_js
|
||||
end
|
||||
|
||||
def generate_pages
|
||||
generate_index_page
|
||||
generate_formulae_pages
|
||||
end
|
||||
|
||||
def copy_assets
|
||||
assets_source_dir = File.join(THEME_SOURCE_DIR, 'assets')
|
||||
assets_output_dir = File.join(OUTPUT_DIR, 'assets')
|
||||
|
||||
# Create the output assets directory if it doesn't exist
|
||||
FileUtils.mkdir_p(assets_output_dir)
|
||||
|
||||
# Check if source assets directory exists
|
||||
if Dir.exist?(assets_source_dir)
|
||||
# Copy all files recursively, preserving directory structure
|
||||
Dir.glob(File.join(assets_source_dir, '**', '*')).each do |source_file|
|
||||
next if File.directory?(source_file)
|
||||
|
||||
# Calculate relative path from source assets dir
|
||||
relative_path = Pathname.new(source_file).relative_path_from(Pathname.new(assets_source_dir))
|
||||
output_file = File.join(assets_output_dir, relative_path)
|
||||
|
||||
# Create parent directories if needed
|
||||
FileUtils.mkdir_p(File.dirname(output_file))
|
||||
|
||||
# Copy the file
|
||||
FileUtils.cp(source_file, output_file)
|
||||
end
|
||||
|
||||
asset_count = Dir.glob(File.join(assets_source_dir, '**', '*')).reject { |f| File.directory?(f) }.size
|
||||
puts "📁 Copied #{asset_count} asset files to #{assets_output_dir}"
|
||||
else
|
||||
puts "⚠️ Assets source directory not found: #{assets_source_dir}"
|
||||
end
|
||||
end
|
||||
|
||||
def generate_css
|
||||
css_source_path = File.join(THEME_SOURCE_DIR, 'style.css')
|
||||
css_output_path = File.join(OUTPUT_DIR, 'style.css')
|
||||
|
||||
if File.exist?(css_source_path)
|
||||
css_content = File.read(css_source_path)
|
||||
minified_css = minify_css(css_content)
|
||||
File.write(css_output_path, minified_css)
|
||||
puts "📄 Generated and minified CSS (#{minified_css.length} bytes)"
|
||||
else
|
||||
puts "⚠️ CSS source file not found: #{css_source_path}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def minify_css(css)
|
||||
css
|
||||
.gsub(/\/\*.*?\*\//m, '') # Remove comments
|
||||
.gsub(/\s+/, ' ') # Collapse whitespace
|
||||
.gsub(/\s*{\s*/, '{') # Remove spaces around braces
|
||||
.gsub(/\s*}\s*/, '}')
|
||||
.gsub(/\s*:\s*/, ':') # Remove spaces around colons
|
||||
.gsub(/\s*;\s*/, ';') # Remove spaces around semicolons
|
||||
.gsub(/\s*,\s*/, ',') # Remove spaces around commas
|
||||
.strip
|
||||
end
|
||||
|
||||
def minify_js
|
||||
js_source_path = File.join(THEME_SOURCE_DIR, 'main.js')
|
||||
js_output_path = File.join(OUTPUT_DIR, 'main.js')
|
||||
|
||||
if File.exist?(js_source_path)
|
||||
js_content = File.read(js_source_path)
|
||||
minified_js = minify_js_content(js_content)
|
||||
File.write(js_output_path, minified_js)
|
||||
puts "📄 Generated and minified JavaScript (#{minified_js.length} bytes)"
|
||||
else
|
||||
puts "⚠️ JavaScript source file not found: #{js_source_path}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def minify_js_content(js)
|
||||
# Simple minification that preserves string literals
|
||||
# This is a basic approach that handles most cases
|
||||
result = []
|
||||
in_string = false
|
||||
in_template = false
|
||||
string_char = nil
|
||||
i = 0
|
||||
|
||||
while i < js.length
|
||||
char = js[i]
|
||||
prev_char = i > 0 ? js[i - 1] : nil
|
||||
|
||||
# Handle string and template literal boundaries
|
||||
if !in_string && !in_template && (char == '"' || char == "'" || char == '`')
|
||||
in_string = true if char != '`'
|
||||
in_template = true if char == '`'
|
||||
string_char = char
|
||||
result << char
|
||||
elsif (in_string || in_template) && char == string_char && prev_char != '\\'
|
||||
in_string = false
|
||||
in_template = false
|
||||
string_char = nil
|
||||
result << char
|
||||
elsif in_string || in_template
|
||||
# Preserve everything inside strings and template literals
|
||||
result << char
|
||||
else
|
||||
# Outside strings, apply minification
|
||||
if char == '/' && i + 1 < js.length
|
||||
next_char = js[i + 1]
|
||||
if next_char == '/'
|
||||
# Skip single-line comment
|
||||
i += 1 while i < js.length && js[i] != "\n"
|
||||
next
|
||||
elsif next_char == '*'
|
||||
# Skip multi-line comment
|
||||
i += 2
|
||||
while i < js.length - 1
|
||||
break if js[i] == '*' && js[i + 1] == '/'
|
||||
i += 1
|
||||
end
|
||||
i += 1 # Skip the closing /
|
||||
next
|
||||
else
|
||||
result << char
|
||||
end
|
||||
elsif char =~ /\s/
|
||||
# Only add space if needed between identifiers
|
||||
if result.last && result.last =~ /[a-zA-Z0-9_$]/ &&
|
||||
i + 1 < js.length && js[i + 1] =~ /[a-zA-Z0-9_$]/
|
||||
result << ' '
|
||||
end
|
||||
else
|
||||
result << char
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
|
||||
result.join.strip
|
||||
end
|
||||
|
||||
def generate_index_page
|
||||
template = load_template('index.html.erb')
|
||||
content = template.result(binding)
|
||||
File.write(File.join(OUTPUT_DIR, 'index.html'), content)
|
||||
end
|
||||
|
||||
def generate_formulae_pages
|
||||
@data['formulae'].each do |formula|
|
||||
generate_formula_page(formula)
|
||||
end
|
||||
|
||||
# Generate formulae index
|
||||
template = load_template('formulae.html.erb')
|
||||
content = template.result(binding)
|
||||
File.write(File.join(OUTPUT_DIR, 'formulae.html'), content)
|
||||
end
|
||||
|
||||
def generate_formula_page(formula)
|
||||
@formula = formula
|
||||
template = load_template('formula.html.erb')
|
||||
content = template.result(binding)
|
||||
|
||||
formula_dir = File.join(OUTPUT_DIR, 'formula')
|
||||
FileUtils.mkdir_p(formula_dir)
|
||||
File.write(File.join(formula_dir, "#{formula['name']}.html"), content)
|
||||
end
|
||||
|
||||
def load_template(name)
|
||||
template_path = File.join(TEMPLATES_DIR, name)
|
||||
template_content = File.read(template_path)
|
||||
ERB.new(template_content)
|
||||
end
|
||||
|
||||
def templates_exist?
|
||||
%w[index.html.erb formulae.html.erb formula.html.erb].all? do |template|
|
||||
File.exist?(File.join(TEMPLATES_DIR, template))
|
||||
end
|
||||
end
|
||||
|
||||
def default_data
|
||||
{
|
||||
'tap_name' => 'ivuorinen/homebrew-tap',
|
||||
'generated_at' => Time.now.strftime('%Y-%m-%dT%H:%M:%S%z'),
|
||||
'formulae_count' => 0,
|
||||
'formulae' => []
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Allow running this script directly
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
SiteBuilder.build
|
||||
end
|
||||
121
scripts/make.rb
Normal file
121
scripts/make.rb
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
# Simple make-style command runner for homebrew tap
|
||||
class Make
|
||||
COMMANDS = {
|
||||
'build' => 'Build the static site',
|
||||
'serve' => 'Start development server',
|
||||
'parse' => 'Parse formulae and generate JSON data',
|
||||
'clean' => 'Clean generated files',
|
||||
'help' => 'Show this help message'
|
||||
}.freeze
|
||||
|
||||
def self.run(command = nil)
|
||||
new.execute(command || ARGV[0])
|
||||
end
|
||||
|
||||
def execute(command)
|
||||
case command&.downcase
|
||||
when 'build'
|
||||
build
|
||||
when 'serve'
|
||||
serve
|
||||
when 'parse'
|
||||
parse
|
||||
when 'clean'
|
||||
clean
|
||||
when 'help', nil
|
||||
help
|
||||
else
|
||||
puts "❌ Unknown command: #{command}"
|
||||
help
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build
|
||||
puts "🏗️ Building homebrew tap documentation..."
|
||||
|
||||
success = system('ruby', script_path('parse_formulas.rb'))
|
||||
exit 1 unless success
|
||||
|
||||
success = system('ruby', script_path('build_site.rb'))
|
||||
exit 1 unless success
|
||||
|
||||
puts "✅ Build complete!"
|
||||
end
|
||||
|
||||
def serve
|
||||
port = ARGV[1]&.to_i || 4000
|
||||
host = ARGV[2] || 'localhost'
|
||||
|
||||
puts "🚀 Starting development server on http://#{host}:#{port}"
|
||||
|
||||
exec('ruby', script_path('serve.rb'), port.to_s, host)
|
||||
end
|
||||
|
||||
def parse
|
||||
puts "📋 Parsing formulae..."
|
||||
|
||||
success = system('ruby', script_path('parse_formulas.rb'))
|
||||
exit 1 unless success
|
||||
|
||||
puts "✅ Formulae parsing complete!"
|
||||
end
|
||||
|
||||
def clean
|
||||
puts "🧹 Cleaning generated files..."
|
||||
|
||||
files_to_clean = [
|
||||
docs_path('index.html'),
|
||||
docs_path('formulae.html'),
|
||||
docs_path('formula'),
|
||||
docs_path('_templates'),
|
||||
docs_path('_data', 'formulae.json'),
|
||||
docs_path('style.css'),
|
||||
docs_path('main.js')
|
||||
]
|
||||
|
||||
files_to_clean.each do |path|
|
||||
if File.exist?(path)
|
||||
FileUtils.rm_rf(path)
|
||||
puts " 🗑️ Removed #{path}"
|
||||
end
|
||||
end
|
||||
|
||||
puts "✅ Clean complete!"
|
||||
end
|
||||
|
||||
def help
|
||||
puts "Homebrew Tap Documentation Builder"
|
||||
puts
|
||||
puts "Usage: ruby scripts/make.rb <command>"
|
||||
puts
|
||||
puts "Commands:"
|
||||
COMMANDS.each do |cmd, desc|
|
||||
puts " #{cmd.ljust(10)} #{desc}"
|
||||
end
|
||||
puts
|
||||
puts "Examples:"
|
||||
puts " ruby scripts/make.rb build # Build the site"
|
||||
puts " ruby scripts/make.rb serve # Start server on port 4000"
|
||||
puts " ruby scripts/make.rb serve 3000 # Start server on port 3000"
|
||||
puts " ruby scripts/make.rb serve 3000 0.0.0.0 # Start server on all interfaces"
|
||||
end
|
||||
|
||||
def script_path(filename)
|
||||
File.join(__dir__, filename)
|
||||
end
|
||||
|
||||
def docs_path(*parts)
|
||||
File.join(__dir__, '..', 'docs', *parts)
|
||||
end
|
||||
end
|
||||
|
||||
# Run if executed directly
|
||||
Make.run if __FILE__ == $PROGRAM_NAME
|
||||
@@ -114,7 +114,7 @@ class FormulaParser
|
||||
|
||||
def write_json_output(formulae)
|
||||
output = {
|
||||
tap_name: 'ivuorinen/homebrew-tap',
|
||||
tap_name: 'ivuorinen/tap',
|
||||
generated_at: format_time_iso8601(Time.now),
|
||||
formulae_count: formulae.length,
|
||||
formulae: formulae
|
||||
|
||||
170
scripts/serve.rb
Normal file
170
scripts/serve.rb
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'webrick'
|
||||
require 'fileutils'
|
||||
require_relative 'parse_formulas'
|
||||
require_relative 'build_site'
|
||||
|
||||
# Simple development server for the homebrew tap documentation
|
||||
class DevServer
|
||||
def self.serve(port: 4000, host: 'localhost')
|
||||
new(port: port, host: host).start
|
||||
end
|
||||
|
||||
def initialize(port: 4000, host: 'localhost')
|
||||
@port = port
|
||||
@host = host
|
||||
@site_dir = File.expand_path('../docs', __dir__)
|
||||
@docs_dir = File.expand_path('../docs', __dir__)
|
||||
end
|
||||
|
||||
def start
|
||||
puts "🔄 Building site..."
|
||||
build_site
|
||||
|
||||
puts "🚀 Starting development server..."
|
||||
puts "📍 Server address: http://#{@host}:#{@port}"
|
||||
puts "📁 Serving from: #{@site_dir}"
|
||||
puts "💡 Press Ctrl+C to stop"
|
||||
|
||||
start_server
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_site
|
||||
# Generate formulae data
|
||||
FormulaParser.run
|
||||
|
||||
# Build static site
|
||||
SiteBuilder.build
|
||||
end
|
||||
|
||||
def start_server
|
||||
server = WEBrick::HTTPServer.new(
|
||||
Port: @port,
|
||||
Host: @host,
|
||||
DocumentRoot: @site_dir,
|
||||
Logger: WEBrick::Log.new($stderr, WEBrick::Log::INFO),
|
||||
AccessLog: [[
|
||||
$stderr,
|
||||
WEBrick::AccessLog::COMBINED_LOG_FORMAT
|
||||
]]
|
||||
)
|
||||
|
||||
# Handle Ctrl+C gracefully
|
||||
trap('INT') do
|
||||
puts "\n👋 Stopping server..."
|
||||
server.shutdown
|
||||
end
|
||||
|
||||
# Add custom mime types if needed
|
||||
server.config[:MimeTypes]['json'] = 'application/json'
|
||||
|
||||
# Add auto-rebuild on file changes (simple polling)
|
||||
start_file_watcher
|
||||
|
||||
server.start
|
||||
end
|
||||
|
||||
def start_file_watcher
|
||||
Thread.new do
|
||||
last_mtime = get_max_mtime
|
||||
rebuild_pending = false
|
||||
watched_files = get_watched_files_count
|
||||
|
||||
puts "👀 Watching #{watched_files} files for changes..."
|
||||
|
||||
loop do
|
||||
sleep 1
|
||||
current_mtime = get_max_mtime
|
||||
|
||||
if current_mtime > last_mtime && !rebuild_pending
|
||||
rebuild_pending = true
|
||||
changed_file = find_changed_file(last_mtime)
|
||||
puts "📝 Changed: #{changed_file}" if changed_file
|
||||
puts "🔄 Rebuilding in 1 second..."
|
||||
|
||||
# Debounce: wait for additional changes
|
||||
sleep 1
|
||||
|
||||
# Check if more changes occurred during debounce period
|
||||
final_mtime = get_max_mtime
|
||||
|
||||
puts "🔨 Building site..."
|
||||
build_site
|
||||
last_mtime = final_mtime
|
||||
rebuild_pending = false
|
||||
puts "✅ Rebuild complete"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_watched_files_count
|
||||
files = get_all_watched_files
|
||||
files.select { |f| File.exist?(f) && !File.directory?(f) }.count
|
||||
end
|
||||
|
||||
def find_changed_file(since_time)
|
||||
files = get_all_watched_files
|
||||
files.select { |f| File.exist?(f) && !File.directory?(f) }
|
||||
.find { |f| File.mtime(f) > since_time }
|
||||
&.sub(File.expand_path('..', __dir__) + '/', '')
|
||||
end
|
||||
|
||||
def get_all_watched_files
|
||||
[
|
||||
# Watch Formula files for changes
|
||||
Dir.glob(File.expand_path('../Formula/**/*.rb', __dir__)),
|
||||
# Watch all theme files including partials
|
||||
Dir.glob(File.expand_path('../theme/**/*', __dir__)),
|
||||
# Specifically watch for erb templates and partials
|
||||
Dir.glob(File.expand_path('../theme/*.erb', __dir__)),
|
||||
Dir.glob(File.expand_path('../theme/_*.erb', __dir__)),
|
||||
Dir.glob(File.expand_path('../theme/*.html.erb', __dir__)),
|
||||
Dir.glob(File.expand_path('../theme/_*.html.erb', __dir__)),
|
||||
# Watch CSS and JS
|
||||
Dir.glob(File.expand_path('../theme/*.css', __dir__)),
|
||||
Dir.glob(File.expand_path('../theme/*.js', __dir__)),
|
||||
# Watch assets directory
|
||||
Dir.glob(File.expand_path('../theme/assets/**/*', __dir__)),
|
||||
# Watch build scripts for changes
|
||||
[File.expand_path('../scripts/parse_formulas.rb', __dir__)],
|
||||
[File.expand_path('../scripts/build_site.rb', __dir__)],
|
||||
# Watch Makefile
|
||||
[File.expand_path('../Makefile', __dir__)]
|
||||
].flatten.compact.uniq
|
||||
end
|
||||
|
||||
def get_max_mtime
|
||||
files_to_watch = get_all_watched_files
|
||||
|
||||
# Filter out non-existent files and directories, get modification times
|
||||
existing_files = files_to_watch.select { |f| File.exist?(f) && !File.directory?(f) }
|
||||
|
||||
if existing_files.empty?
|
||||
Time.at(0)
|
||||
else
|
||||
existing_files.map { |f| File.mtime(f) }.max
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Command line interface
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
# Check for --list-watched flag
|
||||
if ARGV.include?('--list-watched')
|
||||
server = DevServer.new
|
||||
files = server.send(:get_all_watched_files).select { |f| File.exist?(f) && !File.directory?(f) }
|
||||
puts "📋 Watching #{files.count} files:"
|
||||
files.sort.each { |f| puts " - #{f.sub(File.expand_path('..', __dir__) + '/', '')}" }
|
||||
exit 0
|
||||
end
|
||||
|
||||
port = ARGV[0]&.to_i || 4000
|
||||
host = ARGV[1] || 'localhost'
|
||||
|
||||
DevServer.serve(port: port, host: host)
|
||||
end
|
||||
10
theme/_command_input.html.erb
Normal file
10
theme/_command_input.html.erb
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class="install-cmd relative flex items-center gap-<%= size == 'sm' ? '2' : '4' %> rounded-2xl border border-slate-200 bg-slate-900 shadow-sm dark:border-slate-700 dark:bg-slate-950">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="<%= h(command) %>"
|
||||
onclick="this.select()"
|
||||
class="flex-1 bg-transparent <%= size == 'sm' ? 'py-2 text-xs' : 'py-3 text-sm' %> font-mono text-slate-100 outline-none cursor-pointer border-0 appearance-none focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-400 dark:focus-visible:outline-sky-300 focus-visible:outline-offset-[-2px] rounded-2xl px-8"
|
||||
>
|
||||
<span class="<%= size == 'sm' ? 'text-[10px]' : 'text-xs' %> uppercase tracking-wide text-slate-400 pr-8"><%= h(label || 'CLI') %></span>
|
||||
</div>
|
||||
7
theme/_footer.html.erb
Normal file
7
theme/_footer.html.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<footer class="border-t border-slate-200 pt-6 text-sm text-slate-500 dark:border-slate-700 dark:text-slate-400">
|
||||
<p class="px-8">
|
||||
<time datetime="<%= h(generated_at) %>" title="<%= h(generated_at) %>">
|
||||
Generated at <%= h(format_relative_time(generated_at)) %>
|
||||
</time>
|
||||
</p>
|
||||
</footer>
|
||||
55
theme/_formula_card.html.erb
Normal file
55
theme/_formula_card.html.erb
Normal file
@@ -0,0 +1,55 @@
|
||||
<%
|
||||
# Required parameters:
|
||||
# - formula: The formula hash object
|
||||
# - base_path: Path prefix for formula links (e.g., '' or '../')
|
||||
# Optional parameters:
|
||||
# - show_homepage: Show homepage link (default: false)
|
||||
# - show_dependencies: Show dependency badges (default: false)
|
||||
# - show_last_modified: Show last modified time (default: false)
|
||||
|
||||
formula_name = formula['name'].to_s
|
||||
return if formula_name.empty?
|
||||
formula_slug = formula_name.gsub(/[^a-z0-9._-]/i, '-')
|
||||
formula_slug = formula_name if formula_slug.empty?
|
||||
formula_description = formula['description'].to_s
|
||||
# Check if methods exist (they're defined as singleton methods in PartialContext)
|
||||
show_homepage = respond_to?(:show_homepage) ? self.show_homepage : false
|
||||
show_dependencies = respond_to?(:show_dependencies) ? self.show_dependencies : false
|
||||
show_last_modified = respond_to?(:show_last_modified) ? self.show_last_modified : false
|
||||
%>
|
||||
<article class="formula-card flex h-full flex-col gap-4 rounded-3xl border border-slate-200 bg-white/80 p-6 shadow-sm ring-1 ring-black/5 transition hover:-translate-y-1 hover:shadow-md dark:border-slate-700 dark:bg-slate-900/70 dark:ring-white/10">
|
||||
<header class="space-y-2">
|
||||
<h3 class="text-xl font-semibold">
|
||||
<a href="<%= base_path %>formula/<%= h(formula_slug) %>.html" class="transition hover:text-sky-600 dark:hover:text-sky-400"><%= h(formula_name) %></a>
|
||||
</h3>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-300"><%= h(formula_description) %></p>
|
||||
<% if show_homepage && formula['homepage'] %>
|
||||
<p class="text-sm">
|
||||
<a href="<%= h(formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center gap-1 text-sky-600 transition hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300">
|
||||
Homepage
|
||||
<span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</p>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<% if show_last_modified && formula['last_modified'] %>
|
||||
<div class="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
<span>Updated <%= format_relative_time(formula['last_modified']) %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% if formula['license'] %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-sky-500 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-sky-400">License: <%= h(formula['license'].to_s) %></span>
|
||||
<% end %>
|
||||
<% if formula['version'] %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-emerald-500 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-emerald-400">v<%= h(formula['version'].to_s) %></span>
|
||||
<% end %>
|
||||
<% if show_dependencies %>
|
||||
<% Array(formula['dependencies']).compact.each do |dep| %>
|
||||
<span class="badge inline-flex items-center rounded-full bg-slate-800 px-3 py-1 text-xs uppercase tracking-wide text-white dark:bg-slate-700">dep: <%= h(dep.to_s) %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</article>
|
||||
31
theme/_head.html.erb
Normal file
31
theme/_head.html.erb
Normal file
@@ -0,0 +1,31 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<% page_title = title.to_s %>
|
||||
<title><%= h(page_title) %></title>
|
||||
<% stylesheet = style_path || 'style.css' %>
|
||||
<link rel="stylesheet" href="<%= h(stylesheet) %>">
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class"
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
const stored = window.localStorage.getItem("theme");
|
||||
const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const theme = stored || (prefersDark ? "dark" : "light");
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
document.documentElement.classList.toggle("light", theme !== "dark");
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
} catch (error) {
|
||||
document.documentElement.classList.toggle("dark", false);
|
||||
document.documentElement.classList.toggle("light", true);
|
||||
document.documentElement.setAttribute("data-theme", "light");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
15
theme/_header.html.erb
Normal file
15
theme/_header.html.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
<header class="flex flex-col gap-6">
|
||||
<nav aria-label="Main navigation" class="px-8">
|
||||
<%= render_partial('nav', active: active_page, base_path: base_path) %>
|
||||
</nav>
|
||||
|
||||
<div class="header flex flex-col gap-6 rounded-3xl border border-slate-200 bg-white/80 p-8 shadow-sm ring-1 ring-black/5 backdrop-blur dark:border-slate-700 dark:bg-slate-900/80 dark:ring-white/10">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold tracking-tight md:text-4xl"><%= h(tap_name) %></h1>
|
||||
<p class="max-w-3xl text-base text-slate-600 dark:text-slate-300"><%= h(description) %></p>
|
||||
</div>
|
||||
<button class="theme-toggle inline-flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full border border-slate-300 bg-white/80 text-xl text-slate-600 shadow-sm transition hover:bg-slate-100 hover:text-slate-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500 dark:border-slate-600 dark:bg-slate-900/70 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="Toggle dark mode" tabindex="0">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
13
theme/_nav.html.erb
Normal file
13
theme/_nav.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<% nav_base = "rounded-full px-3 py-1 transition hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-white" %>
|
||||
<% nav_active = "bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900" %>
|
||||
<% active_tab = (active || :none).to_sym rescue :none %>
|
||||
<% prefix = base_path.to_s %>
|
||||
<% href = lambda do |page|
|
||||
prefix.empty? ? page : File.join(prefix, page)
|
||||
end %>
|
||||
<nav aria-label="Main navigation" class="flex flex-wrap items-center gap-3 text-sm font-medium text-slate-600 dark:text-slate-300">
|
||||
<% home_classes = [nav_base, (active_tab == :home ? nav_active : nil)].compact.join(' ') %>
|
||||
<% formulae_classes = [nav_base, (active_tab == :formulae ? nav_active : nil)].compact.join(' ') %>
|
||||
<a href="<%= h(href.call('index.html')) %>" class="<%= home_classes %>">Home</a>
|
||||
<a href="<%= h(href.call('formulae.html')) %>" class="<%= formulae_classes %>">All Formulae</a>
|
||||
</nav>
|
||||
6
theme/_nothing_here.html.erb
Normal file
6
theme/_nothing_here.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white/80 p-8 text-center shadow-sm ring-1 ring-black/5 backdrop-blur dark:border-slate-700 dark:bg-slate-900/80 dark:ring-white/10">
|
||||
<h2 class="text-2xl font-semibold tracking-tight mb-4">No formulae available</h2>
|
||||
<p class="text-slate-600 dark:text-slate-300">This tap currently has no formulae. Please check back later.</p>
|
||||
</div>
|
||||
</main>
|
||||
BIN
theme/assets/MonaspaceArgonVar.woff
Normal file
BIN
theme/assets/MonaspaceArgonVar.woff
Normal file
Binary file not shown.
BIN
theme/assets/MonaspaceArgonVar.woff2
Normal file
BIN
theme/assets/MonaspaceArgonVar.woff2
Normal file
Binary file not shown.
111
theme/formula.html.erb
Normal file
111
theme/formula.html.erb
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% tap_name = raw_tap_name.empty? ? 'ivuorinen/tap' : raw_tap_name %>
|
||||
<% formula_name = @formula['name'].to_s %>
|
||||
<% name = h("#{tap_name}/#{formula_name}") %>
|
||||
<%= render_partial('head', title: "#{formula_name} - #{tap_name}", style_path: '../style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: name, description: @formula['description'].to_s, active_page: :formulae, base_path: '../') %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-4">
|
||||
<div class="flex items-center justify-between px-8 pr-6">
|
||||
<h2 class="text-2xl font-semibold tracking-tight h-10">Installation</h2>
|
||||
<div class="flex gap-2">
|
||||
<% if @formula['url'] %>
|
||||
<a href="<%= h(@formula['url'].to_s) %>" target="_blank" class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="Download source">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<% if @formula['homepage'] %>
|
||||
<a href="<%= h(@formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:border-slate-600 dark:hover:bg-slate-800 dark:hover:text-white" aria-label="View on GitHub">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render_partial('command_input', command: "brew install #{name}", size: 'lg', label: 'CLI') %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm ring-1 ring-black/5 dark:border-slate-700 dark:bg-slate-900/70 dark:ring-white/10">
|
||||
<table class="w-full divide-y divide-slate-200 text-sm dark:divide-slate-700">
|
||||
<caption class="sr-only">Formula Details</caption>
|
||||
<tbody>
|
||||
<% if @formula['version'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Version</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['version'].to_s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @formula['license'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">License</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['license'].to_s) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @formula['homepage'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Homepage</th>
|
||||
<td class="px-4 py-3">
|
||||
<a href="<%= h(@formula['homepage'].to_s) %>" target="_blank" class="inline-flex items-center gap-1 text-sky-600 transition hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300">
|
||||
<%= h(@formula['homepage'].to_s) %>
|
||||
<span aria-hidden="true">↗</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% dependencies = Array(@formula['dependencies']).reject(&:nil?) %>
|
||||
<% if dependencies.any? %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Dependencies</th>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<% dependencies.each do |dep| %>
|
||||
<span class="inline-flex items-center rounded-full bg-slate-800 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-white dark:bg-slate-700">dep: <%= h(dep.to_s) %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Formula File</th>
|
||||
<td class="px-4 py-3"><code class="rounded-xl bg-slate-900 px-3 py-2 font-mono text-xs text-slate-100 dark:bg-slate-950"><%= h(@formula['file_path'].to_s) %></code></td>
|
||||
</tr>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">Last Modified</th>
|
||||
<td class="px-4 py-3 text-slate-600 dark:text-slate-200"><%= h(@formula['last_modified'].to_s) %></td>
|
||||
</tr>
|
||||
<% if @formula['sha256'] %>
|
||||
<tr class="divide-x divide-slate-200 dark:divide-slate-700">
|
||||
<th scope="row" class="w-40 bg-slate-50 px-4 py-3 text-left font-medium text-slate-700 dark:bg-slate-900 dark:text-slate-300">SHA256</th>
|
||||
<td class="px-4 py-3">
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value="<%= h(@formula['sha256'].to_s) %>"
|
||||
onclick="this.select()"
|
||||
class="w-full bg-slate-50 dark:bg-slate-800 px-3 py-1.5 font-mono text-xs text-slate-800 dark:text-slate-200 rounded-lg border border-slate-200 dark:border-slate-600 cursor-pointer appearance-none focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-400 dark:focus-visible:outline-sky-300 focus-visible:outline-offset-[-2px]"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at']) %>
|
||||
<script src="../main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
46
theme/formulae.html.erb
Normal file
46
theme/formulae.html.erb
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% tap_name = raw_tap_name.empty? ? 'ivuorinen/tap' : raw_tap_name %>
|
||||
<%= render_partial('head', title: "All Formulae - #{tap_name}", style_path: 'style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: tap_name, description: 'Homebrew Tap containing custom formulae for various tools and utilities.', active_page: :formulae, base_path: '') %>
|
||||
|
||||
<% if @data['formulae_count'].to_i == 0 %>
|
||||
<%= render_partial('nothing_here') %>
|
||||
<% else %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-6">
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8">All Formulae</h2>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 px-8"><%= h(@data['formulae_count'].to_s) %> total</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<input
|
||||
type="search"
|
||||
id="formula-search"
|
||||
placeholder="Search formulae..."
|
||||
class="w-full rounded-xl border border-slate-200 bg-white px-8 py-2 text-sm placeholder-slate-400 shadow-sm transition focus:border-sky-400 focus:outline-none focus:ring-1 focus:ring-sky-400 dark:border-slate-700 dark:bg-slate-900 dark:placeholder-slate-500 dark:focus:border-sky-300 dark:focus:ring-sky-300"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="formula-grid grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<%
|
||||
# Sort all formulae alphabetically by name
|
||||
@data['formulae'].sort_by { |f| f['name'].to_s.downcase }.each do |formula|
|
||||
%>
|
||||
<%= render_partial('formula_card', formula: formula, base_path: '', show_homepage: true, show_dependencies: true, show_last_modified: true) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at']) %>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
49
theme/index.html.erb
Normal file
49
theme/index.html.erb
Normal file
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full min-h-full scroll-smooth font-sans transition-colors duration-200">
|
||||
<% raw_tap_name = @data['tap_name'].to_s %>
|
||||
<% safe_tap_name = raw_tap_name.empty? ? 'ivuorinen/homebrew-tap' : raw_tap_name %>
|
||||
<%= render_partial('head', title: safe_tap_name, style_path: 'style.css') %>
|
||||
<body class="min-h-screen bg-slate-50 text-slate-900 transition-colors duration-200 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div class="mx-auto flex min-h-screen max-w-6xl flex-col gap-12 px-6 py-10">
|
||||
<%= render_partial('header', tap_name: safe_tap_name, description: 'Homebrew Tap containing custom formulae for various tools and utilities.', active_page: :home, base_path: '') %>
|
||||
|
||||
<% if @data['formulae_count'].to_i == 0 %>
|
||||
<%= render_partial('nothing_here') %>
|
||||
<% else %>
|
||||
|
||||
<main class="flex flex-1 flex-col gap-8">
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8 h-10">Quick Start</h2>
|
||||
<%= render_partial('command_input', command: "brew tap #{safe_tap_name}", size: 'lg', label: 'CLI') %>
|
||||
</section>
|
||||
|
||||
<section class="space-y-6">
|
||||
<div class="flex items-end justify-between gap-4">
|
||||
<h2 class="text-2xl font-semibold tracking-tight px-8">Latest Updates</h2>
|
||||
<a href="formulae.html" class="text-sm text-sky-600 hover:text-sky-500 dark:text-sky-400 dark:hover:text-sky-300 px-8">View all <%= h(@data['formulae_count'].to_s) %> formulae →</a>
|
||||
</div>
|
||||
|
||||
<div class="formula-grid grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<%
|
||||
# Sort formulae by last_modified date (most recent first) and take top 6
|
||||
sorted_formulae = Array(@data['formulae'])
|
||||
.select { |f| f.is_a?(Hash) && f['last_modified'] }
|
||||
.sort_by { |f| f['last_modified'] }
|
||||
.reverse
|
||||
.first(6)
|
||||
|
||||
sorted_formulae.each do |formula|
|
||||
%>
|
||||
<%= render_partial('formula_card', formula: formula, base_path: '', show_last_modified: true) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= render_partial('footer', generated_at: @data['generated_at'].to_s) %>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
156
theme/main.js
Normal file
156
theme/main.js
Normal file
@@ -0,0 +1,156 @@
|
||||
// Dark mode toggle functionality
|
||||
(() => {
|
||||
const STORAGE_KEY = "theme";
|
||||
|
||||
function getStoredTheme() {
|
||||
try {
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setStoredTheme(theme) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, theme);
|
||||
} catch {
|
||||
// Ignore storage failures
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemTheme() {
|
||||
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
function getCurrentTheme() {
|
||||
return getStoredTheme() || getSystemTheme() || "light";
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.classList.toggle("dark", theme === "dark");
|
||||
const toggle = document.querySelector(".theme-toggle");
|
||||
if (toggle) {
|
||||
toggle.innerHTML = theme === "dark" ? "☀️" : "🌙";
|
||||
toggle.setAttribute(
|
||||
"aria-label",
|
||||
theme === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
||||
setStoredTheme(newTheme);
|
||||
applyTheme(newTheme);
|
||||
}
|
||||
|
||||
// Watch for system theme changes
|
||||
const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
|
||||
mediaQuery?.addEventListener("change", (e) => {
|
||||
if (!getStoredTheme()) {
|
||||
applyTheme(e.matches ? "dark" : "light");
|
||||
}
|
||||
});
|
||||
|
||||
// Theme toggle click handler
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.closest(".theme-toggle")) {
|
||||
e.preventDefault();
|
||||
toggleTheme();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize theme
|
||||
applyTheme(getCurrentTheme());
|
||||
window.toggleTheme = toggleTheme;
|
||||
})();
|
||||
|
||||
// Click-to-copy functionality for command inputs
|
||||
(() => {
|
||||
async function copyToClipboard(input) {
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(input.value);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// Keep text selected for manual copy
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function showCopyFeedback(element, success) {
|
||||
if (!success) return;
|
||||
|
||||
const label = element.closest(".install-cmd")?.querySelector("span");
|
||||
if (!label) return;
|
||||
|
||||
const originalText = label.textContent;
|
||||
label.textContent = "Copied!";
|
||||
label.style.color = "#10b981";
|
||||
|
||||
setTimeout(() => {
|
||||
label.textContent = originalText;
|
||||
label.style.color = "";
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Setup copy handlers using event delegation
|
||||
document.addEventListener("click", async (e) => {
|
||||
const input = e.target.closest('input[readonly][onclick*="select"]');
|
||||
if (input) {
|
||||
input.removeAttribute("onclick");
|
||||
const success = await copyToClipboard(input);
|
||||
showCopyFeedback(input, success);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// Formula search functionality
|
||||
(() => {
|
||||
let searchTimeout = null;
|
||||
const searchInput = document.getElementById("formula-search");
|
||||
if (!searchInput) return;
|
||||
|
||||
function fuzzyMatch(needle, haystack) {
|
||||
if (!needle) return true;
|
||||
|
||||
needle = needle.toLowerCase();
|
||||
haystack = haystack.toLowerCase();
|
||||
|
||||
let j = 0;
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
const char = needle[i];
|
||||
j = haystack.indexOf(char, j);
|
||||
if (j === -1) return false;
|
||||
j++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function performSearch() {
|
||||
const searchTerm = searchInput.value.trim();
|
||||
const cards = document.querySelectorAll(".formula-card");
|
||||
|
||||
cards.forEach((card) => {
|
||||
const text = `${card.querySelector("h3")?.textContent || ""} ${card.querySelector("p")?.textContent || ""}`;
|
||||
card.style.display = !searchTerm || fuzzyMatch(searchTerm, text) ? "" : "none";
|
||||
});
|
||||
}
|
||||
searchInput.addEventListener("input", () => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(performSearch, 300);
|
||||
});
|
||||
|
||||
searchInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
searchInput.value = "";
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
})();
|
||||
43
theme/style.css
Normal file
43
theme/style.css
Normal file
@@ -0,0 +1,43 @@
|
||||
@font-face {
|
||||
font-family: "Monaspace Argon";
|
||||
src:
|
||||
url("assets/MonaspaceArgonVar.woff2") format("woff2"),
|
||||
url("assets/MonaspaceArgonVar.woff") format("woff");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family:
|
||||
"Monaspace Argon",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
"Monaspace Argon",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-family: "Monaspace Argon", monospace;
|
||||
font-feature-settings:
|
||||
"calt", "ss01", "ss02", "ss03", "ss04", "ss05", "ss06", "ss07", "ss08", "ss09", "ss10",
|
||||
"liga";
|
||||
}
|
||||
Reference in New Issue
Block a user