mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
* fix: post-audit follow-up code fixes (cache, fonts, icons, dev script, CI) The docs refresh effort (#306, ledmatrix-plugins#92) surfaced seven code bugs that were intentionally left out of the docs PRs because they required code changes rather than doc fixes. This PR addresses the six that belong in LEDMatrix (the seventh — a lacrosse-scoreboard mode rename — lives in the plugins repo). Bug 1: cache_manager.delete() AttributeError src/common/api_helper.py:287 and src/plugin_system/resource_monitor.py:343 both call cache_manager.delete(key), which doesn't exist — only clear_cache(key=None). Added a delete() alias method on CacheManager that forwards to clear_cache(key). Reverts the "There is no delete() method" wording in DEVELOPER_QUICK_REFERENCE, .cursorrules so the docs match the new shim. Bug 2: dev_plugin_setup.sh PROJECT_ROOT resolution scripts/dev/dev_plugin_setup.sh:9 set PROJECT_ROOT to SCRIPT_DIR instead of walking up two levels to the repo root, so PLUGINS_DIR resolved to scripts/dev/plugins/ and created symlinks under the script's own directory. Fixed the path and removed the stray scripts/dev/plugins/of-the-day symlink left by earlier runs. Bug 3: plugin custom icons regressed from v2 to v3 web_interface/blueprints/api_v3.py built the /plugins/installed response without including the manifest's "icon" field, and web_interface/templates/v3/base.html hardcoded fas fa-puzzle-piece in all three plugin-tab render sites. Pass the icon through the API and read it from the templates with a puzzle-piece fallback. Reverts the "currently broken" banners in docs/PLUGIN_CUSTOM_ICONS.md and docs/PLUGIN_CUSTOM_ICONS_FEATURE.md. Bug 4: register_plugin_fonts was never wired up src/font_manager.py:150 defines register_plugin_fonts(plugin_id, font_manifest) but nothing called it, so plugin manifests with a "fonts" block were silently no-ops. Wired the call into PluginManager.load_plugin() right after plugin_loader.load_plugin returns. Reverts the "not currently wired" warning in docs/FONT_MANAGER.md's "For Plugin Developers" section. Bug 5: dead web_interface_v2 import pattern (LEDMatrix half) src/base_odds_manager.py had a try/except importing web_interface_v2.increment_api_counter, falling back to a no-op stub. The module doesn't exist anywhere in the v3 codebase and no API metrics dashboard reads it. Deleted the import block and the single call site; the plugins-repo half of this cleanup lands in ledmatrix-plugins#<next>. Bug 7: no CI test workflow .github/workflows/ only contained security-audit.yml; pytest ran locally but was not gated on PRs. Added .github/workflows/tests.yml running pytest against Python 3.10, 3.11, 3.12 in EMULATOR=true mode, skipping tests marked hardware or slow. Updated docs/HOW_TO_RUN_TESTS.md to reflect that the workflow now exists. Verification done locally: - CacheManager.delete(key) round-trips with set/get - base_odds_manager imports without the v2 module present - dev_plugin_setup.sh PROJECT_ROOT resolves to repo root - api_v3 and plugin_manager compile clean - tests.yml YAML parses - Script syntax check on dev_plugin_setup.sh Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review comments on #307 - src/cache_manager.py: clear_cache(key) treated empty string as "wipe all" because of `if key:`. Switched to `key is None` branching, made delete(key) and clear_cache(key) reject empty strings and None outright with ValueError, and updated both docstrings to make the contract explicit. Verified locally with a round-trip test that clear_cache() (no arg) still wipes everything but clear_cache("") and delete("") raise. - src/plugin_system/plugin_manager.py: was reaching for the font manager via getattr(self.display_manager, 'font_manager', None). PluginManager already takes a dedicated font_manager parameter (line 54) and stores it as self.font_manager (line 69), so the old path was both wrong and could miss the font manager entirely when the host injects them separately. Switched to self.font_manager directly with the same try/except warning behavior. - web_interface/templates/v3/base.html: in the full plugin-tab renderer, the icon was injected with `<i class="${escapeHtml(plugin.icon)}">` — but escapeHtml only escapes <, >, and &, not double quotes, so a manifest with a quote in its icon string could break out of the class attribute. Replaced the innerHTML template with createElement for the <i> tag, set className from plugin.icon directly (no string interpolation), and used a text node for the label. Same fix shape would also harden the two stub-renderer sites at line 515 / 774, but those already escape `"` to " and CodeRabbit only flagged this site, so leaving them for now. - docs/FONT_MANAGER.md: clarified that the Manual Font Overrides *workflow* (set_override / remove_override / font_overrides.json) is the supported override path today, and only the Fonts tab in the web UI is the placeholder. Previous wording conflated the two and made it sound like overrides themselves were broken. - docs/HOW_TO_RUN_TESTS.md: replaced the vague "see the PR adding it" with a concrete link to #307 and a note that the workflow file itself is held back pending the workflow scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
526 lines
15 KiB
Bash
Executable File
526 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# LEDMatrix Plugin Development Setup Script
|
|
# Manages symbolic links between plugin repositories and the plugins/ directory
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
PLUGINS_DIR="$PROJECT_ROOT/plugins"
|
|
CONFIG_FILE="$PROJECT_ROOT/dev_plugins.json"
|
|
DEFAULT_DEV_DIR="$HOME/.ledmatrix-dev-plugins"
|
|
GITHUB_USER="ChuckBuilds"
|
|
GITHUB_PATTERN="ledmatrix-"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Load configuration file
|
|
load_config() {
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
DEV_PLUGINS_DIR=$(jq -r '.dev_plugins_dir // "'"$DEFAULT_DEV_DIR"'"' "$CONFIG_FILE" 2>/dev/null || echo "$DEFAULT_DEV_DIR")
|
|
# Expand ~ in path
|
|
DEV_PLUGINS_DIR="${DEV_PLUGINS_DIR/#\~/$HOME}"
|
|
else
|
|
DEV_PLUGINS_DIR="$DEFAULT_DEV_DIR"
|
|
fi
|
|
mkdir -p "$DEV_PLUGINS_DIR"
|
|
}
|
|
|
|
# Validate plugin structure
|
|
validate_plugin() {
|
|
local plugin_path="$1"
|
|
if [[ ! -f "$plugin_path/manifest.json" ]]; then
|
|
log_error "Plugin directory does not contain manifest.json: $plugin_path"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Get plugin ID from manifest
|
|
get_plugin_id() {
|
|
local plugin_path="$1"
|
|
if [[ -f "$plugin_path/manifest.json" ]]; then
|
|
jq -r '.id // empty' "$plugin_path/manifest.json" 2>/dev/null || echo ""
|
|
fi
|
|
}
|
|
|
|
# Check if path is a symlink
|
|
is_symlink() {
|
|
[[ -L "$1" ]]
|
|
}
|
|
|
|
# Check if plugin directory exists
|
|
plugin_exists() {
|
|
[[ -e "$PLUGINS_DIR/$1" ]]
|
|
}
|
|
|
|
# Get symlink target
|
|
get_symlink_target() {
|
|
if is_symlink "$PLUGINS_DIR/$1"; then
|
|
readlink -f "$PLUGINS_DIR/$1"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Link a local plugin repository
|
|
link_plugin() {
|
|
local plugin_name="$1"
|
|
local repo_path="$2"
|
|
|
|
if [[ -z "$plugin_name" ]] || [[ -z "$repo_path" ]]; then
|
|
log_error "Usage: $0 link <plugin-name> <repo-path>"
|
|
exit 1
|
|
fi
|
|
|
|
# Resolve absolute path
|
|
if [[ ! "$repo_path" = /* ]]; then
|
|
repo_path="$(cd "$(dirname "$repo_path")" && pwd)/$(basename "$repo_path")"
|
|
fi
|
|
|
|
if [[ ! -d "$repo_path" ]]; then
|
|
log_error "Repository path does not exist: $repo_path"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate plugin structure
|
|
if ! validate_plugin "$repo_path"; then
|
|
exit 1
|
|
fi
|
|
|
|
# Check for existing plugin
|
|
if plugin_exists "$plugin_name"; then
|
|
if is_symlink "$PLUGINS_DIR/$plugin_name"; then
|
|
local target=$(get_symlink_target "$plugin_name")
|
|
if [[ "$target" == "$repo_path" ]]; then
|
|
log_info "Plugin $plugin_name is already linked to $repo_path"
|
|
return 0
|
|
else
|
|
log_warn "Plugin $plugin_name exists as symlink to $target"
|
|
read -p "Replace existing symlink? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
log_info "Aborted"
|
|
exit 0
|
|
fi
|
|
rm "$PLUGINS_DIR/$plugin_name"
|
|
fi
|
|
else
|
|
log_warn "Plugin directory exists but is not a symlink: $PLUGINS_DIR/$plugin_name"
|
|
read -p "Backup and replace? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
log_info "Aborted"
|
|
exit 0
|
|
fi
|
|
mv "$PLUGINS_DIR/$plugin_name" "$PLUGINS_DIR/${plugin_name}.backup.$(date +%Y%m%d%H%M%S)"
|
|
fi
|
|
fi
|
|
|
|
# Create symlink
|
|
ln -s "$repo_path" "$PLUGINS_DIR/$plugin_name"
|
|
|
|
local plugin_id=$(get_plugin_id "$repo_path")
|
|
if [[ -n "$plugin_id" ]] && [[ "$plugin_id" != "$plugin_name" ]]; then
|
|
log_warn "Plugin ID in manifest ($plugin_id) differs from directory name ($plugin_name)"
|
|
fi
|
|
|
|
log_success "Linked $plugin_name to $repo_path"
|
|
}
|
|
|
|
# Clone repository from GitHub
|
|
clone_from_github() {
|
|
local repo_url="$1"
|
|
local target_dir="$2"
|
|
local branch="${3:-}"
|
|
|
|
log_info "Cloning $repo_url to $target_dir"
|
|
|
|
local clone_cmd=("git" "clone")
|
|
|
|
if [[ -n "$branch" ]]; then
|
|
clone_cmd+=("--branch" "$branch")
|
|
fi
|
|
|
|
clone_cmd+=("--depth" "1" "$repo_url" "$target_dir")
|
|
|
|
if ! "${clone_cmd[@]}"; then
|
|
log_error "Failed to clone repository"
|
|
return 1
|
|
fi
|
|
|
|
log_success "Cloned repository successfully"
|
|
return 0
|
|
}
|
|
|
|
# Link plugin from GitHub
|
|
link_github_plugin() {
|
|
local plugin_name="$1"
|
|
local repo_url="${2:-}"
|
|
|
|
if [[ -z "$plugin_name" ]]; then
|
|
log_error "Usage: $0 link-github <plugin-name> [repo-url]"
|
|
exit 1
|
|
fi
|
|
|
|
load_config
|
|
|
|
# Construct repo URL if not provided
|
|
if [[ -z "$repo_url" ]]; then
|
|
repo_url="https://github.com/${GITHUB_USER}/${GITHUB_PATTERN}${plugin_name}.git"
|
|
log_info "Using default GitHub URL: $repo_url"
|
|
fi
|
|
|
|
# Determine target directory name from URL
|
|
local repo_name=$(basename "$repo_url" .git)
|
|
local target_dir="$DEV_PLUGINS_DIR/$repo_name"
|
|
|
|
# Check if already cloned
|
|
if [[ -d "$target_dir" ]]; then
|
|
log_info "Repository already exists at $target_dir"
|
|
if [[ -d "$target_dir/.git" ]]; then
|
|
log_info "Updating repository..."
|
|
(cd "$target_dir" && git pull --rebase || true)
|
|
fi
|
|
else
|
|
# Clone the repository
|
|
if ! clone_from_github "$repo_url" "$target_dir"; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Validate plugin structure
|
|
if ! validate_plugin "$target_dir"; then
|
|
log_error "Cloned repository does not appear to be a valid plugin"
|
|
exit 1
|
|
fi
|
|
|
|
# Link the plugin
|
|
link_plugin "$plugin_name" "$target_dir"
|
|
}
|
|
|
|
# Unlink a plugin
|
|
unlink_plugin() {
|
|
local plugin_name="$1"
|
|
|
|
if [[ -z "$plugin_name" ]]; then
|
|
log_error "Usage: $0 unlink <plugin-name>"
|
|
exit 1
|
|
fi
|
|
|
|
if ! plugin_exists "$plugin_name"; then
|
|
log_error "Plugin does not exist: $plugin_name"
|
|
exit 1
|
|
fi
|
|
|
|
if ! is_symlink "$PLUGINS_DIR/$plugin_name"; then
|
|
log_warn "Plugin $plugin_name is not a symlink. Cannot unlink."
|
|
exit 1
|
|
fi
|
|
|
|
local target=$(get_symlink_target "$plugin_name")
|
|
rm "$PLUGINS_DIR/$plugin_name"
|
|
log_success "Unlinked $plugin_name (repository preserved at $target)"
|
|
}
|
|
|
|
# List all plugins
|
|
list_plugins() {
|
|
if [[ ! -d "$PLUGINS_DIR" ]]; then
|
|
log_error "Plugins directory does not exist: $PLUGINS_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Plugin Status:${NC}"
|
|
echo "==============="
|
|
echo
|
|
|
|
local has_plugins=false
|
|
|
|
for item in "$PLUGINS_DIR"/*; do
|
|
[[ -e "$item" ]] || continue
|
|
[[ -d "$item" ]] || continue
|
|
|
|
local plugin_name=$(basename "$item")
|
|
[[ "$plugin_name" =~ ^\.|^_ ]] && continue
|
|
|
|
has_plugins=true
|
|
|
|
if is_symlink "$item"; then
|
|
local target=$(get_symlink_target "$plugin_name")
|
|
echo -e "${GREEN}✓${NC} ${BLUE}$plugin_name${NC} (symlink)"
|
|
echo " → $target"
|
|
|
|
# Check git status if it's a git repo
|
|
if [[ -d "$target/.git" ]]; then
|
|
local branch=$(cd "$target" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
local status=$(cd "$target" && git status --porcelain 2>/dev/null | head -1)
|
|
if [[ -n "$status" ]]; then
|
|
echo -e " ${YELLOW}⚠ Git repo has uncommitted changes${NC} (branch: $branch)"
|
|
else
|
|
echo -e " ${GREEN}✓ Git repo is clean${NC} (branch: $branch)"
|
|
fi
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}○${NC} ${BLUE}$plugin_name${NC} (regular directory)"
|
|
fi
|
|
echo
|
|
done
|
|
|
|
if [[ "$has_plugins" == false ]]; then
|
|
log_info "No plugins found in $PLUGINS_DIR"
|
|
fi
|
|
}
|
|
|
|
# Check status of all linked plugins
|
|
check_status() {
|
|
if [[ ! -d "$PLUGINS_DIR" ]]; then
|
|
log_error "Plugins directory does not exist: $PLUGINS_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Plugin Development Status:${NC}"
|
|
echo "========================="
|
|
echo
|
|
|
|
local broken_count=0
|
|
local clean_count=0
|
|
local dirty_count=0
|
|
|
|
for item in "$PLUGINS_DIR"/*; do
|
|
[[ -e "$item" ]] || continue
|
|
[[ -d "$item" ]] || continue
|
|
|
|
local plugin_name=$(basename "$item")
|
|
[[ "$plugin_name" =~ ^\.|^_ ]] && continue
|
|
|
|
if is_symlink "$item"; then
|
|
if [[ ! -e "$item" ]]; then
|
|
echo -e "${RED}✗${NC} ${BLUE}$plugin_name${NC} - ${RED}BROKEN SYMLINK${NC}"
|
|
broken_count=$((broken_count + 1))
|
|
continue
|
|
fi
|
|
|
|
local target=$(get_symlink_target "$plugin_name")
|
|
echo -e "${GREEN}✓${NC} ${BLUE}$plugin_name${NC}"
|
|
echo " Path: $target"
|
|
|
|
if [[ -d "$target/.git" ]]; then
|
|
local branch=$(cd "$target" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
local remote=$(cd "$target" && git remote get-url origin 2>/dev/null || echo "no remote")
|
|
local commits_behind=$(cd "$target" && git rev-list --count HEAD..@{upstream} 2>/dev/null || echo "0")
|
|
local commits_ahead=$(cd "$target" && git rev-list --count @{upstream}..HEAD 2>/dev/null || echo "0")
|
|
local status=$(cd "$target" && git status --porcelain 2>/dev/null)
|
|
|
|
echo " Branch: $branch"
|
|
echo " Remote: $remote"
|
|
|
|
if [[ -n "$status" ]]; then
|
|
echo -e " ${YELLOW}Status: Has uncommitted changes${NC}"
|
|
dirty_count=$((dirty_count + 1))
|
|
elif [[ "$commits_behind" != "0" ]] || [[ "$commits_ahead" != "0" ]]; then
|
|
if [[ "$commits_behind" != "0" ]]; then
|
|
echo -e " ${YELLOW}Status: $commits_behind commit(s) behind remote${NC}"
|
|
fi
|
|
if [[ "$commits_ahead" != "0" ]]; then
|
|
echo -e " ${GREEN}Status: $commits_ahead commit(s) ahead of remote${NC}"
|
|
fi
|
|
dirty_count=$((dirty_count + 1))
|
|
else
|
|
echo -e " ${GREEN}Status: Clean and up to date${NC}"
|
|
clean_count=$((clean_count + 1))
|
|
fi
|
|
else
|
|
echo " (Not a git repository)"
|
|
fi
|
|
echo
|
|
fi
|
|
done
|
|
|
|
echo "Summary:"
|
|
echo " ${GREEN}Clean: $clean_count${NC}"
|
|
echo " ${YELLOW}Needs attention: $dirty_count${NC}"
|
|
[[ $broken_count -gt 0 ]] && echo -e " ${RED}Broken: $broken_count${NC}"
|
|
}
|
|
|
|
# Update plugin(s)
|
|
update_plugins() {
|
|
local plugin_name="${1:-}"
|
|
|
|
load_config
|
|
|
|
if [[ -n "$plugin_name" ]]; then
|
|
# Update single plugin
|
|
if ! plugin_exists "$plugin_name"; then
|
|
log_error "Plugin does not exist: $plugin_name"
|
|
exit 1
|
|
fi
|
|
|
|
if ! is_symlink "$PLUGINS_DIR/$plugin_name"; then
|
|
log_error "Plugin $plugin_name is not a symlink"
|
|
exit 1
|
|
fi
|
|
|
|
local target=$(get_symlink_target "$plugin_name")
|
|
|
|
if [[ ! -d "$target/.git" ]]; then
|
|
log_error "Plugin repository is not a git repository: $target"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Updating $plugin_name from $target"
|
|
(cd "$target" && git pull --rebase)
|
|
log_success "Updated $plugin_name"
|
|
else
|
|
# Update all linked plugins
|
|
log_info "Updating all linked plugins..."
|
|
local updated=0
|
|
local failed=0
|
|
|
|
for item in "$PLUGINS_DIR"/*; do
|
|
[[ -e "$item" ]] || continue
|
|
[[ -d "$item" ]] || continue
|
|
|
|
local name=$(basename "$item")
|
|
[[ "$name" =~ ^\.|^_ ]] && continue
|
|
|
|
if is_symlink "$item"; then
|
|
local target=$(get_symlink_target "$name")
|
|
if [[ -d "$target/.git" ]]; then
|
|
log_info "Updating $name..."
|
|
if (cd "$target" && git pull --rebase); then
|
|
log_success "Updated $name"
|
|
updated=$((updated + 1))
|
|
else
|
|
log_error "Failed to update $name"
|
|
failed=$((failed + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo
|
|
log_info "Update complete: $updated succeeded, $failed failed"
|
|
fi
|
|
}
|
|
|
|
# Show usage
|
|
show_usage() {
|
|
cat << EOF
|
|
LEDMatrix Plugin Development Setup
|
|
|
|
Usage: $0 <command> [options]
|
|
|
|
Commands:
|
|
link <plugin-name> <repo-path>
|
|
Link a local plugin repository to the plugins directory
|
|
|
|
link-github <plugin-name> [repo-url]
|
|
Clone and link a plugin from GitHub
|
|
If repo-url is not provided, uses: https://github.com/${GITHUB_USER}/${GITHUB_PATTERN}<plugin-name>.git
|
|
|
|
unlink <plugin-name>
|
|
Remove symlink for a plugin (preserves repository)
|
|
|
|
list
|
|
List all plugins and their link status
|
|
|
|
status
|
|
Check status of all linked plugins (git status, branch, etc.)
|
|
|
|
update [plugin-name]
|
|
Update plugin(s) from git repository
|
|
If plugin-name is omitted, updates all linked plugins
|
|
|
|
help
|
|
Show this help message
|
|
|
|
Examples:
|
|
# Link a local plugin
|
|
$0 link music ../ledmatrix-music
|
|
|
|
# Link from GitHub (auto-detects URL)
|
|
$0 link-github music
|
|
|
|
# Link from GitHub with custom URL
|
|
$0 link-github stocks https://github.com/ChuckBuilds/ledmatrix-stocks.git
|
|
|
|
# Check status
|
|
$0 status
|
|
|
|
# Update all plugins
|
|
$0 update
|
|
|
|
Configuration:
|
|
Create dev_plugins.json in project root to customize:
|
|
- dev_plugins_dir: Where to clone GitHub repos (default: ~/.ledmatrix-dev-plugins)
|
|
- plugins: Plugin definitions (optional, for auto-discovery)
|
|
|
|
EOF
|
|
}
|
|
|
|
# Main command dispatcher
|
|
main() {
|
|
# Ensure plugins directory exists
|
|
mkdir -p "$PLUGINS_DIR"
|
|
|
|
case "${1:-}" in
|
|
link)
|
|
shift
|
|
link_plugin "$@"
|
|
;;
|
|
link-github)
|
|
shift
|
|
link_github_plugin "$@"
|
|
;;
|
|
unlink)
|
|
shift
|
|
unlink_plugin "$@"
|
|
;;
|
|
list)
|
|
list_plugins
|
|
;;
|
|
status)
|
|
check_status
|
|
;;
|
|
update)
|
|
shift
|
|
update_plugins "$@"
|
|
;;
|
|
help|--help|-h|"")
|
|
show_usage
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $1"
|
|
echo
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|
|
|