Example Plugins
This page provides two complete example plugins to help you quickly understand the development patterns for iRightMenu Pro plugins.
hello-world — Minimal Example
A minimal plugin demonstrating the basic plugin structure, menu registration, and system notifications.
Use case: Getting started, understanding the basic plugin structure.
Directory Structure
com.example.hello-world/
├── plugin.json # Plugin manifest
├── main.lua # Main script
└── locales/ # Internationalization
├── en.json
└── zh-Hans.json
plugin.json
{
"id": "com.example.hello-world",
"name": "{{plugin.name}}",
"version": "1.0.0",
"author": "Your Name",
"description": "{{plugin.description}}",
"locale": "en",
"main": "main.lua",
"menus": [
{
"id": "greet",
"title": "{{menu.greet}}",
"description": "Show a greeting notification",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true
},
{
"id": "count",
"title": "{{menu.count}}",
"description": "Count selected items",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true,
"minFiles": 1
}
]
}
main.lua
-- Create the plugin class
local HelloWorld = Plugin:extend()
-- Initialization: register menu handlers
function HelloWorld:init()
app.log.info("[HelloWorld] Plugin initialized")
-- Register handlers matching menus[].id in plugin.json
self:registerHandler("greet", self.showGreeting)
self:registerHandler("count", self.countItems)
end
-- Handler: show a greeting notification
function HelloWorld:showGreeting(context)
app.notification.show(
"Hello!",
"Welcome to iRightMenu Pro plugins!"
)
end
-- Handler: count selected items
function HelloWorld:countItems(context)
local files = context.selectedFiles
local count = #files
local message
if count == 1 then
message = "1 item selected"
else
message = count .. " items selected"
end
app.notification.show("Count", message)
end
-- Return the plugin class (the engine instantiates it automatically)
return HelloWorld
Localization Files
locales/zh-Hans.json
{
"plugin.name": "Hello World",
"plugin.description": "A simple example plugin",
"menu.greet": "Say Hello",
"menu.count": "Count Files"
}
file-processor — Advanced Example
A more complete plugin demonstrating plugin settings, progress bars, file operations, multiple menu items, error handling, and internationalization.
Use case: Learning advanced features, as a starting point for production-grade plugins.
Directory Structure
com.example.file-processor/
├── plugin.json
├── main.lua
└── locales/
├── en.json
└── zh-Hans.json
plugin.json
{
"id": "com.example.file-processor",
"name": "{{plugin.name}}",
"version": "1.0.0",
"author": "Your Name",
"description": "{{plugin.description}}",
"locale": "en",
"main": "main.lua",
"settings": {
"title": "{{settings.title}}",
"fields": [
{
"key": "output_format",
"type": "select",
"label": "{{settings.output_format}}",
"default": "txt",
"options": ["txt", "json", "csv"]
},
{
"key": "include_hidden",
"type": "checkbox",
"label": "{{settings.include_hidden}}",
"default": false
},
{
"key": "output_dir",
"type": "folder",
"label": "{{settings.output_dir}}",
"default": ""
}
]
},
"menus": [
{
"id": "list_files",
"title": "{{menu.list_files}}",
"description": "Generate a file list from selected items",
"enabled": true,
"showOnFiles": true,
"showOnFolders": true,
"minFiles": 1
},
{
"id": "calculate_hash",
"title": "{{menu.calculate_hash}}",
"description": "Calculate MD5 hash of selected files",
"enabled": true,
"showOnFiles": true,
"showOnFolders": false,
"minFiles": 1
}
]
}
Settings field types:
text(text input),checkbox(toggle),select(dropdown),folder(folder picker). Plugins read user settings viaapp.settings.get(key, default).
main.lua
local FileProcessor = Plugin:extend()
function FileProcessor:init()
self:registerHandler("list_files", self.listFiles)
self:registerHandler("calculate_hash", self.calculateHash)
end
-- ========================================
-- Generate file list
-- ========================================
function FileProcessor:listFiles(context)
local files = context.selectedFiles
if #files == 0 then
app.dialog.alert("Error", "No files selected")
return
end
-- Read plugin settings
local format = app.settings.get("output_format", "txt")
local includeHidden = app.settings.get("include_hidden", false)
local outputDir = app.settings.get("output_dir", "")
if outputDir == "" then
outputDir = context.currentDirectory
end
local outputFile = app.path.join(outputDir, "file-list." .. format)
-- Show progress bar
app.progress.show(app.i18n.t("progress.scanning"), {
message = app.i18n.t("progress.preparing"),
indeterminate = true
})
-- Collect file information
local fileList = {}
for _, path in ipairs(files) do
local basename = app.path.basename(path)
if includeHidden or not basename:match("^%.") then
table.insert(fileList, {
name = basename,
path = path,
size = app.file.size(path) or 0,
isDir = app.path.isDirectory(path)
})
end
end
app.progress.hide()
-- Format output based on settings
local content
if format == "json" then
content = app.json.stringify(fileList, true)
elseif format == "csv" then
content = self:formatAsCSV(fileList)
else
content = self:formatAsTxt(fileList)
end
-- Write to file
local success = app.file.write(outputFile, content)
if success then
app.notification.show(
app.i18n.t("notify.success"),
app.i18n.t("notify.file_created", outputFile)
)
app.finder.reveal(outputFile)
else
app.dialog.alert(
app.i18n.t("notify.error"),
app.i18n.t("notify.write_failed")
)
end
end
-- ========================================
-- Calculate MD5 hash
-- ========================================
function FileProcessor:calculateHash(context)
local files = context.selectedFiles
-- Filter out directories, keep only files
local regularFiles = {}
for _, path in ipairs(files) do
if app.path.isFile(path) then
table.insert(regularFiles, path)
end
end
if #regularFiles == 0 then
app.dialog.alert("Error", "No regular files selected")
return
end
-- Hash calculation with progress
app.progress.show(app.i18n.t("progress.hashing"), {
message = "",
indeterminate = false
})
local results = {}
for i, path in ipairs(regularFiles) do
local percent = (i / #regularFiles) * 100
local filename = app.path.basename(path)
app.progress.update(percent, filename)
local hash = app.crypto.md5File(path)
if hash then
table.insert(results, { name = filename, hash = hash })
end
end
app.progress.hide()
-- Display results
if #results > 0 then
local message = ""
for _, r in ipairs(results) do
message = message .. r.name .. "\n" .. r.hash .. "\n\n"
end
app.dialog.alert(app.i18n.t("dialog.hash_results"), message)
end
end
-- ========================================
-- Utility functions
-- ========================================
function FileProcessor:formatAsTxt(fileList)
local lines = {}
for _, info in ipairs(fileList) do
local sizeStr = self:formatSize(info.size)
local typeStr = info.isDir and "[DIR]" or "[FILE]"
table.insert(lines, string.format("%s %s %s", typeStr, sizeStr, info.name))
end
return table.concat(lines, "\n")
end
function FileProcessor:formatAsCSV(fileList)
local lines = {"name,path,size,type"}
for _, info in ipairs(fileList) do
local typeStr = info.isDir and "directory" or "file"
table.insert(lines, string.format('"%s","%s",%d,%s',
info.name, info.path, info.size, typeStr))
end
return table.concat(lines, "\n")
end
function FileProcessor:formatSize(bytes)
if bytes < 1024 then
return string.format("%d B", bytes)
elseif bytes < 1024 * 1024 then
return string.format("%.1f KB", bytes / 1024)
elseif bytes < 1024 * 1024 * 1024 then
return string.format("%.1f MB", bytes / (1024 * 1024))
else
return string.format("%.2f GB", bytes / (1024 * 1024 * 1024))
end
end
return FileProcessor
Localization Files
locales/zh-Hans.json
{
"plugin.name": "File Processor",
"plugin.description": "Process files and generate reports",
"settings.title": "File Processor Settings",
"settings.output_format": "Output Format",
"settings.include_hidden": "Include Hidden Files",
"settings.output_dir": "Output Directory",
"menu.list_files": "Generate File List",
"menu.calculate_hash": "Calculate MD5 Hash",
"progress.scanning": "Scanning files",
"progress.preparing": "Preparing...",
"progress.hashing": "Calculating hashes",
"notify.success": "Success",
"notify.error": "Error",
"notify.file_created": "File created: {0}",
"notify.write_failed": "Failed to write file",
"dialog.hash_results": "MD5 Hash Results"
}
Quick Test
Copy an example plugin to the plugins directory to test it:
cp -r hello-world ~/Library/Group\ Containers/group.net.ymlab.iRightMenu.pro/Plugins/com.example.hello-world
Then reload plugins in iRightMenu Pro, or restart Finder.
Using Examples as Templates
- Copy an example directory and rename it to your plugin ID (e.g.,
com.yourname.myplugin) - Update
id,name,version, and other fields inplugin.json - Write your plugin logic in
main.lua - Update the localization files under
locales/