Skip to content

Instantly share code, notes, and snippets.

@lephuongbg
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lephuongbg/9824898 to your computer and use it in GitHub Desktop.
Save lephuongbg/9824898 to your computer and use it in GitHub Desktop.
Ruby Script to deploy Joomla Extension from package directory to Joomla installation directory
#!/usr/bin/env ruby
require 'nokogiri'
require 'colorize'
require 'fileutils'
require 'optparse'
require 'logger'
require 'rb-inotify'
DESTINATION = "/home/herop/public_html/joomla3"
include Nokogiri
class Deployer
attr_reader :path
attr_reader :manifest_path
def initialize(options = {})
@options = { verbose: false, debug: false, path: "." }
@options.merge! options
@path = @options[:path]
end
# Start the deployment operation
#
# @raise [RuntimeError] if error occurs
#
def deploy
if self.class == Deployer
extension_type = manifest.root.attr("type")
klass = Module.const_get(extension_type.capitalize + "Deployer")
if klass < Deployer
return klass.new(@options).deploy
end
end
raise RuntimeError, "Deployment for this type of extension is not implemented"
end
# Get and return the manifest of the extension
#
# == Returns:
# `Nokogiri::XML::Document` - Nokogiri::XML::Document object
#
# @raise [RuntimeError] if no manifest find found
def manifest
Dir.glob(path + "/*.xml").each do |file_name|
manifest = XML::parse(File::open(file_name))
# Found manifest
if manifest.root.name == "extension"
@manifest = manifest
@manifest_path = file_name
return @manifest
end
end
raise RuntimeError, "No manifest file found!"
end
# Get and return the name of the extension
#
# == Returns:
# String
#
def name
manifest.root.xpath("name/text()").to_s
end
# Return the logger for our deployer
#
# == Returns:
# Logger
def logger
if @logger.nil?
@logger = Logger.new(STDOUT)
@logger.level = Logger::WARN
if @options[:verbose]
@logger.level = Logger::INFO
end
if @options[:debug]
@logger.level = Logger::DEBUG
end
@logger.formatter = proc do |severity, datetime, progname, msg|
case severity
when "INFO"
msg.green + "\n"
when "WARN"
msg.yellow + "\n"
when "ERROR", "FATAL"
msg.red + "\n"
else
msg + "\n"
end
end
end
@logger
end
end
module FileUtilsOutputBuffer
def ob_start
@output = StringIO.new
@origin_output = @fileutils_output
@fileutils_output = @output
end
def ob_end
@fileutils_output = @origin_output
@output.string
end
end
module DeployerHelper
include FileUtilsOutputBuffer
include FileUtils::Verbose
#
# Copy files to site part
#
def copy_site_files(destination_path)
site_files_node = manifest.root.xpath("files")
if site_files_node.empty?
logger.info "No site files to copy!"
else
logger.info "STAGE #{stage}: Copy site files..."
ob_start
# Clean site files destination
if Dir.exist? destination_path
rm_r destination_path
end
# Remake site files destination
mkdir destination_path
# Copy site files
site_files_dir = site_files_node.attr("folder") || "."
cp_r(
site_files_node.xpath("*/text()").map do |node|
File.join(site_files_dir,node.content)
end,
destination_path
)
logger.debug ob_end
end
end
#
# Copy site languages
#
def copy_site_languages
site_languages_node = manifest.root.xpath("languages")
if site_languages_node.empty?
logger.info "No site languages to copy!"
else
logger.info "STAGE #{stage}: Copy site languages"
ob_start
site_languages_dir = site_languages_node.attr("folder") || "."
site_languages_node.xpath("language").each do |language_node|
language_path = File.join(site_languages_dir, language_node.xpath("text()").pop().content)
language_tag = language_node.attr("tag")
dest_language_path = File.join(DESTINATION, "language", language_tag)
if !Dir.exist?(dest_language_path)
Dir.mkdir(dest_language_path)
end
cp language_path, dest_language_path
end
logger.debug ob_end
end
end
#
# Copy files to media part
#
def copy_media_files
media_files_node = manifest.root.xpath("media")
if media_files_node.empty?
logger.info "No media files to copy!"
else
logger.info "STAGE #{stage}: Copy media files"
ob_start
media_files_dir = media_files_node.attr("folder") || "."
destination_path = File.join(DESTINATION, "media", media_files_node.attr("destination") || ".")
if Dir.exists? destination_path
rm_r destination_path
end
mkdir destination_path
cp_r(
media_files_node.xpath("*/text()").map do |node|
File.join(media_files_dir, node.content)
end,
destination_path
)
logger.debug ob_end
end
end
#
# Copy files to administrator part
#
def copy_admin_files(dest_path)
admin_files_node = manifest.root.xpath("administration/files")
if admin_files_node.empty?
logger.info "No adminitrator files to copy!"
else
logger.info "STAGE #{stage}: Copy administrator files"
ob_start
# Clean administrator files destination
if Dir.exist? dest_path
rm_r dest_path
end
# Remake administrator files destination
mkdir dest_path
# Copy administrator files
admin_files_dir = admin_files_node.attr("folder") || "."
cp_r(
admin_files_node.xpath("*/text()").map do |file_node|
File.join(admin_files_dir,file_node.content)
end,
dest_path
)
logger.debug ob_end
end
end
#
# Copy administrator languages
#
def copy_admin_languages
admin_languages_node = manifest.root.xpath("administration/languages")
if admin_languages_node.empty?
logger.info "No administrator languages to copy!"
else
logger.info "STAGE #{stage}: Copy administrator languages"
ob_start
admin_languages_dir = admin_languages_node.attr("folder") || "."
admin_languages_node.xpath("language").each do |language_node|
language_path = File.join(admin_languages_dir, language_node.xpath("text()").pop().content)
language_tag = language_node.attr("tag")
dest_language_path = File.join(DESTINATION, "administrator", "language", language_tag)
if !Dir.exist?(dest_language_path)
mkdir dest_language_path
end
cp language_path, dest_language_path
end
logger.debug ob_end
end
end
private
def stage
if @stage.nil?
@stage = 1
else
@stage += 1
end
@stage
end
end
# Component Adapter for Deployer
class ComponentDeployer < Deployer
include DeployerHelper
def deploy
logger.info "Joomla Component detected: #{name}"
copy_site_files File.join(DESTINATION, "components", name)
copy_site_languages
copy_media_files
copy_admin_files File.join(DESTINATION, "administrator", "components", name)
copy_admin_languages
logger.info "STAGE #{stage}: Deploy extension manifest"
ob_start
cp manifest_path, File.join(DESTINATION, "administrator", "components", name)
logger.debug ob_end
end
end
# Plugin Adapter for Deployer
class PluginDeployer < Deployer
include DeployerHelper
def deploy
logger.info "Joomla Plugin detected: #{name}"
group = manifest.root.attr("group")
copy_site_files File.join(DESTINATION, "plugins", group, name.sub("plg_#{group}_",""))
# Copy languages
# NOTE: Languages must be copy to administrator part
languages_node = manifest.root.xpath("languages")
if languages_node.empty?
logger.info "No languages to copy!"
else
logger.info "STAGE #{stage}: Copy languages"
ob_start
languages_dir = languages_node.attr("folder") || "."
languages_node.xpath("language").each do |language_node|
language_path = File.join(languages_dir, language_node.xpath("text()").pop().content)
language_tag = language_node.attr("tag")
dest_language_path = File.join(DESTINATION, "administrator", "language", language_tag)
if !Dir.exist?(dest_language_path)
mkdir dest_language_path
end
cp language_path, dest_language_path
end
logger.debug ob_end
end
end
end
# Module Adapter for Deployer
class ModuleDeployer < Deployer
include DeployerHelper
def deploy
logger.info "Joomla Module detected: #{name}"
copy_site_files File.join(DESTINATION, "modules", name)
copy_media_files
copy_site_languages
end
end
# Template Adapter for Deployer
class TemplateDeployer < Deployer
end
# Library Adapter for Deployer
class LibraryDeployer < Deployer
end
#
# START DEPLOYMENT
#
begin
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: joomext_deploy [options]"
opts.on("-v", "--verbose", "Display script stages") do
options[:verbose] = TRUE
end
opts.on("-b", "--debug", "Display executed commands and script stages. Override -v options") do
options[:debug] = TRUE
end
opts.on("-d", "--daemon", "Run this script as a daemon") do
options[:daemon] = TRUE
end
opts.on("-p [PATH]", "--path", "Choose a path to Joomla extension instead of current directory") do |p|
options[:path] = p
end
end.parse!
if options[:daemon]
notifier = INotify::Notifier.new
notifier.watch (options[:path] || "."), :moved_to, :recursive do |event|
Deployer.new(options).deploy
system('notify-send "Joomla Extension Deployed" "`date`" -i flag-blue')
end
notifier.run
else
Deployer.new(options).deploy
end
rescue SystemExit, Interrupt
puts "\n"
rescue RuntimeError => e
puts e.message.red
puts e.backtrace
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment