Update to current Perl
This commit is contained in:
parent
ebd46d86d4
commit
3cc88f8150
57 changed files with 11638 additions and 665 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/*.kpf
|
||||
.DS_Store
|
||||
/*.kpf
|
||||
|
|
|
|||
169
Dockerfile
Normal file
169
Dockerfile
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
FROM debian:latest
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt update && apt -y install perl cpanminus libaspell-dev make libdbd-mysql-perl libdigest-perl-md5-perl libxml-simple-perl \
|
||||
libmodule-install-perl gcc libperl-dev default-libmysqlclient-dev libpng-dev build-essential libgd-dev mariadb-client imagemagick \
|
||||
libpng-dev libjpeg-dev libtiff-dev libapache2-mod-perl2 libapache2-mod-perl2-dev libapache2-request-perl libimage-magick-perl vim \
|
||||
apache2 apache2-utils
|
||||
|
||||
|
||||
RUN cpanm --notest --force \
|
||||
Algorithm::Permute \
|
||||
App::Cmd \
|
||||
Archive::Any \
|
||||
Archive::Tar \
|
||||
Archive::Zip \
|
||||
Business::OnlinePayment \
|
||||
Business::OnlinePayment::AuthorizeNet \
|
||||
Business::PayPal::API \
|
||||
Business::Tax::VAT::Validation \
|
||||
CHI \
|
||||
CSS::Minifier::XS \
|
||||
CSS::Packer \
|
||||
Cache::FastMmap \
|
||||
Capture::Tiny \
|
||||
Class::C3 \
|
||||
Class::InsideOut \
|
||||
Clone \
|
||||
Color::Calc \
|
||||
Compress::Zlib \
|
||||
Config::JSON \
|
||||
DBI \
|
||||
Data::ICal \
|
||||
DateTime \
|
||||
DateTime::Event::ICal \
|
||||
DateTime::Format::HTTP \
|
||||
DateTime::Format::Mail \
|
||||
DateTime::Format::Strptime \
|
||||
DBD::mysql@4.051 \
|
||||
Devel::StackTrace \
|
||||
Devel::StackTrace::WithLexicals \
|
||||
Digest::MD5 \
|
||||
Digest::SHA \
|
||||
Email::Valid \
|
||||
Exception::Class \
|
||||
Facebook::Graph \
|
||||
File::Path \
|
||||
Finance::Quote \
|
||||
GD \
|
||||
GD::Graph \
|
||||
Geo::Coder::Googlev3 \
|
||||
HTML::Form \
|
||||
HTML::Highlight \
|
||||
HTML::Packer \
|
||||
HTML::Parser \
|
||||
HTML::TagCloud \
|
||||
HTML::TagFilter \
|
||||
HTML::Template \
|
||||
HTML::Template::Expr \
|
||||
HTTP::BrowserDetect \
|
||||
HTTP::Exception \
|
||||
HTTP::Headers \
|
||||
HTTP::Request \
|
||||
IO::File::WithPath \
|
||||
IO::Interactive::Tiny \
|
||||
IO::Socket::SSL \
|
||||
IO::Zlib \
|
||||
Image::ExifTool \
|
||||
Imager \
|
||||
Imager::File::PNG \
|
||||
JSON \
|
||||
JSON::Any \
|
||||
JSON::PP \
|
||||
JavaScript::Minifier::XS \
|
||||
JavaScript::Packer \
|
||||
Kwargs \
|
||||
LWP \
|
||||
LWP::Protocol::https \
|
||||
List::MoreUtils \
|
||||
Locales \
|
||||
Log::Log4perl \
|
||||
MIME::Tools \
|
||||
Module::Find \
|
||||
Monkey::Patch \
|
||||
Moose \
|
||||
MooseX::NonMoose \
|
||||
MooseX::Storage \
|
||||
MooseX::Storage::Format::JSON \
|
||||
Net::CIDR::Lite \
|
||||
Net::DNS \
|
||||
Net::LDAP \
|
||||
Net::POP3 \
|
||||
Net::SMTP \
|
||||
Net::Twitter \
|
||||
Number::Format \
|
||||
POE \
|
||||
POE::Component::Client::HTTP \
|
||||
POE::Component::IKC::Server \
|
||||
POE::Component::IKC \
|
||||
Package::Stash \
|
||||
Params::Validate \
|
||||
Path::Class \
|
||||
PerlIO::eol \
|
||||
Plack \
|
||||
Plack::Middleware::Debug \
|
||||
Plack::Middleware::Status \
|
||||
Plack::Request \
|
||||
Plack::Response \
|
||||
Pod::Coverage \
|
||||
Readonly \
|
||||
Scope::Guard \
|
||||
Search::QueryParser \
|
||||
Storable \
|
||||
Template \
|
||||
Test::Class \
|
||||
Test::Deep \
|
||||
Test::Differences \
|
||||
Test::Exception \
|
||||
Test::Harness \
|
||||
Test::Log::Dispatch \
|
||||
Test::LongString \
|
||||
Test::MockObject \
|
||||
Test::MockTime \
|
||||
Test::More \
|
||||
Test::Tester \
|
||||
Test::WWW::Mechanize::PSGI \
|
||||
Text::Aspell \
|
||||
Text::Balanced \
|
||||
Text::CSV_XS \
|
||||
Tie::CPHash \
|
||||
Tie::IxHash \
|
||||
Time::HiRes \
|
||||
Try::Tiny \
|
||||
URI::Escape \
|
||||
UUID::Tiny \
|
||||
Weather::Com::Finder \
|
||||
XML::FeedPP \
|
||||
XML::FeedPP::MediaRSS \
|
||||
XML::Simple \
|
||||
common::sense \
|
||||
namespace::autoclean
|
||||
|
||||
|
||||
#RUN cpanm --notest --force \
|
||||
# DBD::mysql@4.051
|
||||
|
||||
|
||||
ADD lib /data/WebGUI/lib
|
||||
ADD sbin /data/WebGUI/sbin
|
||||
ADD share /data/WebGUI/share
|
||||
ADD www/extras /data/WebGUI/www/extras
|
||||
ADD www/maintenance.html /data/WebGUI/www/maintenance.html
|
||||
ADD www/uploads /data/WebGUI/uploads
|
||||
ADD etc /data/WebGUI/etc
|
||||
|
||||
ADD distribution/webgui/entrypoint /entrypoint
|
||||
|
||||
ADD distribution/webgui/www.example.com.conf /etc/apache2/sites-available/000-default.conf
|
||||
ADD distribution/webgui/webgui.conf /etc/apache2/mods-enabled/webgui.conf
|
||||
ADD distribution/webgui/modperl.pl /etc/apache2/modperl.pl
|
||||
RUN useradd --home=/data/WebGUI webgui; chown -R webgui: /data/WebGUI; chmod 755 /entrypoint; \
|
||||
# apt remove -y cpanminus make gcc libperl-dev ; rm -rf /root/.cpanm; \
|
||||
echo "ServerName localhost" >> /etc/apache2/apache2.conf;
|
||||
|
||||
#USER webgui
|
||||
|
||||
WORKDIR /data/WebGUI
|
||||
|
||||
CMD [ "/entrypoint" ]
|
||||
41
distribution/docker-compose.yml
Normal file
41
distribution/docker-compose.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# This file is ideal for development and testing.
|
||||
# --- DO NOT USE FOR PRODUCTION ---
|
||||
# however, you can use it as a guide to create a proper production environment.
|
||||
services:
|
||||
db:
|
||||
image: mariadb
|
||||
container_name: db
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=123qwe
|
||||
- MYSQL_USER=webgui
|
||||
- MYSQL_PASSWORD=123qwe
|
||||
- MYSQL_DATABASE=www_example_com
|
||||
volumes:
|
||||
- ./share/create.sql:/docker-entrypoint-initdb.d/1.sql
|
||||
ports:
|
||||
- 3306
|
||||
|
||||
webgui:
|
||||
image: webgui
|
||||
container_name: webgui
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- ../.:/data/WebGUI
|
||||
expose:
|
||||
- "80"
|
||||
|
||||
nginx-proxy:
|
||||
image: nginx:latest
|
||||
container_name: nginx_proxy
|
||||
depends_on:
|
||||
- webgui
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
ports:
|
||||
- "80:80"
|
||||
#TODO: add ssl later
|
||||
#- "443:443"
|
||||
# TODO: serve staatic files directly from nginx
|
||||
#- ./www:/var/www/html:ro
|
||||
|
||||
20
distribution/nginx/nginx.conf
Normal file
20
distribution/nginx/nginx.conf
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
worker_processes auto;
|
||||
events {}
|
||||
|
||||
http {
|
||||
upstream backend {
|
||||
server webgui:80;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
2543
distribution/share/create.sql
Normal file
2543
distribution/share/create.sql
Normal file
File diff suppressed because one or more lines are too long
43
distribution/webgui/entrypoint
Normal file
43
distribution/webgui/entrypoint
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash
|
||||
|
||||
# script that runs when the docker container starts
|
||||
echo "Starting WebGUI entrypoint script..."
|
||||
for configfile in `perl -Ilib sbin/wgd for-each`;do
|
||||
|
||||
echo "Processing config file: $configfile"
|
||||
# wait for the db to come up and get our version so we know if we need to run upgrades
|
||||
connection_info=`perl -Ilib sbin/wgd db --config-file=$configfile --webgui-root=/data/WebGUI --print | sed "s/'//g" `
|
||||
while true; do
|
||||
db_version=`mysql --skip-column-names -e 'select webguiVersion from webguiVersion order by dateApplied desc limit 1;' $connection_info ` && break
|
||||
echo "waiting for the database to come up..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
code_version=`perl -Ilib -e 'use WebGUI; print $WebGUI::VERSION;'`
|
||||
|
||||
echo "code version $code_version"
|
||||
echo "database version $db_version for $configfile"
|
||||
|
||||
#this table shoud have a defult value. Shoud be in upgrade.pl to new version
|
||||
`mysql -e 'alter table userSession MODIFY COLUMN userId char(22) NOT NULL DEFAULT "0";' $connection_info ` && break
|
||||
`mysql -e 'alter table assetVersionTag MODIFY COLUMN name char(255) NOT NULL DEFAULT "default";' $connection_info ` && break
|
||||
if [ "$db_version" != "$code_version" ];then
|
||||
perl -Ilib sbin/wgd reset --upgrade --verbose --config-file=$configfile --webgui-root=/data/WebGUI/
|
||||
### alter table userSession MODIFY COLUMN userId char(22) NOT NULL DEFAULT '0';
|
||||
|
||||
## perl -Ilib sbin/wgd db --config-file=www.example.com --webgui-root=/data/WebGUI
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
#perl -I/data/WebGUI/lib sbin/spectre.pl --daemon
|
||||
|
||||
service apache2 start
|
||||
|
||||
while true; do
|
||||
echo "Starting Apache web server..."
|
||||
sleep 60
|
||||
done
|
||||
|
||||
apachectl -DFOREGROUND
|
||||
6
distribution/webgui/modperl.pl
Normal file
6
distribution/webgui/modperl.pl
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#use Apache2::SizeLimit;
|
||||
#$Apache2::SizeLimit::MAX_PROCESS_SIZE = 600000;
|
||||
#$Apache2::SizeLimit::MAX_UNSHARED_SIZE = 600000;
|
||||
#$Apache2::SizeLimit::CHECK_EVERY_N_REQUESTS = 10;
|
||||
|
||||
1;
|
||||
4
distribution/webgui/webgui.conf
Normal file
4
distribution/webgui/webgui.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
PerlSetVar WebguiRoot /data/WebGUI
|
||||
#PerlCleanupHandler Apache2::SizeLimit
|
||||
PerlRequire /data/WebGUI/sbin/preload.perl
|
||||
PerlRequire /etc/apache2/modperl.pl
|
||||
16
distribution/webgui/www.example.com.conf
Normal file
16
distribution/webgui/www.example.com.conf
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<VirtualHost 127.0.0.1:80>
|
||||
ServerName www.example.com
|
||||
ServerAlias localhost example.com unitedknowledge.vps2.unitedknowledge.net www.unitedknowledge.eu unitedknowledge.eu www.united-knowledge.eu united-knowledge.eu www.unitedknowledge.net unitedknowledge.net
|
||||
DocumentRoot /data/WebGUI/www
|
||||
SetEnvIf SSLPROXY "1" \
|
||||
SSLPROXY
|
||||
SetHandler perl-script
|
||||
PerlInitHandler WebGUI
|
||||
CustomLog /var/log/apache2/www.example.com.access.log combined
|
||||
ErrorLog /var/log/apache2/www.example.com.error.log
|
||||
PerlSetVar WebguiConfig www.example.com.conf
|
||||
|
||||
<Directory "/data/WebGUI/www">
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
1178
docs/create.sql
1178
docs/create.sql
File diff suppressed because one or more lines are too long
602
lib/WGDev.pm
Normal file
602
lib/WGDev.pm
Normal file
|
|
@ -0,0 +1,602 @@
|
|||
package WGDev;
|
||||
# ABSTRACT: WebGUI Developer Utilities
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use File::Spec ();
|
||||
use Cwd ();
|
||||
use WGDev::X ();
|
||||
use Try::Tiny;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = bless {}, $class;
|
||||
my $root;
|
||||
my $config;
|
||||
if ( $_[0] && -d $_[0] ) {
|
||||
( $root, $config ) = @_;
|
||||
}
|
||||
else {
|
||||
( $config, $root ) = @_;
|
||||
}
|
||||
if ($root) {
|
||||
$self->root($root);
|
||||
}
|
||||
if ($config) {
|
||||
$self->config_file($config);
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub set_environment {
|
||||
my $self = shift;
|
||||
my %options = @_;
|
||||
require Config;
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if !$self->root;
|
||||
WGDev::X::NoWebGUIConfig->throw
|
||||
if !$self->config_file;
|
||||
if (! $options{localized}) {
|
||||
$self->{orig_env}
|
||||
||= { map { $_ => $ENV{$_} } qw(WEBGUI_ROOT WEBGUI_CONFIG PERL5LIB) };
|
||||
}
|
||||
##no critic (RequireLocalizedPunctuationVars)
|
||||
$ENV{WEBGUI_ROOT} = $self->root;
|
||||
$ENV{WEBGUI_CONFIG} = $self->config_file;
|
||||
$ENV{PERL5LIB} = join $Config::Config{path_sep}, $self->lib,
|
||||
$ENV{PERL5LIB} || ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub reset_environment {
|
||||
my $self = shift;
|
||||
my $orig_env = delete $self->{orig_env};
|
||||
return
|
||||
if !$orig_env;
|
||||
##no critic (RequireLocalizedPunctuationVars)
|
||||
@ENV{ keys %{$orig_env} } = values %{$orig_env};
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub root {
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
my $path = shift;
|
||||
if ( -d $path
|
||||
&& -e File::Spec->catfile( $path, 'lib', 'WebGUI.pm' )
|
||||
)
|
||||
{
|
||||
$self->{root} = File::Spec->rel2abs($path);
|
||||
$self->{lib} = File::Spec->catdir( $self->{root}, 'lib' );
|
||||
unshift @INC, $self->lib;
|
||||
}
|
||||
else {
|
||||
WGDev::X::BadParameter->throw(
|
||||
'parameter' => 'WebGUI root directory',
|
||||
'value' => $path,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $self->{root};
|
||||
}
|
||||
|
||||
sub config_file {
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
my $path = shift;
|
||||
require Config::JSON;
|
||||
if ( -e $path ) {
|
||||
}
|
||||
elsif (
|
||||
$self->root
|
||||
&& -e (
|
||||
my $fullpath
|
||||
= File::Spec->catfile( $self->root, 'etc', $path ) ) )
|
||||
{
|
||||
$path = $fullpath;
|
||||
}
|
||||
else {
|
||||
WGDev::X::BadParameter->throw(
|
||||
'parameter' => 'WebGUI config file',
|
||||
'value' => $path,
|
||||
);
|
||||
}
|
||||
if ( !$self->root ) {
|
||||
try {
|
||||
$self->root(
|
||||
File::Spec->catpath(
|
||||
( File::Spec->splitpath($path) )[ 0, 1 ],
|
||||
File::Spec->updir
|
||||
) );
|
||||
};
|
||||
}
|
||||
my $path_abs = File::Spec->rel2abs($path);
|
||||
my $config;
|
||||
if ( ! try { $config = Config::JSON->new($path_abs) } ) {
|
||||
WGDev::X::BadParameter->throw(
|
||||
'parameter' => 'WebGUI config file',
|
||||
'value' => $path,
|
||||
);
|
||||
}
|
||||
$self->close_session;
|
||||
$self->close_config;
|
||||
$self->{config_file} = $path_abs;
|
||||
$self->{config} = $config;
|
||||
delete $self->{config_file_relative};
|
||||
}
|
||||
return $self->{config_file};
|
||||
}
|
||||
|
||||
sub lib {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if !$self->root;
|
||||
if ( !wantarray ) {
|
||||
return $self->{lib};
|
||||
}
|
||||
my @lib = $self->{lib};
|
||||
if ( !$self->{custom_lib} ) {
|
||||
my @custom_lib;
|
||||
$self->{custom_lib} = \@custom_lib;
|
||||
my $custom
|
||||
= File::Spec->catfile( $self->root, 'sbin', 'preload.custom' );
|
||||
if ( -e $custom && open my $fh, '<', $custom ) {
|
||||
while ( my $line = <$fh> ) {
|
||||
$line =~ s/[#].*//msx;
|
||||
$line =~ s/\A\s+//msx;
|
||||
$line =~ s/\s+\z//msx;
|
||||
if ( -d $line ) {
|
||||
unshift @custom_lib, $line;
|
||||
}
|
||||
}
|
||||
close $fh or WGDev::X::IO::Read->throw( path => $custom );
|
||||
}
|
||||
}
|
||||
unshift @lib, @{ $self->{custom_lib} };
|
||||
return @lib;
|
||||
}
|
||||
|
||||
sub config {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIConfig->throw
|
||||
if !$self->config_file;
|
||||
return $self->{config} ||= do {
|
||||
require Config::JSON;
|
||||
Config::JSON->new( $self->config_file );
|
||||
};
|
||||
}
|
||||
|
||||
sub close_config {
|
||||
my $self = shift;
|
||||
delete $self->{config};
|
||||
|
||||
# if we're closing the config, we probably want new sessions to pick up
|
||||
# changes to the file
|
||||
if ( WebGUI::Config->can('clearCache') ) {
|
||||
WebGUI::Config->clearCache;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub config_file_relative {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIConfig->throw
|
||||
if !$self->config_file;
|
||||
return $self->{config_file_relative} ||= do {
|
||||
my $config_dir
|
||||
= Cwd::realpath( File::Spec->catdir( $self->root, 'etc' ) );
|
||||
File::Spec->abs2rel( $self->config_file, $config_dir );
|
||||
};
|
||||
}
|
||||
|
||||
sub db {
|
||||
my $self = shift;
|
||||
require WGDev::Database;
|
||||
return $self->{db} ||= WGDev::Database->new( $self->config );
|
||||
}
|
||||
|
||||
sub session {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIConfig->throw
|
||||
if !$self->config_file;
|
||||
require WebGUI::Session;
|
||||
if ( $self->{session} ) {
|
||||
my $dbh = $self->{session}->db->dbh;
|
||||
|
||||
# if the database handle died, close the session
|
||||
if ( !$dbh->ping ) {
|
||||
delete $self->{asset};
|
||||
( delete $self->{session} )->close;
|
||||
}
|
||||
}
|
||||
return $self->{session} ||= do {
|
||||
my $session = $self->create_session($self->{session_id});
|
||||
$self->{session_id} = $session->getId;
|
||||
$session;
|
||||
};
|
||||
}
|
||||
|
||||
sub create_session {
|
||||
my $self = shift;
|
||||
my $session_id = shift;
|
||||
my $session;
|
||||
if ( $self->version->module =~ /^8[.]/msx ) {
|
||||
$session
|
||||
= WebGUI::Session->open( $self->config_file,
|
||||
undef, undef, $session_id );
|
||||
}
|
||||
else {
|
||||
$session
|
||||
= WebGUI::Session->open( $self->root, $self->config_file_relative,
|
||||
undef, undef, $session_id );
|
||||
}
|
||||
return $session;
|
||||
}
|
||||
|
||||
sub close_session {
|
||||
my $self = shift;
|
||||
if ( $self->{session} ) { # if we have a cached session
|
||||
my $session = $self->session; # get the session, recreating if needed
|
||||
$session->var->end; # close the session
|
||||
$session->close;
|
||||
delete $self->{asset};
|
||||
delete $self->{session};
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub list_site_configs {
|
||||
my $self = shift;
|
||||
my $root = $self->root;
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if !$root;
|
||||
|
||||
if ( opendir my $dh, File::Spec->catdir( $root, 'etc' ) ) {
|
||||
my @configs = readdir $dh;
|
||||
closedir $dh
|
||||
or WGDev::X::IO::Read->throw('Unable to close directory handle');
|
||||
@configs = map { File::Spec->catdir( $root, 'etc', $_ ) }
|
||||
grep { /\Q.conf\E$/msx && !/^(?:spectre|log)\Q.conf\E$/msx }
|
||||
@configs;
|
||||
return @configs;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub asset {
|
||||
my $self = shift;
|
||||
require WGDev::Asset;
|
||||
return $self->{asset} ||= WGDev::Asset->new( $self->session );
|
||||
}
|
||||
|
||||
sub version {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if !$self->root;
|
||||
require WGDev::Version;
|
||||
return $self->{version} ||= WGDev::Version->new( $self->root );
|
||||
}
|
||||
|
||||
sub wgd_config { ##no critic (ProhibitExcessComplexity)
|
||||
my ( $self, $key_list, $value ) = @_;
|
||||
my $config = \( $self->{wgd_config} );
|
||||
if ( !${$config} ) {
|
||||
$config = \( $self->read_wgd_config );
|
||||
}
|
||||
my @keys;
|
||||
if ( ref $key_list && ref $key_list eq 'ARRAY' ) {
|
||||
@keys = @{$key_list};
|
||||
}
|
||||
else {
|
||||
@keys = split /[.]/msx, $key_list;
|
||||
}
|
||||
|
||||
if ( !${$config} ) {
|
||||
$config = \( $self->{wgd_config} = {} );
|
||||
}
|
||||
while (@keys) {
|
||||
my $key = shift @keys;
|
||||
my $numeric = $key ne q{} && $key =~ /^[+]?-?\d*$/msx;
|
||||
my $type = ref ${$config};
|
||||
if ( ( !$type && !defined $value )
|
||||
|| $type eq 'SCALAR'
|
||||
|| ( $type eq 'ARRAY' && !$numeric ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
elsif ( $type eq 'ARRAY' or ( !$type && $numeric ) ) {
|
||||
if ( !$type ) {
|
||||
${$config} = [];
|
||||
}
|
||||
my ($insert) = $key =~ s/^([+])//msx;
|
||||
if ( !defined $value
|
||||
&& ( $insert || !defined ${$config}->[$key] ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ($insert) {
|
||||
if ( $key ne q{} ) {
|
||||
if ( $key < 0 ) {
|
||||
$key += @{ ${$config} };
|
||||
}
|
||||
splice @{ ${$config} }, $key, 0, undef;
|
||||
}
|
||||
else {
|
||||
$key = @{ ${$config} };
|
||||
}
|
||||
}
|
||||
$config = \( ${$config}->[$key] );
|
||||
}
|
||||
else {
|
||||
if ( !$type ) {
|
||||
${$config} = {};
|
||||
}
|
||||
if ( !defined ${$config}->{$key} && !defined $value ) {
|
||||
return;
|
||||
}
|
||||
$config = \( ${$config}->{$key} );
|
||||
}
|
||||
if (@keys) {
|
||||
next;
|
||||
}
|
||||
if ($value) {
|
||||
return ${$config} = $value;
|
||||
}
|
||||
return ${$config};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
my $json;
|
||||
|
||||
sub read_wgd_config {
|
||||
my $self = shift;
|
||||
for my $config_file ( "$ENV{HOME}/.wgdevcfg", '/etc/wgdevcfg' ) {
|
||||
if ( -e $config_file ) {
|
||||
my $config;
|
||||
open my $fh, '<', $config_file or next;
|
||||
my $content = do { local $/; <$fh> };
|
||||
close $fh or next;
|
||||
$self->{wgd_config_path} = Cwd::realpath($config_file);
|
||||
if ( $content eq q{} ) {
|
||||
$config = {};
|
||||
}
|
||||
else {
|
||||
if ( !$json ) {
|
||||
require JSON;
|
||||
$json = JSON->new;
|
||||
$json->utf8;
|
||||
$json->relaxed;
|
||||
$json->canonical;
|
||||
$json->pretty;
|
||||
}
|
||||
$config = try { $json->decode($content) } || {};
|
||||
}
|
||||
return $self->{wgd_config} = $config;
|
||||
}
|
||||
}
|
||||
return $self->{wgd_config} = {};
|
||||
}
|
||||
|
||||
sub write_wgd_config {
|
||||
my $self = shift;
|
||||
my $config_path = $self->{wgd_config_path};
|
||||
if ( !$self->{wgd_config_path} ) {
|
||||
$config_path = $self->{wgd_config_path} = $ENV{HOME} . '/.wgdevcfg';
|
||||
}
|
||||
my $config = $self->{wgd_config} || {};
|
||||
if ( !$json ) {
|
||||
require JSON;
|
||||
$json = JSON->new;
|
||||
$json->utf8;
|
||||
$json->relaxed;
|
||||
$json->canonical;
|
||||
$json->pretty;
|
||||
}
|
||||
my $encoded = $json->encode($config);
|
||||
$encoded =~ s/\n?\z/\n/msx;
|
||||
open my $fh, '>', $config_path
|
||||
or WGDev::X::IO::Write->throw(
|
||||
message => 'Unable to write config file',
|
||||
path => $config_path,
|
||||
);
|
||||
print {$fh} $encoded;
|
||||
close $fh
|
||||
or WGDev::X::IO::Write->throw(
|
||||
message => 'Unable to write config file',
|
||||
path => $config_path,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub my_config {
|
||||
my $self = shift;
|
||||
my $key = shift;
|
||||
my @keys;
|
||||
if ( ref $key && ref $key eq 'ARRAY' ) {
|
||||
@keys = @{$key};
|
||||
}
|
||||
else {
|
||||
@keys = split /[.]/msx, $key;
|
||||
}
|
||||
my $caller = caller;
|
||||
my $remove = ( ref $self ) . q{::};
|
||||
$caller =~ s/^\Q$remove//msx;
|
||||
unshift @keys, map { lcfirst $_ } split /::/msx, $caller;
|
||||
return $self->wgd_config( \@keys, @_ );
|
||||
}
|
||||
|
||||
sub yaml_decode {
|
||||
_load_yaml_lib();
|
||||
goto &yaml_decode;
|
||||
}
|
||||
|
||||
sub yaml_encode {
|
||||
_load_yaml_lib();
|
||||
goto &yaml_encode;
|
||||
}
|
||||
|
||||
sub _load_yaml_lib {
|
||||
## no critic (ProhibitCascadingIfElse)
|
||||
no warnings 'redefine';
|
||||
if ( try { require YAML::XS } ) {
|
||||
*yaml_encode = \&YAML::XS::Dump;
|
||||
*yaml_decode = \&YAML::XS::Load;
|
||||
}
|
||||
elsif ( try { require YAML::Syck } ) {
|
||||
*yaml_encode = \&YAML::Syck::Dump;
|
||||
*yaml_decode = \&YAML::Syck::Load;
|
||||
}
|
||||
elsif ( try { require YAML } ) {
|
||||
*yaml_encode = \&YAML::Dump;
|
||||
*yaml_decode = \&YAML::Load;
|
||||
}
|
||||
elsif ( try { require YAML::Tiny } ) {
|
||||
*yaml_encode = \&YAML::Tiny::Dump;
|
||||
*yaml_decode = \&YAML::Tiny::Load;
|
||||
}
|
||||
else {
|
||||
*yaml_encode = *yaml_decode = sub {
|
||||
WGDev::X->throw('No YAML library available!');
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
local $@;
|
||||
try {
|
||||
$self->close_session;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use WGDev;
|
||||
|
||||
my $wgd = WGDev->new( $webgui_root, $config_file );
|
||||
|
||||
my $webgui_session = $wgd->session;
|
||||
my $webgui_version = $wgd->version->module;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Performs common actions needed by WebGUI developers, such as recreating their
|
||||
site from defaults, checking version numbers, exporting packages, and more.
|
||||
|
||||
=func C<yaml_encode ( $structure )>
|
||||
|
||||
Loads a YAML module if needed and encodes a data structure with it.
|
||||
|
||||
=func C<yaml_decode ( $yaml_string )>
|
||||
|
||||
Loads a YAML module if needed and decodes a data structure with it.
|
||||
|
||||
=method C<new ( [ $root ], [ $config ] )>
|
||||
|
||||
Creates a new WGDev object. Optionally accepts a WebGUI root path and config
|
||||
file. These will be passed on to the C<root> and C<config_file> methods.
|
||||
|
||||
=method C<root ( [ $webgui_root ] )>
|
||||
|
||||
Sets or returns the WebGUI root path the object will be interacting with. If
|
||||
the path can't be recognized as a WebGUI root, an error will be thrown. The
|
||||
return value will always be an absolute path to the WebGUI root.
|
||||
|
||||
=method C<config_file ( [ $webgui_config ] )>
|
||||
|
||||
Sets or returns the site config file path. The given path can be relative to
|
||||
the current directory or to the etc directory in the WebGUI root. If the
|
||||
config file is found and the WebGUI root is not yet set, it will set the root
|
||||
based on the config file path. If the specified config file can't be found,
|
||||
an error will be thrown.
|
||||
|
||||
=method C<config_file_relative>
|
||||
|
||||
Returns the config file path relative to the WebGUI config directory. Useful
|
||||
for initializing WebGUI sessions, which require the config path to be relative
|
||||
to that directory.
|
||||
|
||||
=method C<lib>
|
||||
|
||||
In scalar context, returns the WebGUI library path based on the WebGUI root.
|
||||
In array context, it also includes the library paths specified in the
|
||||
F<preload.custom> file.
|
||||
|
||||
=method C<list_site_configs>
|
||||
|
||||
Returns a list of the available site configuration files in the
|
||||
C<etc> directory of the specified WebGUI root path. The returned
|
||||
paths will include the full file path.
|
||||
|
||||
=method C<config>
|
||||
|
||||
Returns a Config::JSON object based on the file set using C<config_file>.
|
||||
|
||||
=method C<session>
|
||||
|
||||
Returns a WebGUI session initialized using the WebGUI root and config file.
|
||||
|
||||
=method C<asset>
|
||||
|
||||
Returns a L<WGDev::Asset> object for simple asset operations.
|
||||
|
||||
=method C<db>
|
||||
|
||||
Returns a L<WGDev::Database> object for database interaction without starting
|
||||
a WebGUI session.
|
||||
|
||||
=method C<version>
|
||||
|
||||
Returns a L<WGDev::Version> object for checking the WebGUI version number in
|
||||
several different places.
|
||||
|
||||
=method C<close_config>
|
||||
|
||||
Closes the link to the WebGUI config file. Future calls to C<config> will
|
||||
load a new object based on the file.
|
||||
|
||||
=method C<close_session>
|
||||
|
||||
Closes the WebGUI session. If the session object has expired or is no longer
|
||||
valid, it will first be re-opened, then closed properly.
|
||||
|
||||
=method C<set_environment>
|
||||
|
||||
Sets the C<WEBGUI_ROOT>, C<WEBGUI_CONFIG>, and C<PERL5LIB> environment variables
|
||||
based on C<root>, C<config_file>, and C<lib>.
|
||||
|
||||
=method C<reset_environment>
|
||||
|
||||
Resets the C<WEBGUI_ROOT>, C<WEBGUI_CONFIG>, and C<PERL5LIB> based to what they
|
||||
were prior to set_environment being called.
|
||||
|
||||
=method C<wgd_config ( [ $config_param [, $value ] ] )>
|
||||
|
||||
Get or set WGDev config file parameters. Accepts two parameters, the config
|
||||
directive and optionally the value to set it to. The config directive is the
|
||||
path in a data structure specified either as an array reference of keys or a
|
||||
period separated string of keys.
|
||||
|
||||
=method C<my_config ( [ $config_param [, $value ] ] )>
|
||||
|
||||
Similar to wgd_config, but prefixes the specified path with keys based on the
|
||||
caller's package. For example, a package of C<WGDev::Command::Reset> becomes
|
||||
C<command.reset>.
|
||||
|
||||
=method C<read_wgd_config>
|
||||
|
||||
Reads and parses the WGDev config file into memory. Will be automatically
|
||||
called by C<wgd_config> as needed.
|
||||
|
||||
=method C<write_wgd_config>
|
||||
|
||||
Saves the current configuration back to the WGDev config file.
|
||||
|
||||
=cut
|
||||
|
||||
433
lib/WGDev/Asset.pm
Normal file
433
lib/WGDev/Asset.pm
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
package WGDev::Asset;
|
||||
# ABSTRACT: Asset utility functions
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use constant LINE_LENGTH => 78;
|
||||
|
||||
use WGDev;
|
||||
use WGDev::X;
|
||||
use Try::Tiny;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $session = shift;
|
||||
my $self = bless { session => $session, }, $class;
|
||||
require WebGUI::Asset;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub root {
|
||||
my $self = shift;
|
||||
return WebGUI::Asset->getRoot( $self->{session} );
|
||||
}
|
||||
|
||||
sub import_node {
|
||||
my $self = shift;
|
||||
return WebGUI::Asset->getImportNode( $self->{session} );
|
||||
}
|
||||
|
||||
sub default_asset { goto &home }
|
||||
|
||||
sub home {
|
||||
my $self = shift;
|
||||
return WebGUI::Asset->getDefault( $self->{session} );
|
||||
}
|
||||
|
||||
sub by_url {
|
||||
my $self = shift;
|
||||
my $asset = WebGUI::Asset->newByUrl( $self->{session}, @_ );
|
||||
if (! defined $asset) {
|
||||
WGDev::X::AssetNotFound->throw(asset => $_[0]);
|
||||
}
|
||||
return $asset;
|
||||
}
|
||||
|
||||
sub by_id {
|
||||
my $self = shift;
|
||||
my ($asset_id, $revision) = @_;
|
||||
my $asset;
|
||||
if (WebGUI::Asset->can('newById')) {
|
||||
$asset = WebGUI::Asset->newById( $self->{session}, $asset_id, $revision );
|
||||
}
|
||||
else {
|
||||
$asset = WebGUI::Asset->new( $self->{session}, $asset_id, undef, $revision );
|
||||
}
|
||||
if (! defined $asset) {
|
||||
WGDev::X::AssetNotFound->throw(asset => $_[0]);
|
||||
}
|
||||
return $asset;
|
||||
}
|
||||
|
||||
sub find {
|
||||
my ( $self, $asset_spec ) = @_;
|
||||
my $session = $self->{session};
|
||||
my $asset;
|
||||
my $e;
|
||||
if ( $session->id->valid($asset_spec) ) {
|
||||
try {
|
||||
$asset = $self->by_id($asset_spec);
|
||||
}
|
||||
catch {
|
||||
$e = $_;
|
||||
};
|
||||
}
|
||||
if ( !$asset ) {
|
||||
try {
|
||||
$asset = WebGUI::Asset->newByUrl( $session, $asset_spec );
|
||||
}
|
||||
catch {
|
||||
$e ||= $_;
|
||||
};
|
||||
}
|
||||
if ( $asset && ref $asset && $asset->isa('WebGUI::Asset') ) {
|
||||
return $asset;
|
||||
}
|
||||
if ($e) {
|
||||
WGDev::X->inflate($e);
|
||||
}
|
||||
WGDev::X::AssetNotFound->throw( asset => $asset_spec );
|
||||
}
|
||||
|
||||
my $package_re = qr{
|
||||
[[:upper:]]\w+
|
||||
(?: ::[[:upper:]]\w+ )*
|
||||
}msx;
|
||||
|
||||
sub validate_class {
|
||||
my $self = shift;
|
||||
my $in_class = my $class = shift;
|
||||
if (
|
||||
$class =~ s{\A
|
||||
# optionally starting with WebGUI::Asset:: or ::
|
||||
(?:(?:WebGUI::Asset)?::)?
|
||||
( $package_re )
|
||||
\z
|
||||
}{WebGUI::Asset::$1}msx
|
||||
)
|
||||
{
|
||||
my $short_class = $1;
|
||||
return wantarray ? ( $class, $short_class ) : $class;
|
||||
}
|
||||
WGDev::X::BadAssetClass->throw( class => $in_class );
|
||||
}
|
||||
|
||||
sub _gen_serialize_header {
|
||||
my $self = shift;
|
||||
my $header_text = shift;
|
||||
my $header = "==== $header_text ";
|
||||
$header .= ( q{=} x ( LINE_LENGTH - length $header ) ) . "\n";
|
||||
return $header;
|
||||
}
|
||||
|
||||
sub serialize {
|
||||
my ( $self, $asset, $properties ) = @_;
|
||||
my $class = ref $asset || $asset;
|
||||
WGDev::X::BadParameter->throw('No asset or class specified')
|
||||
if not defined $class;
|
||||
if ( !ref $asset ) {
|
||||
( my $module = $class . '.pm' ) =~ s{::}{/}msxg;
|
||||
require $module;
|
||||
}
|
||||
my $short_class = $class;
|
||||
$short_class =~ s/^WebGUI::Asset:://xms;
|
||||
|
||||
my ( $asset_properties, $meta, $text )
|
||||
= $self->_asset_properties( $asset, $properties );
|
||||
|
||||
my $basic_yaml = WGDev::yaml_encode( {
|
||||
'Asset ID' => $asset_properties->{assetId},
|
||||
'Title' => $asset_properties->{title},
|
||||
'Menu Title' => $asset_properties->{menuTitle},
|
||||
'URL' => $asset_properties->{url},
|
||||
'Parent' => (
|
||||
ref $asset
|
||||
? $asset->getParent->get('url')
|
||||
: $self->import_node->get('url')
|
||||
),
|
||||
} );
|
||||
|
||||
# filter out unneeded YAML syntax
|
||||
$basic_yaml =~ s/\A---(?:\Q {}\E)?\s*//msx;
|
||||
$basic_yaml =~ s/\r?\n/\n/msxg;
|
||||
$basic_yaml =~ s/[ ]+$//msxg;
|
||||
|
||||
# line up colons
|
||||
$basic_yaml =~ s/^([^:]+):/sprintf("%-12s:", $1)/msxeg;
|
||||
my $output = $self->_gen_serialize_header($short_class) . $basic_yaml;
|
||||
|
||||
for my $field ( sort keys %{$text} ) {
|
||||
my $value = $text->{$field};
|
||||
if ( !defined $value ) {
|
||||
$value = q{~};
|
||||
}
|
||||
$value =~ s/\r\n?/\n/msxg;
|
||||
$output .= $self->_gen_serialize_header($field) . $value . "\n";
|
||||
}
|
||||
|
||||
my $meta_yaml = WGDev::yaml_encode($meta);
|
||||
$meta_yaml =~ s/\A---(?:\Q {}\E)?\s*//msx;
|
||||
$meta_yaml =~ s/\r?\n/\n/msxg;
|
||||
$meta_yaml =~ s/[ ]+$//msxg;
|
||||
$output .= $self->_gen_serialize_header('Properties') . $meta_yaml . "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
sub _asset_properties {
|
||||
my $self = shift;
|
||||
my $class = shift;
|
||||
my $properties = shift;
|
||||
my $asset;
|
||||
if ( ref $class ) {
|
||||
$asset = $class;
|
||||
$class = ref $asset;
|
||||
}
|
||||
@_ = ($self, $class, $asset, $properties);
|
||||
if ($class->can('definition')) {
|
||||
goto &_asset_properties_definition;
|
||||
}
|
||||
goto &_asset_properties_meta;
|
||||
}
|
||||
|
||||
sub _asset_properties_definition {
|
||||
my $self = shift;
|
||||
my ($class, $asset, $properties) = @_;
|
||||
|
||||
my $definition = $class->definition( $self->{session} );
|
||||
my %text;
|
||||
my %meta;
|
||||
|
||||
my $asset_properties
|
||||
= { $asset ? %{ $asset->get } : (), $properties ? %{$properties} : (),
|
||||
};
|
||||
for my $def ( @{$definition} ) {
|
||||
while ( my ( $property, $property_def )
|
||||
= each %{ $def->{properties} } )
|
||||
{
|
||||
if ( !defined $asset_properties->{$property}
|
||||
&& defined $property_def->{defaultValue} )
|
||||
{
|
||||
$asset_properties->{$property}
|
||||
= $self->_get_property_default($property_def);
|
||||
}
|
||||
|
||||
$self->_filter_property(
|
||||
$property,
|
||||
$asset_properties->{$property},
|
||||
ucfirst ( $property_def->{fieldType} || q{} ),
|
||||
$property_def->{tab},
|
||||
\%text,
|
||||
\%meta,
|
||||
);
|
||||
}
|
||||
}
|
||||
return ( $asset_properties, \%meta, \%text );
|
||||
}
|
||||
|
||||
sub _asset_properties_meta {
|
||||
my $self = shift;
|
||||
my ($class, $asset, $properties) = @_;
|
||||
|
||||
my %text;
|
||||
my %meta;
|
||||
|
||||
my $asset_properties
|
||||
= { $asset ? %{ $asset->get } : (), $properties ? %{$properties} : (),
|
||||
};
|
||||
|
||||
for my $property ( $class->meta->get_all_property_list ) {
|
||||
my $attr = $class->meta->find_attribute_by_name($property);
|
||||
if ( !defined $asset_properties->{$property} ) {
|
||||
$asset_properties->{$property} = $attr->default;
|
||||
}
|
||||
my $field_type = ucfirst $attr->fieldType;
|
||||
$self->_filter_property(
|
||||
$property,
|
||||
$asset_properties->{$property},
|
||||
ucfirst $attr->fieldType,
|
||||
$attr->form->{tab},
|
||||
\%text,
|
||||
\%meta,
|
||||
);
|
||||
}
|
||||
return ( $asset_properties, \%meta, \%text );
|
||||
}
|
||||
|
||||
sub _filter_property { ##no critic (ProhibitManyArgs)
|
||||
my $self = shift;
|
||||
my ( $property, $value, $field_type, $tab, $text, $meta ) = @_;
|
||||
if ( $property eq 'title'
|
||||
|| $property eq 'menuTitle'
|
||||
|| $property eq 'url' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
elsif ($field_type eq 'HTMLArea'
|
||||
|| $field_type eq 'Textarea'
|
||||
|| $field_type eq 'Codearea' )
|
||||
{
|
||||
$text->{$property} = $value;
|
||||
}
|
||||
elsif ( $field_type eq 'Hidden' ) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
$meta->{ $tab || 'properties' }{$property} = $value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
my %basic_translation = (
|
||||
'Title' => 'title',
|
||||
'Asset ID' => 'assetId',
|
||||
'Menu Title' => 'menuTitle',
|
||||
'URL' => 'url',
|
||||
'Parent' => 'parent',
|
||||
);
|
||||
|
||||
sub deserialize {
|
||||
my $self = shift;
|
||||
my $asset_data = shift;
|
||||
my @text_sections = split m{
|
||||
^====[ ] # line start, plus equal signs
|
||||
((?:\w|:)+) # word chars or colons (Perl namespace)
|
||||
[ ]=+ # space + equals
|
||||
(?:\n|\z) # end of line or end of string
|
||||
}msx, $asset_data;
|
||||
|
||||
# due to split, there is an extra empty entry at the beginning
|
||||
shift @text_sections;
|
||||
my $class = $self->validate_class( shift @text_sections );
|
||||
my $basic_data = shift @text_sections;
|
||||
my %sections;
|
||||
my %properties;
|
||||
|
||||
while ( my $section = shift @text_sections ) {
|
||||
my $section_data = shift @text_sections;
|
||||
chomp $section_data;
|
||||
if ( $section_data eq q{~} ) {
|
||||
$section_data = undef;
|
||||
}
|
||||
$sections{$section} = $section_data;
|
||||
}
|
||||
if ( my $prop_data = delete $sections{Properties} ) {
|
||||
my $tabs = WGDev::yaml_decode($prop_data);
|
||||
%properties = map { %{$_} } values %{$tabs};
|
||||
}
|
||||
|
||||
@properties{ keys %sections } = values %sections;
|
||||
|
||||
my $basic_untrans = WGDev::yaml_decode($basic_data);
|
||||
for my $property ( keys %{$basic_untrans} ) {
|
||||
if ( $basic_translation{$property} ) {
|
||||
$properties{ $basic_translation{$property} }
|
||||
= $basic_untrans->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
$properties{className} = $class;
|
||||
|
||||
return \%properties;
|
||||
}
|
||||
|
||||
sub _get_property_default {
|
||||
my $self = shift;
|
||||
my $property_def = shift;
|
||||
my $default = $property_def->{defaultValue};
|
||||
my $form_class = $property_def->{fieldType};
|
||||
if ($form_class) {
|
||||
$form_class = "WebGUI::Form::\u$form_class";
|
||||
my $form_module = join q{/}, ( split /::/msx, $form_class . '.pm' );
|
||||
if ( eval { require $form_module; 1 } ) {
|
||||
my $form = $form_class->new( $self->{session},
|
||||
{ defaultValue => $default } );
|
||||
$default = $form->getDefaultValue;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
sub export_extension {
|
||||
my $self = shift;
|
||||
my $asset = shift;
|
||||
my $class = ref $asset || $asset;
|
||||
return
|
||||
if !defined $class;
|
||||
my $short_class = $class;
|
||||
$short_class =~ s/.*:://msx;
|
||||
my $extension = lc $short_class;
|
||||
$extension =~ s/(?<!^)[aeiouy]//msxg;
|
||||
$extension =~ tr/a-z//s;
|
||||
return $extension;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $root_node = $wgd->asset->root;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Performs common actions on assets.
|
||||
|
||||
=method C<new ( $session )>
|
||||
|
||||
Creates a new object. Requires a single parameter of the WebGUI session to use.
|
||||
|
||||
=method C<by_id ( $asset_id )>
|
||||
|
||||
Finds an asset based on an asset ID.
|
||||
|
||||
=method C<by_url ( $asset_url )>
|
||||
|
||||
Finds an asset based on a URL.
|
||||
|
||||
=method C<find ( $asset_id_or_url )>
|
||||
|
||||
Finds an asset based on either an asset ID or a URL based on the format of
|
||||
the input.
|
||||
|
||||
=method C<home>
|
||||
|
||||
An alias for the C<default_asset> method.
|
||||
|
||||
=method C<default_asset>
|
||||
|
||||
Returns the default WebGUI asset, as will be shown for the URL of C</>.
|
||||
|
||||
=method C<root>
|
||||
|
||||
Returns the root WebGUI asset.
|
||||
|
||||
=method C<import_node>
|
||||
|
||||
Returns the Import Node asset.
|
||||
|
||||
=method C<serialize ( $asset_or_class )>
|
||||
|
||||
Serializes an asset into a string that can be written out to a file.
|
||||
|
||||
=method C<deserialize ( $asset_data_text )>
|
||||
|
||||
Deserializes a string as generated by C<serialize> into either a hash
|
||||
reference of properties that can be used to create or update an asset.
|
||||
|
||||
=method C<validate_class ( [ $class_name ] )>
|
||||
|
||||
Accepts a class name of an asset in either full (C<WebGUI::Asset::Template>) or
|
||||
short (C<Template>) form. In scalar context, returns the full class name. In
|
||||
array context, returns an array of the full and the short class name. Will
|
||||
throw an error if the provided class is not valid.
|
||||
|
||||
=method C<export_extension ( $asset_or_class )>
|
||||
|
||||
Returns a file extension to use for exporting the given asset or
|
||||
class. The extension will be the last segment of the class name,
|
||||
lower cased, with repeated letters and vowels (except for an initial
|
||||
vowel) removed.
|
||||
|
||||
=cut
|
||||
|
||||
493
lib/WGDev/Command.pm
Normal file
493
lib/WGDev/Command.pm
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
package WGDev::Command;
|
||||
# ABSTRACT: Run WGDev commands
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use Getopt::Long ();
|
||||
use File::Spec ();
|
||||
use Cwd ();
|
||||
use WGDev::X ();
|
||||
|
||||
sub run {
|
||||
my $class = shift;
|
||||
local @ARGV = @_;
|
||||
Getopt::Long::Configure(
|
||||
qw(default gnu_getopt pass_through no_auto_abbrev));
|
||||
Getopt::Long::GetOptions(
|
||||
'h|?|help' => \( my $opt_help ),
|
||||
'V|ver|version' => \( my $opt_version ),
|
||||
|
||||
'F|config-file=s' => \( my $opt_config ),
|
||||
'R|webgui-root=s' => \( my $opt_root ),
|
||||
'S|sitename=s' => \( my $opt_sitename ),
|
||||
) || WGDev::X::CommandLine->throw( usage => $class->usage(0) );
|
||||
my @params = @ARGV;
|
||||
|
||||
my $command_name = shift @params;
|
||||
|
||||
my $command_module = eval { $class->get_command_module($command_name) };
|
||||
if ( $command_name && !$command_module ) {
|
||||
my $command_exec = $class->_find_cmd_exec($command_name);
|
||||
if ($command_exec) {
|
||||
require WGDev::Command::Run;
|
||||
$command_module = 'WGDev::Command::Run';
|
||||
unshift @params, $command_exec, $opt_help ? '--help' : (),
|
||||
$opt_version ? '--version' : ();
|
||||
undef $opt_help;
|
||||
undef $opt_version;
|
||||
}
|
||||
else {
|
||||
WGDev::X::CommandLine::BadCommand->throw(
|
||||
command_name => $command_name,
|
||||
usage => $class->usage(0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($opt_version) {
|
||||
$class->report_version( $command_name, $command_module );
|
||||
}
|
||||
elsif ($opt_help) {
|
||||
$class->report_help( $command_name, $command_module );
|
||||
}
|
||||
elsif ( !$command_name ) {
|
||||
print $class->usage(0);
|
||||
require WGDev::Command::Commands;
|
||||
return WGDev::Command::Commands->help;
|
||||
}
|
||||
else {
|
||||
require WGDev;
|
||||
my $wgd = WGDev->new;
|
||||
$class->guess_webgui_paths(
|
||||
wgd => $wgd,
|
||||
root => $opt_root,
|
||||
config_file => $opt_config,
|
||||
sitename => $opt_sitename,
|
||||
);
|
||||
my $command = $command_module->new($wgd);
|
||||
return $command->run(@params);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_params_or_defaults {
|
||||
my $class = shift;
|
||||
my %params = @_;
|
||||
my $wgd = $params{wgd};
|
||||
|
||||
if ( $params{config_file} && $params{sitename} ) {
|
||||
WGDev::X::BadParameter->throw(
|
||||
q{Can't specify both a config file and a sitename});
|
||||
}
|
||||
|
||||
##no tidy
|
||||
my $webgui_root
|
||||
= $params{root}
|
||||
|| $ENV{WEBGUI_ROOT}
|
||||
|| $wgd->my_config('webgui_root');
|
||||
##tidy
|
||||
my $webgui_config;
|
||||
my $webgui_sitename;
|
||||
|
||||
# avoid buggy critic module
|
||||
##no critic (ProhibitCallsToUndeclaredSubs)
|
||||
FIND_CONFIG: {
|
||||
( $webgui_config = $params{config_file} )
|
||||
&& last FIND_CONFIG;
|
||||
( $webgui_sitename = $params{sitename} )
|
||||
&& last FIND_CONFIG;
|
||||
( $webgui_config = $ENV{WEBGUI_CONFIG} )
|
||||
&& last FIND_CONFIG;
|
||||
( $webgui_sitename = $ENV{WEBGUI_SITENAME} )
|
||||
&& last FIND_CONFIG;
|
||||
( $webgui_config = $wgd->my_config('webgui_config') )
|
||||
&& last FIND_CONFIG;
|
||||
( $webgui_sitename = $wgd->my_config('webgui_sitename') )
|
||||
&& last FIND_CONFIG;
|
||||
}
|
||||
|
||||
$params{root} = $webgui_root;
|
||||
$params{config_file} = $webgui_config;
|
||||
$params{sitename} = $webgui_sitename;
|
||||
return %params;
|
||||
}
|
||||
|
||||
sub guess_webgui_paths {
|
||||
my $class = shift;
|
||||
my %params = $class->get_params_or_defaults(@_);
|
||||
my $wgd = $params{wgd};
|
||||
|
||||
my $webgui_root = $params{root};
|
||||
my $webgui_config = $params{config_file};
|
||||
my $webgui_sitename = $params{sitename};
|
||||
|
||||
my $e;
|
||||
|
||||
# first we need to find the webgui root
|
||||
if ($webgui_root) {
|
||||
$wgd->root($webgui_root);
|
||||
}
|
||||
|
||||
# if that didn't set the root and we have a config, try to set it.
|
||||
# if it is absolute, it will give us a root as well
|
||||
if ( !$wgd->root && $webgui_config ) {
|
||||
if ( eval { $class->set_config_by_input( $wgd, $webgui_config ); } ) {
|
||||
return $wgd
|
||||
if $wgd->root;
|
||||
}
|
||||
else {
|
||||
$e = WGDev::X->caught || WGDev::X->new($@);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$wgd->root ) {
|
||||
if ( !eval { $class->set_root_relative($wgd); 1 } ) {
|
||||
|
||||
# throw error from previous try to set the config
|
||||
$e->rethrow if $e;
|
||||
return $wgd;
|
||||
}
|
||||
}
|
||||
|
||||
if ($webgui_sitename) {
|
||||
$class->set_config_by_sitename( $wgd, $webgui_sitename );
|
||||
}
|
||||
elsif ($webgui_config) {
|
||||
$class->set_config_by_input( $wgd, $webgui_config );
|
||||
}
|
||||
return $wgd;
|
||||
}
|
||||
|
||||
sub set_root_relative {
|
||||
my ( $class, $wgd ) = @_;
|
||||
my $dir = Cwd::getcwd();
|
||||
while (1) {
|
||||
if ( -e File::Spec->catfile( $dir, 'lib', 'WebGUI.pm' ) ) {
|
||||
$wgd->root($dir);
|
||||
last;
|
||||
}
|
||||
my $parent
|
||||
= Cwd::realpath( File::Spec->catdir( $dir, File::Spec->updir ) );
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if $dir eq $parent;
|
||||
$dir = $parent;
|
||||
}
|
||||
return $wgd;
|
||||
}
|
||||
|
||||
sub set_config_by_input {
|
||||
my ( $class, $wgd, $webgui_config ) = @_;
|
||||
|
||||
# first, try the specified config file
|
||||
if ( eval { $wgd->config_file($webgui_config) } ) {
|
||||
return $wgd;
|
||||
}
|
||||
my $e = WGDev::X->caught;
|
||||
|
||||
# if that didn't work, try it with .conf appended
|
||||
if ( $webgui_config !~ /\Q.conf\E$/msx ) {
|
||||
if ( eval { $wgd->config_file( $webgui_config . '.conf' ) } ) {
|
||||
return $wgd;
|
||||
}
|
||||
}
|
||||
|
||||
# if neither normal or alternate config files worked, die
|
||||
$e->rethrow;
|
||||
}
|
||||
|
||||
sub set_config_by_sitename {
|
||||
my ( $class, $wgd, $sitename ) = @_;
|
||||
require Config::JSON;
|
||||
my @configs = $wgd->list_site_configs;
|
||||
my $found_config;
|
||||
my $sitename_regex = qr/ (?:^|[.]) \Q$sitename\E $ /msx;
|
||||
for my $config_file (@configs) {
|
||||
my $config = eval { Config::JSON->new($config_file) };
|
||||
next
|
||||
if !$config;
|
||||
for my $config_sitename ( @{ $config->get('sitename') } ) {
|
||||
if ( $config_sitename =~ m/$sitename_regex/msx ) {
|
||||
if ($found_config) {
|
||||
WGDev::X->throw("Ambigious site name: $sitename");
|
||||
}
|
||||
$found_config = $config_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($found_config) {
|
||||
$wgd->config_file($found_config);
|
||||
return $wgd;
|
||||
}
|
||||
WGDev::X->throw("Unable to find config file for site: $sitename");
|
||||
}
|
||||
|
||||
sub report_version {
|
||||
my ( $class, $name, $module ) = @_;
|
||||
if ( ref $class ) {
|
||||
$class = ref $class;
|
||||
}
|
||||
print "$class version " . $class->VERSION;
|
||||
if ($module) {
|
||||
print " - $module version " . $module->VERSION;
|
||||
}
|
||||
print "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub report_help {
|
||||
my ( $class, $name, $module ) = @_;
|
||||
if ( ref $class ) {
|
||||
$class = ref $class;
|
||||
}
|
||||
if ($module) {
|
||||
if ( $module->can('usage') ) {
|
||||
print $module->usage(1);
|
||||
}
|
||||
else {
|
||||
warn "No documentation for $name command.\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
print $class->usage(1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_command_module {
|
||||
my ( $class, $command_name ) = @_;
|
||||
if ( $command_name && $command_name =~ /^\w+(?:-\w+)*$/mxs ) {
|
||||
my $module = $class->command_to_module($command_name);
|
||||
( my $module_file = "$module.pm" ) =~ s{::}{/}mxsg;
|
||||
if ( eval { require $module_file; 1 }
|
||||
&& $module->can('run')
|
||||
&& $module->can('is_runnable')
|
||||
&& $module->is_runnable )
|
||||
{
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
WGDev::X::BadCommand->throw( 'command_name' => $command_name );
|
||||
}
|
||||
|
||||
sub command_to_module {
|
||||
my ( $class, $command ) = @_;
|
||||
my $module = join q{::}, __PACKAGE__, map {ucfirst} split /-/msx,
|
||||
$command;
|
||||
return $module;
|
||||
}
|
||||
|
||||
sub _find_cmd_exec {
|
||||
my ( $class, $command_name, $root, $config ) = @_;
|
||||
if ($command_name) {
|
||||
for my $path ( File::Spec->path ) {
|
||||
my $execpath = File::Spec->catfile( $path, "wgd-$command_name" );
|
||||
if ( -x $execpath ) {
|
||||
return $execpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub usage {
|
||||
my $class = shift;
|
||||
require WGDev::Help;
|
||||
return WGDev::Help::package_usage( $class, @_ );
|
||||
}
|
||||
|
||||
sub command_list {
|
||||
my $class = shift;
|
||||
my %commands;
|
||||
( my $fn_prefix = $class ) =~ s{::}{/}msxg;
|
||||
|
||||
require File::Find;
|
||||
my %lib_check;
|
||||
for my $inc_path (@INC) {
|
||||
##no critic (ProhibitParensWithBuiltins)
|
||||
my $command_root
|
||||
= File::Spec->catdir( $inc_path, split( /::/msx, $class ) );
|
||||
next
|
||||
if !-d $command_root;
|
||||
my $find_callback = sub {
|
||||
return
|
||||
if !/\Q.pm\E$/msx;
|
||||
|
||||
no warnings 'once';
|
||||
my $lib_path
|
||||
= File::Spec->abs2rel( $File::Find::name, $inc_path );
|
||||
$lib_check{$lib_path} = 1;
|
||||
};
|
||||
File::Find::find( { no_chdir => 1, wanted => $find_callback },
|
||||
$command_root );
|
||||
}
|
||||
no warnings 'once';
|
||||
for my $module ( grep {m{^\Q$fn_prefix\E/}msx} ( keys %INC, @App::WGDev::PACKED ) ) {
|
||||
$lib_check{$module} = 1;
|
||||
}
|
||||
for my $module ( keys %lib_check ) {
|
||||
my $package = $module;
|
||||
$package =~ s/\Q.pm\E$//msx;
|
||||
$package = join q{::}, File::Spec->splitdir($package);
|
||||
##no critic (RequireCheckingReturnValueOfEval)
|
||||
eval {
|
||||
require $module;
|
||||
if ( $package->can('run')
|
||||
&& $package->can('is_runnable')
|
||||
&& $package->is_runnable
|
||||
) {
|
||||
( my $command = $package ) =~ s/^\Q$class\E:://msx;
|
||||
$command = join q{-}, map {lcfirst} split m{::}msx, $command;
|
||||
$commands{$command} = 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for my $command ( map { glob File::Spec->catfile( $_, 'wgd-*' ) }
|
||||
File::Spec->path )
|
||||
{
|
||||
next
|
||||
if !-x $command;
|
||||
my $file = ( File::Spec->splitpath($command) )[2];
|
||||
$file =~ s/^wgd-//msx;
|
||||
$commands{$file} = 1;
|
||||
}
|
||||
my @commands = sort keys %commands;
|
||||
return @commands;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd [arguments] <subcommand> [subcommand arguments]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Runs sub-commands from the C<WGDev::Command> namespace, or standalone
|
||||
scripts starting with F<wgd->
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-h> C<-?> C<--help>
|
||||
|
||||
Display usage summary for any command.
|
||||
|
||||
=item C<-V> C<--version>
|
||||
|
||||
Display version information
|
||||
|
||||
=item C<-F> C<--config-file>
|
||||
|
||||
Specify WebGUI config file to use. Can be absolute, relative to
|
||||
the current directory, or relative to WebGUI's config directory.
|
||||
If not specified, it will try to use the C<WEBGUI_CONFIG> environment
|
||||
variable or the C<command.webgui_config> option from the configuration
|
||||
file.
|
||||
|
||||
=item C<-S> C<--sitename>
|
||||
|
||||
Specify the name of a WebGUI site to operate on. This will check
|
||||
all of the config files in WebGUI's config directory for a single
|
||||
site using the specified C<sitename>. If not specified, the
|
||||
C<WEBGUI_SITENAME> environment variable and C<command.webgui_sitename>
|
||||
option will be used if available.
|
||||
|
||||
=item C<-R> C<--webgui-root>
|
||||
|
||||
Specify WebGUI's root directory. Can be absolute or relative. If
|
||||
not specified, first the C<WEBGUI_ROOT> environment variable and
|
||||
C<command.webgui_root> option from the configuration file will be
|
||||
checked, then will search upward from the current path for a WebGUI
|
||||
installation.
|
||||
|
||||
=item C<< <subcommand> >>
|
||||
|
||||
The sub-command to run or get help for.
|
||||
|
||||
=back
|
||||
|
||||
=method C<run ( @arguments )>
|
||||
|
||||
Runs C<wgd>, processing the arguments specified and running a sub-command if possible.
|
||||
|
||||
=method C<usage ( [$verbosity] )>
|
||||
|
||||
Returns usage information for C<wgd>. The verbosity level is passed on
|
||||
to L<WGDev::Help::package_usage|WGDev::Help/package_usage>.
|
||||
|
||||
=method C<command_list>
|
||||
|
||||
Searches for available sub-commands and returns them as an array.
|
||||
This list includes available Perl modules that pass the
|
||||
L</get_command_module> check and executable files beginning with
|
||||
F<wgd->.
|
||||
|
||||
=method C<command_to_module ( $command )>
|
||||
|
||||
Converts a command into the module that would implement it. Returns
|
||||
that module name.
|
||||
|
||||
=method C<get_command_module ( $command )>
|
||||
|
||||
Converts the command to a module, then attempts to load that module.
|
||||
If the module loads successfully, implements the C<run> and
|
||||
C<is_runnable> methods, and C<is_runnable> returns true, returns
|
||||
the module. If not, returns C<undef>.
|
||||
|
||||
=method C<< get_params_or_defaults ( wgd => $wgd, %params ) >>
|
||||
|
||||
Finds the specified WebGUI root, config file, and C<sitename>. Uses
|
||||
environment variables and configuration file if not specified
|
||||
directly. Returns C<%params> with C<root>, C<config_file>, and
|
||||
C<sitename> options updated.
|
||||
|
||||
=method C<< guess_webgui_paths ( wgd => $wgd, [root => $webgui_root], [config_file => $webgui_config] ) >>
|
||||
|
||||
Attempts to detect the paths to use for the WebGUI root and config
|
||||
file. Initializes the specified C<$wgd> object. If specified, attempts
|
||||
to use the specified paths first. If not specified, first checks
|
||||
the environment variables C<WEBGUI_ROOT> and C<WEBGUI_CONFIG>.
|
||||
Next, attempts to search upward from the current path to find the
|
||||
WebGUI root. If a WebGUI root has been found but not a config file,
|
||||
checks for available config files. If only one is available, it
|
||||
is used as the config file.
|
||||
|
||||
=method C<set_root_relative ( $wgd )>
|
||||
|
||||
Attempts to set the root WebGUI directory based on the current
|
||||
directory. Searches upward from the current path for a valid WebGUI
|
||||
root directory, and sets it in the C<$wgd> object if found. If no
|
||||
valid root is found, throws an error.
|
||||
|
||||
=method C<set_config_by_input ( $wgd, $config )>
|
||||
|
||||
Sets the config file in the C<$wgd> object based on the specified
|
||||
WebGUI config file. If the specified file isn't found, but a file
|
||||
with the same name with the C<.conf> extension added to it does
|
||||
exist, that file will be used. If a config file can't be found,
|
||||
throws an error.
|
||||
|
||||
=method C<set_config_by_sitename ( $wgd, $sitename )>
|
||||
|
||||
Sets the config file in the C<$wgd> object based on the specified
|
||||
site name. All of the available config files will be checked and
|
||||
if one of the sites lists the site name, its config file will be
|
||||
used.
|
||||
|
||||
=method C<report_help ( [$command, $module] )>
|
||||
|
||||
Shows help information for C<wgd> or a sub-command. If a command
|
||||
and module is specified, attempts to call C<usage> on the module
|
||||
or displays an error. Otherwise, displays help information for
|
||||
C<wgd>.
|
||||
|
||||
=method C<report_version ( [$command, $module] )>
|
||||
|
||||
Reports version information about C<wgd>. If specified, also
|
||||
includes version information about a sub-command.
|
||||
|
||||
=cut
|
||||
|
||||
270
lib/WGDev/Command/Base.pm
Normal file
270
lib/WGDev/Command/Base.pm
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
package WGDev::Command::Base;
|
||||
# ABSTRACT: Super-class for implementing WGDev commands
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub is_runnable {
|
||||
my $class = shift;
|
||||
return $class->can('process');
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ( $class, $wgd ) = @_;
|
||||
my $self = bless {
|
||||
wgd => $wgd,
|
||||
options => {},
|
||||
arguments => [],
|
||||
}, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub wgd { return $_[0]->{wgd} }
|
||||
|
||||
sub parse_params {
|
||||
my $self = shift;
|
||||
local @ARGV = @_;
|
||||
|
||||
require Getopt::Long;
|
||||
Getopt::Long::Configure( 'default', $self->config_parse_options );
|
||||
|
||||
my %getopt_params = (
|
||||
'<>' => sub {
|
||||
$self->argument( map {"$_"} @_ );
|
||||
},
|
||||
);
|
||||
|
||||
for my $option ( $self->config_options ) {
|
||||
|
||||
# for complex options, name is first word segment
|
||||
( my $option_name ) = ( $option =~ /([\w-]+)/msx );
|
||||
my $method = 'option_' . $option_name;
|
||||
$method =~ tr/-/_/;
|
||||
if ( $self->can($method) ) {
|
||||
$getopt_params{$option} = sub {
|
||||
$self->$method( @_[ 1 .. $#_ ] );
|
||||
};
|
||||
}
|
||||
else {
|
||||
$getopt_params{$option} = \( $self->{options}{$option_name} );
|
||||
}
|
||||
}
|
||||
my $result = Getopt::Long::GetOptions(%getopt_params);
|
||||
push @{ $self->{arguments} }, @ARGV;
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub parse_params_string {
|
||||
my $self = shift;
|
||||
my $param_string = shift;
|
||||
require Text::ParseWords;
|
||||
return $self->parse_params( Text::ParseWords::shellwords($param_string) );
|
||||
}
|
||||
|
||||
sub config_parse_options { return qw(gnu_getopt) }
|
||||
sub config_options { }
|
||||
|
||||
sub option {
|
||||
my $self = shift;
|
||||
my $option = shift || return;
|
||||
if (@_) {
|
||||
return $self->{options}{$option} = shift;
|
||||
}
|
||||
return $self->{options}{$option};
|
||||
}
|
||||
|
||||
sub set_option_default {
|
||||
my $self = shift;
|
||||
my $option = shift || return;
|
||||
if ( !defined $self->option($option) ) {
|
||||
return $self->option( $option, @_ );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub argument {
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
push @{ $self->{arguments} }, @_;
|
||||
return wantarray ? @_ : $_[-1];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub arguments {
|
||||
my $self = shift;
|
||||
if ( @_ && ref $_[0] eq 'ARRAY' ) {
|
||||
my $arguments = shift;
|
||||
@{ $self->{arguments} } = @{$arguments};
|
||||
}
|
||||
return @{ $self->{arguments} };
|
||||
}
|
||||
|
||||
sub run {
|
||||
my $self = shift;
|
||||
WGDev::X::NoWebGUIRoot->throw
|
||||
if $self->needs_root && !$self->wgd->root;
|
||||
WGDev::X::NoWebGUIConfig->throw
|
||||
if $self->needs_config && !$self->wgd->config_file;
|
||||
my @params = ( @_ == 1 && ref $_[0] eq 'ARRAY' ) ? @{ +shift } : @_;
|
||||
local $| = 1;
|
||||
if ( !$self->parse_params(@params) ) {
|
||||
my $usage = $self->usage(0);
|
||||
WGDev::X::CommandLine::BadParams->throw( usage => $usage );
|
||||
}
|
||||
return $self->process;
|
||||
}
|
||||
|
||||
sub usage {
|
||||
my $class = shift;
|
||||
my $verbosity = shift;
|
||||
if ( ref $class ) {
|
||||
$class = ref $class;
|
||||
}
|
||||
require WGDev::Help;
|
||||
my $usage = WGDev::Help::package_usage( $class, $verbosity );
|
||||
return $usage;
|
||||
}
|
||||
|
||||
sub help {
|
||||
my $class = shift;
|
||||
if ( ref $class ) {
|
||||
$class = ref $class;
|
||||
}
|
||||
require WGDev::Help;
|
||||
WGDev::Help::package_perldoc( $class,
|
||||
'!AUTHOR|LICENSE|METHODS|SUBROUTINES' );
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub needs_root {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub needs_config {
|
||||
my $class = shift;
|
||||
return $class->needs_root;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package WGDev::Command::Mine;
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
print "Running my command\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A super-class useful for implementing L<WGDev> command modules. Includes
|
||||
simple methods to override for parameter parsing and provides help text via
|
||||
Pod::Usage.
|
||||
|
||||
While using WGDev::Command::Base is not required to write a command module,
|
||||
it is the recommended way to do so.
|
||||
|
||||
=method C<is_runnable>
|
||||
|
||||
This is a class method that must be implemented and return true for all
|
||||
command modules. This method will return true for any subclass that
|
||||
implements the C<process> method.
|
||||
|
||||
=method C<new ( $wgd )>
|
||||
|
||||
Instantiate a new command object. Requires a L<WGDev> object as the first
|
||||
parameter.
|
||||
|
||||
=method C<wgd>
|
||||
|
||||
Returns the L<WGDev> object used to instantiate the object.
|
||||
|
||||
=method C<config_parse_options>
|
||||
|
||||
Returns an array of parameters used to configure command line parsing. These
|
||||
options are passed directly to L<Getopt::Long>. See
|
||||
L<Getopt::Long/Configuring_Getopt::Long> for details on the available options.
|
||||
By default, returns C<gnu_getopt> and can be overridden to return others.
|
||||
|
||||
=method C<config_options>
|
||||
|
||||
Returns an array of command line options to be parsed. Should be overridden
|
||||
to set which options will be parsed. Should be specified in the syntax
|
||||
accepted by L<Getopt::Long>. Each option will be saved as the the first
|
||||
group of word characters in the option definition. Alternately, if a method
|
||||
with the name C<< option_<name> >> exists, it will be called to set the
|
||||
option instead.
|
||||
|
||||
=method C<option ( $option [, $value] )>
|
||||
|
||||
Sets or returns a command line option. Accepts the option name as the first
|
||||
parameter. If specified, the option will be set the the value of the second
|
||||
parameter.
|
||||
|
||||
=method C<argument ( $argument )>
|
||||
|
||||
Adds an argument to the argument list. Any parameters specified will be added
|
||||
to the argument list. Can be overridden to provide alternate behavior.
|
||||
|
||||
=method C<arguments ( [ \@arguments ] )>
|
||||
|
||||
Sets or returns the arguments list. If specified, the first parameter
|
||||
must be an array reference whose values will be set as the arguments list.
|
||||
|
||||
=method C<parse_params ( @parameters )>
|
||||
|
||||
Sets options based on an array of command line parameters.
|
||||
|
||||
=method C<parse_params_string ( $parameters )>
|
||||
|
||||
Sets options based on a string of command line parameters. The string will be
|
||||
processed with L<Text::ParseWords> C<shellwords> sub then passed on to
|
||||
C<parse_params>.
|
||||
|
||||
=method C<set_option_default ( $option, $value )>
|
||||
|
||||
Sets an option only if it is not currently defined. First parameter is the
|
||||
option to set, second parameter is the value to set it to.
|
||||
|
||||
=method C<needs_root>
|
||||
|
||||
Should be overridden in subclasses to set whether a command needs a WebGUI root directory to run. Returns true if not overridden.
|
||||
|
||||
=method C<needs_config>
|
||||
|
||||
Should be overridden in subclasses to set whether a command needs a WebGUI config file directory to run. Returns the same value as L</needs_root> if not overridden.
|
||||
|
||||
=method C<usage ( [ $verbosity ] )>
|
||||
|
||||
Returns the usage information for the command. The optional first parameter
|
||||
is the verbosity to use.
|
||||
|
||||
=method C<help>
|
||||
|
||||
Display help information for this command using L<perldoc>. Excludes AUTHOR
|
||||
and LICENSE sections.
|
||||
|
||||
=method C<run ( @arguments )>
|
||||
|
||||
Runs the command. Parameters should be the command line parameters
|
||||
to use for running the command. This sub should return a true value
|
||||
on success and either die or return a false value on failure. The
|
||||
default method will first call C<process_params> with the given
|
||||
parameters, call C<usage> if there was a problem with parsing the
|
||||
parameters, or call C<process> if there was not. It will return
|
||||
C<process>'s return value to the caller.
|
||||
|
||||
=method C<process>
|
||||
|
||||
Needs to be subclasses to provide the main functionality of the command. This
|
||||
method will be called as part of the run method. Should return a true value
|
||||
on success.
|
||||
|
||||
=cut
|
||||
|
||||
97
lib/WGDev/Command/Base/Verbosity.pm
Normal file
97
lib/WGDev/Command/Base/Verbosity.pm
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package WGDev::Command::Base::Verbosity;
|
||||
# ABSTRACT: Super-class for implementing WGDev commands with verbosity levels
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->{verbosity} = 1;
|
||||
$self->{tab_level} = 0;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
verbose|v
|
||||
quiet|q
|
||||
);
|
||||
}
|
||||
|
||||
sub option_verbose {
|
||||
my $self = shift;
|
||||
$self->{verbosity}++;
|
||||
return;
|
||||
}
|
||||
|
||||
sub option_quiet {
|
||||
my $self = shift;
|
||||
$self->{verbosity}--;
|
||||
return;
|
||||
}
|
||||
|
||||
sub verbosity {
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
return $self->{verbosity} = shift;
|
||||
}
|
||||
return $self->{verbosity};
|
||||
}
|
||||
|
||||
sub report {
|
||||
my $self = shift;
|
||||
my $message = pop;
|
||||
my $verbose_limit = shift;
|
||||
if ( !defined $verbose_limit ) {
|
||||
$verbose_limit = 1;
|
||||
}
|
||||
return
|
||||
if $verbose_limit > $self->verbosity;
|
||||
my $tabs = "\t" x $self->tab_level;
|
||||
print $tabs . $message;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub tab_level {
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
$self->{tab_level} += shift;
|
||||
}
|
||||
return $self->{tab_level};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package WGDev::Command::Mine;
|
||||
use WGDev::Command::Base::Verbosity;
|
||||
@ISA = qw(WGDev::Command::Base::Verbosity);
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
$self->report("Running my command\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A super-class useful for implementing WGDev command modules. Parses the
|
||||
C<--verbose> and C<--quiet> command line options.
|
||||
|
||||
=method C<verbosity ( [ $verbosity ] )>
|
||||
|
||||
Sets or returns the verbosity. This is modified when parsing parameters. Defaults to 1.
|
||||
|
||||
=method C<report ( [ $verbosity, ] $message )>
|
||||
|
||||
Prints messages based on the current verbosity level. If given two
|
||||
parameters, the first must be the verbosity level to start printing the
|
||||
message at. The second parameter is the message to print. Will also accept
|
||||
a single parameter of a message to print starting at verbosity level 1.
|
||||
|
||||
=cut
|
||||
|
||||
218
lib/WGDev/Command/Batchedit.pm
Normal file
218
lib/WGDev/Command/Batchedit.pm
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
package WGDev::Command::Batchedit;
|
||||
# ABSTRACT: Edits assets by URL or asset ID with a pattern and a string
|
||||
# so it can be used in a shell script / batch file
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
command=s
|
||||
tree=s@
|
||||
class=s@
|
||||
pattern=s
|
||||
string=s
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my @assets_to_edit = $self->get_assets_data;
|
||||
|
||||
if ( !@assets_to_edit ) {
|
||||
WGDev::X->throw('No assets to edit!');
|
||||
}
|
||||
|
||||
# get pattern to match
|
||||
my $pattern = $self->{options}->{pattern};
|
||||
|
||||
# get replacement string
|
||||
my $string = $self->{options}->{string};
|
||||
|
||||
my $output_format = "%-8s: %-30s (%22s) %s\n";
|
||||
|
||||
my $version_tag;
|
||||
for my $asset_to_edit (@assets_to_edit) {
|
||||
my $asset_text = $asset_to_edit->{text};
|
||||
my $old_asset_text = $asset_text;
|
||||
$asset_text =~ s/$pattern/$string/xmsg;
|
||||
if ( $asset_text eq $old_asset_text ) {
|
||||
printf $output_format,
|
||||
'Skipping', ( $asset_to_edit->{url} || $asset_to_edit->{title} ),
|
||||
( $asset_to_edit->{asset_id} || q{} ), $asset_to_edit->{title};
|
||||
next;
|
||||
}
|
||||
$version_tag ||= do {
|
||||
require WebGUI::VersionTag;
|
||||
my $vt = WebGUI::VersionTag->getWorking( $wgd->session );
|
||||
$vt->set( { name => 'WGDev Asset Editor' } );
|
||||
$vt;
|
||||
};
|
||||
my $asset_data = $wgd->asset->deserialize($asset_text);
|
||||
my $asset;
|
||||
my $parent;
|
||||
if ( $asset_data->{parent} ) {
|
||||
$parent = eval { $wgd->asset->find( $asset_data->{parent} ) };
|
||||
}
|
||||
if ( $asset_to_edit->{asset_id} ) {
|
||||
$asset = $wgd->asset->by_id( $asset_to_edit->{asset_id}, undef,
|
||||
$asset_to_edit->{revision} );
|
||||
$asset = $asset->addRevision(
|
||||
$asset_data,
|
||||
undef,
|
||||
{
|
||||
skipAutoCommitWorkflows => 1,
|
||||
skipNotification => 1,
|
||||
} );
|
||||
if ($parent) {
|
||||
$asset->setParent($parent);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parent ||= $wgd->asset->import_node;
|
||||
my $asset_id = $asset_data->{assetId};
|
||||
$asset = $parent->addChild(
|
||||
$asset_data,
|
||||
$asset_id,
|
||||
undef,
|
||||
{
|
||||
skipAutoCommitWorkflows => 1,
|
||||
skipNotification => 1,
|
||||
} );
|
||||
}
|
||||
printf $output_format, ( $asset_to_edit->{asset_id} ? 'Updating' : 'Adding' ),
|
||||
$asset->get('url'), $asset->getId, $asset->get('title');
|
||||
}
|
||||
|
||||
if ($version_tag) {
|
||||
$version_tag->commit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_assets_data {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my @assets_data;
|
||||
for my $asset_spec ( $self->arguments ) {
|
||||
my $asset_data = eval { $self->get_asset_data($asset_spec) };
|
||||
if ( !$asset_data ) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
push @assets_data, $asset_data;
|
||||
}
|
||||
if ( !$self->option('tree') ) {
|
||||
return @assets_data;
|
||||
}
|
||||
for my $parent_spec ( @{ $self->option('tree') } ) {
|
||||
my $parent = $wgd->asset->find($parent_spec) || do {
|
||||
warn "$parent_spec is not a valid asset!\n";
|
||||
next;
|
||||
};
|
||||
my $options = {};
|
||||
if ( $self->option('class') ) {
|
||||
my @classes = @{ $self->option('class') };
|
||||
for (@classes) {
|
||||
s/^(?:(?:WebGUI::Asset)?::)?/WebGUI::Asset::/msx;
|
||||
}
|
||||
$options->{includeOnlyClasses} = \@classes;
|
||||
}
|
||||
my $assets
|
||||
= $parent->getLineage( [qw(self descendants)], $options );
|
||||
for my $asset_id ( @{$assets} ) {
|
||||
my $asset_data = $self->get_asset_data($asset_id);
|
||||
if ( !$asset_data ) {
|
||||
next;
|
||||
}
|
||||
push @assets_data, $asset_data;
|
||||
}
|
||||
}
|
||||
return @assets_data;
|
||||
}
|
||||
|
||||
sub get_asset_data {
|
||||
my $self = shift;
|
||||
my $asset = shift;
|
||||
|
||||
my $wgd_asset = $self->wgd->asset;
|
||||
if ( !ref $asset ) {
|
||||
$asset = eval { $wgd_asset->find($asset) };
|
||||
if ( !$asset ) {
|
||||
die $@;
|
||||
}
|
||||
}
|
||||
|
||||
my $asset_text = $self->wgd->asset->serialize($asset);
|
||||
my $short_class = ref $asset || $asset;
|
||||
$short_class =~ s/^WebGUI::Asset:://msx;
|
||||
|
||||
return {
|
||||
text => $asset_text,
|
||||
class => ref $asset || $asset,
|
||||
asset_id => $asset->getId,
|
||||
url => $asset->get('url'),
|
||||
title => $asset->get('title'),
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd batchedit --pattern=<pattern> --string=<string> <asset> [<asset> ...]
|
||||
wgd batchedit --tree=<asset> --pattern=<pattern> --string=<string> [--tree=<asset> ...] [--class=<class> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Edits assets in-place by replacing all matching 'pattern's with 'string'.
|
||||
If modifications are made, the assets are updated.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--pattern=>
|
||||
|
||||
Pattern to match against for replacing.
|
||||
|
||||
=item C<--string=>
|
||||
|
||||
Replacement string for the matched pattern.
|
||||
|
||||
=item C<< <asset> >>
|
||||
|
||||
Either an asset URL or ID. As many as desired can be specified.
|
||||
Prepending with a slash will force it to be interpreted as a URL.
|
||||
|
||||
=item C<--tree=>
|
||||
|
||||
Will open specified asset and all descendants in editor. Can be specified
|
||||
multiple times.
|
||||
|
||||
=item C<--class=>
|
||||
|
||||
Only used with --tree option. Limits exported assets to specified classes.
|
||||
Can be specified as a full (C<WebGUI::Asset::Template>) or abbreviated
|
||||
(C<Template>) class name.
|
||||
|
||||
=back
|
||||
|
||||
=method C<get_assets_data>
|
||||
|
||||
Creates and returns an array of hash references with information about
|
||||
the assets and exported files. Also follows the C<--tree> option.
|
||||
|
||||
=method C<get_asset_data ( $asset_or_class )>
|
||||
|
||||
Accepts an asset, returning a hash reference of information about the
|
||||
asset.
|
||||
|
||||
=cut
|
||||
|
||||
243
lib/WGDev/Command/Build.pm
Normal file
243
lib/WGDev/Command/Build.pm
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
package WGDev::Command::Build;
|
||||
# ABSTRACT: Builds an SQL script and uploads for site creation
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base::Verbosity);
|
||||
|
||||
use File::Spec ();
|
||||
use WGDev::X ();
|
||||
use WGDev::File;
|
||||
|
||||
sub config_options {
|
||||
return (
|
||||
shift->SUPER::config_options, qw(
|
||||
sql|s
|
||||
uploads|u
|
||||
) );
|
||||
}
|
||||
|
||||
sub parse_params {
|
||||
my $self = shift;
|
||||
my $result = $self->SUPER::parse_params(@_);
|
||||
if ( !defined $self->option('sql') && !defined $self->option('uploads') )
|
||||
{
|
||||
$self->option( 'sql', 1 );
|
||||
$self->option( 'uploads', 1 );
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
|
||||
if ( $self->option('sql') ) {
|
||||
$self->create_db_script;
|
||||
}
|
||||
|
||||
if ( $self->option('uploads') ) {
|
||||
$self->update_local_uploads;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub create_db_script {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $version = $wgd->version->database( $wgd->db->connect );
|
||||
$self->report("WebGUI version: $version\n");
|
||||
|
||||
$self->report('Creating database dump... ');
|
||||
my $wg8 = $wgd->version->module =~ /^8[.]/msx;
|
||||
my $db_file = $wg8 ? do {
|
||||
require WebGUI::Paths;
|
||||
WebGUI::Paths->defaultCreateSQL;
|
||||
} : File::Spec->catfile( $wgd->root, 'docs', 'create.sql' );
|
||||
open my $out, q{>}, $db_file
|
||||
or WGDev::X::IO::Write->throw( path => $db_file );
|
||||
|
||||
$self->write_db_header($out);
|
||||
$self->write_db_structure($out);
|
||||
$self->write_db_data($out);
|
||||
$self->write_db_footer($out);
|
||||
|
||||
close $out
|
||||
or WGDev::X::IO::Write->throw( path => $db_file );
|
||||
$self->report("Done.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub write_db_header {
|
||||
my $self = shift;
|
||||
my $out = shift;
|
||||
print {$out} <<'END_SQL';
|
||||
SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT;
|
||||
SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS;
|
||||
SET @OLD_CHARACTER_SET_CONNECTION = @@CHARACTER_SET_CONNECTION;
|
||||
SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION;
|
||||
SET @OLD_TIME_ZONE = @@TIME_ZONE;
|
||||
SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS;
|
||||
SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS;
|
||||
SET @OLD_SQL_MODE = @@SQL_MODE;
|
||||
SET @OLD_SQL_NOTES = @@SQL_NOTES;
|
||||
|
||||
SET CHARACTER_SET_CLIENT = 'utf8';
|
||||
SET CHARACTER_SET_RESULTS = 'utf8';
|
||||
SET CHARACTER_SET_CONNECTION = 'utf8';
|
||||
SET TIME_ZONE = '+00:00';
|
||||
SET UNIQUE_CHECKS = 0;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
|
||||
SET SQL_NOTES = 0;
|
||||
END_SQL
|
||||
return;
|
||||
}
|
||||
|
||||
sub write_db_footer {
|
||||
my $self = shift;
|
||||
my $out = shift;
|
||||
print {$out} <<'END_SQL';
|
||||
SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT;
|
||||
SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS;
|
||||
SET CHARACTER_SET_CONNECTION = @OLD_CHARACTER_SET_CONNECTION;
|
||||
SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION;
|
||||
SET TIME_ZONE = @OLD_TIME_ZONE;
|
||||
SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS;
|
||||
SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS;
|
||||
SET SQL_MODE = @OLD_SQL_MODE;
|
||||
SET SQL_NOTES = @OLD_SQL_NOTES;
|
||||
END_SQL
|
||||
return;
|
||||
}
|
||||
|
||||
sub write_db_structure {
|
||||
my $self = shift;
|
||||
my $out = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
open my $in, q{-|}, 'mysqldump',
|
||||
$wgd->db->command_line( '--compact', '--no-data',
|
||||
'--compatible=mysql40' )
|
||||
or WGDev::X::System->throw('Unable to run mysqldump');
|
||||
my $statement;
|
||||
while ( my $line = <$in> ) {
|
||||
next
|
||||
if $line =~ /\bSET[^=]+=\s*[@][@]character_set_client;/msxi
|
||||
|| $line =~ /\bSET\s+character_set_client\b/msxi;
|
||||
if ( !$statement && $line =~ /\A(CREATE[ ]TABLE)/msx ) {
|
||||
$statement = $1;
|
||||
}
|
||||
if ( $statement && $line =~ /;\n?\z/msx ) {
|
||||
if ( $statement eq 'CREATE TABLE' ) {
|
||||
$line =~ s/TYPE=(InnoDB|MyISAM)/ENGINE=$1/;
|
||||
$line =~ s/;(\n?)\z/ CHARSET=utf8;$1/msx;
|
||||
}
|
||||
undef $statement;
|
||||
}
|
||||
print {$out} $line;
|
||||
}
|
||||
close $in
|
||||
or WGDev::X::System->throw('Unable to run mysqldump');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub write_db_data {
|
||||
my $self = shift;
|
||||
my $out = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $dbh = $wgd->db->connect;
|
||||
my $version = $wgd->version->database($dbh);
|
||||
|
||||
my %skip_data_tables = map { $_ => 1 } qw(
|
||||
userSession userSessionScratch
|
||||
webguiVersion userLoginLog
|
||||
assetHistory cache
|
||||
);
|
||||
|
||||
my @tables;
|
||||
|
||||
my $sth = $dbh->table_info( undef, undef, q{%}, undef );
|
||||
while ( ( undef, undef, my $table ) = $sth->fetchrow_array ) {
|
||||
next
|
||||
if $skip_data_tables{$table};
|
||||
my ($count)
|
||||
= $dbh->selectrow_array(
|
||||
'SELECT COUNT(*) FROM ' . $dbh->quote_identifier($table) );
|
||||
next
|
||||
if !$count;
|
||||
push @tables, $table;
|
||||
}
|
||||
|
||||
open my $in, q{-|}, 'mysqldump',
|
||||
$wgd->db->command_line( '--no-create-info', '--compact',
|
||||
'--disable-keys', sort @tables, )
|
||||
or WGDev::X::System->throw('Unable to run mysqldump');
|
||||
while ( my $line = <$in> ) {
|
||||
$line =~ s{ /[*] !\d+ \s+ ([^*]+?) \s* [*]/; }{$1;}msx;
|
||||
print {$out} $line;
|
||||
}
|
||||
close $in
|
||||
or WGDev::X::System->throw('Unable to run mysqldump');
|
||||
|
||||
print {$out} 'INSERT INTO webguiVersion '
|
||||
. '(webguiVersion,versionType,dateApplied) '
|
||||
. "VALUES ('$version','Initial Install',UNIX_TIMESTAMP());\n";
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub update_local_uploads {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
$self->report('Loading uploads from site... ');
|
||||
|
||||
my $wg_uploads = File::Spec->catdir( $wgd->root, 'www', 'uploads' );
|
||||
my $site_uploads = $wgd->config->get('uploadsPath');
|
||||
WGDev::File->sync_dirs($site_uploads, $wg_uploads);
|
||||
|
||||
$self->report("Done\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd build [-s] [-u]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Uses the current database and uploads to build a new F<create.sql> and update
|
||||
the local uploads directory. With no options, builds both the database
|
||||
script and the uploads directory.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-s> C<--sql>
|
||||
|
||||
Make F<create.sql> based on current database contents
|
||||
|
||||
=item C<-u> C<--uploads>
|
||||
|
||||
Make uploads based on current site's uploads
|
||||
|
||||
=back
|
||||
|
||||
=method C<create_db_script>
|
||||
|
||||
Builds the F<create.sql> database script. This is done as a dump of the current
|
||||
database structure and data, excluding the data from some tables.
|
||||
|
||||
=method C<update_local_uploads>
|
||||
|
||||
Updates the working directory's uploads from the current site. Files will be
|
||||
deleted or created so the two match.
|
||||
|
||||
=cut
|
||||
|
||||
93
lib/WGDev/Command/Commands.pm
Normal file
93
lib/WGDev/Command/Commands.pm
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package WGDev::Command::Commands;
|
||||
# ABSTRACT: List WGDev sub-commands
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::Command;
|
||||
use WGDev::Help;
|
||||
use WGDev::X ();
|
||||
|
||||
sub needs_root {
|
||||
return;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
return $self->help;
|
||||
}
|
||||
|
||||
sub help {
|
||||
my $class = shift;
|
||||
print "Sub-commands available:\n";
|
||||
my %abstracts = $class->command_abstracts;
|
||||
my @commands = sort keys %abstracts;
|
||||
@commands = (
|
||||
'intro',
|
||||
'commands',
|
||||
'help',
|
||||
undef,
|
||||
grep { $_ ne 'intro' && $_ ne 'commands' && $_ ne 'help' } @commands,
|
||||
);
|
||||
for my $command (@commands) {
|
||||
if ( !defined $command ) {
|
||||
print "\n";
|
||||
next;
|
||||
}
|
||||
my $command_abstract = $abstracts{$command} || '(external command)';
|
||||
printf " %-15s - %s\n", $command, $command_abstract;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub command_abstracts {
|
||||
my $class = shift;
|
||||
my %abstracts = map { $_ => undef } WGDev::Command->command_list;
|
||||
require Pod::PlainText;
|
||||
my $parser = Pod::PlainText->new( indent => 0, width => 1000 );
|
||||
$parser->select('NAME');
|
||||
for my $command ( keys %abstracts ) {
|
||||
my $command_module
|
||||
= eval { WGDev::Command->get_command_module($command) };
|
||||
next
|
||||
if !$command_module;
|
||||
my $pod = WGDev::Help::package_pod($command_module);
|
||||
my $formatted_pod = q{};
|
||||
open my $pod_in, '<', \$pod
|
||||
or WGDev::X::IO->throw;
|
||||
open my $pod_out, '>', \$formatted_pod
|
||||
or WGDev::X::IO->throw;
|
||||
$parser->parse_from_filehandle( $pod_in, $pod_out );
|
||||
close $pod_in or WGDev::X::IO->throw;
|
||||
close $pod_out or WGDev::X::IO->throw;
|
||||
|
||||
if ( $formatted_pod =~ /^ [:\w]+ \s* - \s* (.+?) \s* $/msx ) {
|
||||
$abstracts{$command} = $1;
|
||||
}
|
||||
}
|
||||
return %abstracts;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd commands
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Provides an overview of the available WGDev commands.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
None
|
||||
|
||||
=method C<command_abstracts>
|
||||
|
||||
A class method which returns a hash with keys of the available
|
||||
commands and values of the module abstract extracted from POD.
|
||||
|
||||
=cut
|
||||
|
||||
128
lib/WGDev/Command/Config.pm
Normal file
128
lib/WGDev/Command/Config.pm
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package WGDev::Command::Config;
|
||||
# ABSTRACT: Report or set WGDev configuration parameters
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev ();
|
||||
use WGDev::X ();
|
||||
use WGDev::Command ();
|
||||
|
||||
sub needs_root {
|
||||
return;
|
||||
}
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
struct|s
|
||||
);
|
||||
}
|
||||
|
||||
sub config_parse_options { return qw(gnu_getopt pass_through) }
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my @args = $self->arguments;
|
||||
|
||||
if ( !@args ) {
|
||||
my $usage = $self->usage(0);
|
||||
warn $usage;
|
||||
return;
|
||||
}
|
||||
|
||||
my ( $config_param, $value ) = @args;
|
||||
my @config_path = split /[.]/msx, $config_param;
|
||||
|
||||
if ( defined $value ) {
|
||||
if ( $value =~ s/\A@//msx ) {
|
||||
my $file = $value;
|
||||
my $fh;
|
||||
if ( $file eq q{-} ) {
|
||||
open $fh, '<&=', \*STDIN
|
||||
or WGDev::X::IO::Read->throw;
|
||||
}
|
||||
else {
|
||||
open $fh, '<', $file
|
||||
or WGDev::X::IO::Read->throw( path => $file );
|
||||
}
|
||||
$value = do { local $/; <$fh> };
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => $file );
|
||||
}
|
||||
if ( $self->option('struct') ) {
|
||||
$value =~ s/\A \s* ( [[{] ) /--- $1/msx;
|
||||
$value .= "\n";
|
||||
eval {
|
||||
$value = WGDev::yaml_decode($value);
|
||||
1;
|
||||
} or WGDev::X->throw('Invalid or unsupported format.');
|
||||
}
|
||||
}
|
||||
my $param
|
||||
= $wgd->wgd_config( \@config_path, defined $value ? $value : () );
|
||||
if ( defined $value && defined $param ) {
|
||||
$wgd->write_wgd_config;
|
||||
return 1;
|
||||
}
|
||||
if ( ref $param ) {
|
||||
$param = WGDev::yaml_encode($param);
|
||||
$param =~ s/\A---(?:\Q {}\E)?\n?//msx;
|
||||
}
|
||||
elsif ( !defined $param ) {
|
||||
return 0;
|
||||
}
|
||||
$param =~ s/\n?\z/\n/msx;
|
||||
print $param;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd config [--struct] <config path> [<value>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Report or set WGDev configuration parameters.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-s> C<--struct>
|
||||
|
||||
When setting a config value, specifies that the value should be treated as a
|
||||
data structure formatted as YAML or JSON.
|
||||
|
||||
=item C<< <config path> >>
|
||||
|
||||
Path of the the config variable to retrieve. Sub-level options are specified
|
||||
as a period separated list of keys. Complex options will be returned formatted
|
||||
as YAML.
|
||||
|
||||
=item C<< <value> >>
|
||||
|
||||
The value to set the config option to.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
The WGDev config file is a JSON formatted file existing as either
|
||||
F</etc/wgdevcfg> or F<.wgdevcfg> in the current user's home directory.
|
||||
|
||||
A simple config file looks like:
|
||||
|
||||
{
|
||||
"command" : {
|
||||
"webgui_root" : "/data/WebGUI",
|
||||
"webgui_config" : "dev.localhost.localdomain.conf"
|
||||
}
|
||||
}
|
||||
|
||||
=cut
|
||||
|
||||
115
lib/WGDev/Command/Db.pm
Normal file
115
lib/WGDev/Command/Db.pm
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package WGDev::Command::Db;
|
||||
# ABSTRACT: Connect to database with the MySQL client
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
print|p
|
||||
dump|d:s
|
||||
load|l=s
|
||||
clear|c
|
||||
show
|
||||
);
|
||||
}
|
||||
|
||||
sub config_parse_options { return qw(gnu_getopt pass_through) }
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $db = $self->wgd->db;
|
||||
my @command_line = $db->command_line( $self->arguments );
|
||||
if ( ( defined $self->option('print') || 0 )
|
||||
+ ( defined $self->option('dump') || 0 )
|
||||
+ ( defined $self->option('load') || 0 )
|
||||
+ ( defined $self->option('clear') || 0 ) > 1 )
|
||||
{
|
||||
WGDev::X->throw('Multiple database operations specified!');
|
||||
}
|
||||
|
||||
if ( $self->option('print') ) {
|
||||
print join q{ }, map {"$_"} @command_line;
|
||||
return 1;
|
||||
}
|
||||
if ( $self->option('clear') ) {
|
||||
$db->clear;
|
||||
return 1;
|
||||
}
|
||||
if ( defined $self->option('load') ) {
|
||||
if ( $self->option('load') && $self->option('load') ne q{-} ) {
|
||||
$db->clear;
|
||||
$db->load( $self->option('load') );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if ( defined $self->option('dump') ) {
|
||||
if ( $self->option('dump') && $self->option('dump') ne q{-} ) {
|
||||
$db->dump( $self->option('dump') );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
my $return = system {'mysqldump'} 'mysqldump', @command_line;
|
||||
return $return ? 0 : 1;
|
||||
}
|
||||
}
|
||||
if ( defined $self->option('show') ) {
|
||||
my $return = system {'mysqlshow'} 'mysqlshow', @command_line;
|
||||
return $return ? 0 : 1;
|
||||
}
|
||||
my $return = system {'mysql'} 'mysql', @command_line;
|
||||
return $return ? 0 : 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd db [-p | -d | -l | -c | --show] [mysql options]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Opens the C<mysql> client to your WebGUI database, loads or dumps a database
|
||||
script, or displays database information, or clears a database's contents.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Any arguments not recognized will be passed through to the C<mysql> or
|
||||
C<mysqldump> commands as applicable.
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-p> C<--print>
|
||||
|
||||
Prints out the command options that would be passed to C<mysql>
|
||||
|
||||
=item C<-d> C<--dump=>
|
||||
|
||||
Dumps the database as an SQL script. If a file is specified, dumps to that
|
||||
file. Otherwise, dumps to standard out.
|
||||
|
||||
=item C<-l> C<--load=>
|
||||
|
||||
Loads a database script into the database. Database script must be specified.
|
||||
|
||||
=item C<-c> C<--clear>
|
||||
|
||||
Clears the database, removing all tables.
|
||||
|
||||
=item C<--show>
|
||||
|
||||
Shows database information via C<mysqlshow>.
|
||||
|
||||
For example, to display a summary of the number of columns and rows in each table,
|
||||
use C<mysqlshow>'s C<--count> option:
|
||||
|
||||
wgd db --show --count
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
184
lib/WGDev/Command/Dist.pm
Normal file
184
lib/WGDev/Command/Dist.pm
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package WGDev::Command::Dist;
|
||||
# ABSTRACT: Create a distribution file for WebGUI
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use File::Spec ();
|
||||
|
||||
sub config_options {
|
||||
return (
|
||||
shift->SUPER::config_options, qw(
|
||||
buildDir|b=s
|
||||
) );
|
||||
}
|
||||
|
||||
sub needs_config {
|
||||
return;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
require File::Temp;
|
||||
File::Temp->VERSION(0.19); ##no critic (ProhibitMagicNumbers)
|
||||
require File::Copy;
|
||||
require Cwd;
|
||||
|
||||
my ( $version, $status ) = $wgd->version->module;
|
||||
my $build_dir = $self->option('buildDir');
|
||||
my $build_root;
|
||||
if ($build_dir) {
|
||||
$build_root = $build_dir;
|
||||
mkdir $build_root;
|
||||
}
|
||||
if ( $build_root && !-e $build_root ) {
|
||||
$build_root = File::Temp->newdir;
|
||||
}
|
||||
my $build_webgui = File::Spec->catdir( $build_root, 'WebGUI' );
|
||||
my $build_docs = File::Spec->catdir( $build_root, 'api' );
|
||||
my $cwd = Cwd::cwd();
|
||||
|
||||
mkdir $build_webgui;
|
||||
$self->export_files($build_webgui);
|
||||
my $inst_dir = $build_dir || $cwd;
|
||||
if ( !fork ) {
|
||||
chdir $build_root;
|
||||
exec 'tar', 'czf',
|
||||
File::Spec->catfile( $inst_dir,
|
||||
"webgui-$version-$status.tar.gz" ),
|
||||
'WebGUI';
|
||||
}
|
||||
wait;
|
||||
|
||||
mkdir $build_docs;
|
||||
$self->generate_docs($build_docs);
|
||||
if ( !fork ) {
|
||||
chdir $build_root;
|
||||
exec 'tar', 'czf',
|
||||
File::Spec->catfile(
|
||||
$inst_dir, "webgui-api-$version-$status.tar.gz"
|
||||
),
|
||||
'api';
|
||||
}
|
||||
wait;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub export_files {
|
||||
my $self = shift;
|
||||
my $to_root = shift;
|
||||
my $from = $self->wgd->root;
|
||||
|
||||
if ( -e File::Spec->catdir( $from, '.git' ) ) {
|
||||
system 'git', '--git-dir=' . File::Spec->catdir( $from, '.git' ),
|
||||
'checkout-index', '-a', '--prefix=' . $to_root . q{/};
|
||||
}
|
||||
elsif ( -e File::Spec->catdir( $from, '.svn' ) ) {
|
||||
system 'svn', 'export', $from, $to_root;
|
||||
}
|
||||
else {
|
||||
system 'cp', '-r', $from, $to_root;
|
||||
}
|
||||
|
||||
for my $file (
|
||||
[ 'docs', 'previousVersion.sql' ],
|
||||
[ 'etc', '*.conf' ],
|
||||
[ 'sbin', 'preload.custom' ],
|
||||
[ 'sbin', 'preload.exclude' ] )
|
||||
{
|
||||
my $file_path = File::Spec->catfile( $to_root, @{$file} );
|
||||
for my $file ( glob $file_path ) {
|
||||
unlink $file;
|
||||
}
|
||||
}
|
||||
return $to_root;
|
||||
}
|
||||
|
||||
sub generate_docs {
|
||||
my $self = shift;
|
||||
my $to_root = shift;
|
||||
my $from = $self->wgd->root;
|
||||
require File::Find;
|
||||
require File::Path;
|
||||
require Pod::Html;
|
||||
require File::Temp;
|
||||
File::Temp->VERSION(0.19); ##no critic (ProhibitMagicNumbers)
|
||||
my $code_dir = File::Spec->catdir( $from, 'lib', 'WebGUI' );
|
||||
my $temp_dir = File::Temp->newdir;
|
||||
File::Find::find( {
|
||||
no_chdir => 1,
|
||||
wanted => sub {
|
||||
no warnings 'once';
|
||||
my $code_file = $File::Find::name;
|
||||
return
|
||||
if -d $code_file;
|
||||
my $doc_file = $code_file;
|
||||
return
|
||||
if $doc_file =~ /\b\QOperation.pm\E$/msx;
|
||||
return
|
||||
if $doc_file !~ s/\Q.pm\E$/.html/msx;
|
||||
$doc_file = File::Spec->rel2abs(
|
||||
File::Spec->abs2rel( $doc_file, $code_dir ), $to_root );
|
||||
my $directory = File::Spec->catpath(
|
||||
( File::Spec->splitpath($doc_file) )[ 0, 1 ] );
|
||||
File::Path::mkpath($directory);
|
||||
Pod::Html::pod2html(
|
||||
'--quiet',
|
||||
'--noindex',
|
||||
'--infile=' . $code_file,
|
||||
'--outfile=' . $doc_file,
|
||||
'--cachedir=' . $temp_dir,
|
||||
);
|
||||
},
|
||||
},
|
||||
$code_dir
|
||||
);
|
||||
return $to_root;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd dist [-c] [-d] [-b /data/builds]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Generates distribution files containing WebGUI or the WebGUI API.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
By default, generates both a code and API documentation package.
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-c> C<--code>
|
||||
|
||||
Generates a code distribution
|
||||
|
||||
=item C<-d> C<--documentation>
|
||||
|
||||
Generates an API documentation distribution
|
||||
|
||||
=item C<-b> C<--buildDir>
|
||||
|
||||
Install the directories and tarballs in a different location. If no build directory
|
||||
is specified, it will create a temp file.
|
||||
|
||||
=back
|
||||
|
||||
=method C<export_files ( $directory )>
|
||||
|
||||
Exports the WebGUI root directory, excluding common site specific files, to
|
||||
the specified directory.
|
||||
|
||||
=method C<generate_docs ( $directory )>
|
||||
|
||||
Generate API documentation for WebGUI using Pod::Html in the specified
|
||||
directory.
|
||||
|
||||
=cut
|
||||
|
||||
224
lib/WGDev/Command/Edit.pm
Normal file
224
lib/WGDev/Command/Edit.pm
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package WGDev::Command::Edit;
|
||||
# ABSTRACT: Edits assets by URL
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
command=s
|
||||
tree=s@
|
||||
class=s@
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my @files = $self->export_asset_data;
|
||||
|
||||
if ( !@files ) {
|
||||
WGDev::X->throw('No assets to edit!');
|
||||
}
|
||||
|
||||
## no critic (ProhibitParensWithBuiltins)
|
||||
my $command = $self->option('command') || $ENV{EDITOR} || 'vi';
|
||||
system join( q{ }, $command, map { $_->{filename} } @files );
|
||||
|
||||
my $output_format = "%-8s: %-30s (%22s) %s\n";
|
||||
|
||||
my $version_tag;
|
||||
for my $file (@files) {
|
||||
open my $fh, '<:encoding(UTF-8)', $file->{filename} or next;
|
||||
my $asset_text = do { local $/; <$fh> };
|
||||
close $fh or next;
|
||||
unlink $file->{filename};
|
||||
if ( $asset_text eq $file->{text} ) {
|
||||
printf $output_format,
|
||||
'Skipping', ( $file->{url} || $file->{title} ),
|
||||
( $file->{asset_id} || q{} ), $file->{title};
|
||||
next;
|
||||
}
|
||||
$version_tag ||= do {
|
||||
require WebGUI::VersionTag;
|
||||
my $vt = WebGUI::VersionTag->getWorking( $wgd->session );
|
||||
$vt->set( { name => 'WGDev Asset Editor' } );
|
||||
$vt;
|
||||
};
|
||||
my $asset_data = $wgd->asset->deserialize($asset_text);
|
||||
my $asset;
|
||||
my $parent;
|
||||
if ( $asset_data->{parent} ) {
|
||||
$parent = eval { $wgd->asset->find( $asset_data->{parent} ) };
|
||||
}
|
||||
if ( $file->{asset_id} ) {
|
||||
$asset = $wgd->asset->by_id( $file->{asset_id}, undef,
|
||||
$file->{revision} );
|
||||
$asset = $asset->addRevision(
|
||||
$asset_data,
|
||||
undef,
|
||||
{
|
||||
skipAutoCommitWorkflows => 1,
|
||||
skipNotification => 1,
|
||||
} );
|
||||
if ($parent) {
|
||||
$asset->setParent($parent);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parent ||= $wgd->asset->import_node;
|
||||
my $asset_id = $asset_data->{assetId};
|
||||
$asset = $parent->addChild(
|
||||
$asset_data,
|
||||
$asset_id,
|
||||
undef,
|
||||
{
|
||||
skipAutoCommitWorkflows => 1,
|
||||
skipNotification => 1,
|
||||
} );
|
||||
}
|
||||
printf $output_format, ( $file->{asset_id} ? 'Updating' : 'Adding' ),
|
||||
$asset->get('url'), $asset->getId, $asset->get('title');
|
||||
}
|
||||
|
||||
if ($version_tag) {
|
||||
$version_tag->commit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub export_asset_data {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my @files;
|
||||
for my $asset_spec ( $self->arguments ) {
|
||||
my $file_data = eval { $self->write_temp($asset_spec) };
|
||||
if ( !$file_data ) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
push @files, $file_data;
|
||||
}
|
||||
if ( $self->option('tree') ) {
|
||||
for my $parent_spec ( @{ $self->option('tree') } ) {
|
||||
my $parent = $wgd->asset->find($parent_spec) || do {
|
||||
warn "$parent_spec is not a valid asset!\n";
|
||||
next;
|
||||
};
|
||||
my $options = {};
|
||||
if ( $self->option('class') ) {
|
||||
my @classes = @{ $self->option('class') };
|
||||
for (@classes) {
|
||||
s/^(?:(?:WebGUI::Asset)?::)?/WebGUI::Asset::/msx;
|
||||
}
|
||||
$options->{includeOnlyClasses} = \@classes;
|
||||
}
|
||||
my $assets
|
||||
= $parent->getLineage( [qw(self descendants)], $options );
|
||||
for my $asset_id ( @{$assets} ) {
|
||||
my $file_data = $self->write_temp($asset_id);
|
||||
if ( !$file_data ) {
|
||||
next;
|
||||
}
|
||||
push @files, $file_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return @files;
|
||||
}
|
||||
|
||||
sub write_temp {
|
||||
my $self = shift;
|
||||
my $asset = shift;
|
||||
require File::Temp;
|
||||
|
||||
my $wgd_asset = $self->wgd->asset;
|
||||
if ( !ref $asset ) {
|
||||
$asset = eval { $wgd_asset->find($asset) }
|
||||
|| eval { scalar $wgd_asset->validate_class($asset) };
|
||||
if ( !$asset ) {
|
||||
die $@;
|
||||
}
|
||||
}
|
||||
|
||||
my $short_class = ref $asset || $asset;
|
||||
$short_class =~ s/^WebGUI::Asset:://msx;
|
||||
|
||||
my ( $fh, $filename ) = File::Temp::tempfile();
|
||||
binmode $fh, ':encoding(UTF-8)';
|
||||
my $asset_text = $self->wgd->asset->serialize($asset);
|
||||
|
||||
print {$fh} $asset_text;
|
||||
close $fh or return;
|
||||
return {
|
||||
filename => $filename,
|
||||
text => $asset_text,
|
||||
class => ref $asset || $asset,
|
||||
ref $asset
|
||||
? (
|
||||
asset_id => $asset->getId,
|
||||
url => $asset->get('url'),
|
||||
title => $asset->get('title'),
|
||||
)
|
||||
: ( title => 'New ' . $short_class, ),
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd edit [--command=<command>] <asset> [<asset> ...]
|
||||
wgd edit --tree=<asset> [--tree=<asset> ...] [--class=<class> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Exports asset to temporary files, then opens them in your prefered editor.
|
||||
If modifications are made, the assets are updated.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--command=>
|
||||
|
||||
Command to be executed. If not specified, uses the EDITOR environment
|
||||
variable. If that is not specified, uses C<$EDITOR> or C<vi>.
|
||||
|
||||
=item C<< <asset> >>
|
||||
|
||||
Either an asset URL, ID, or class name. As many can be specified as desired.
|
||||
Prepending with a slash will force it to be interpreted as a URL. Class names
|
||||
specified will be opened with a skeleton for the asset type.
|
||||
|
||||
=item C<--tree=>
|
||||
|
||||
Will open specified asset and all descendants in editor. Can be specified
|
||||
multiple times.
|
||||
|
||||
=item C<--class=>
|
||||
|
||||
Only used with --tree option. Limits exported assets to specified classes.
|
||||
Can be specified as a full (C<WebGUI::Asset::Template>) or abbreviated
|
||||
(C<Template>) class name.
|
||||
|
||||
=back
|
||||
|
||||
=method C<export_asset_data>
|
||||
|
||||
For each item in C<arguments>, exports the asset serialized to text to a
|
||||
temporary file. Also follows the C<--tree> option. Returns an array of
|
||||
hash references with information about the assets and exported files.
|
||||
|
||||
=method C<write_temp ( $asset_or_class )>
|
||||
|
||||
Accepts an asset or a class name and exports it serialized as a text file.
|
||||
Returns a hash reference of information about the file ans asset.
|
||||
|
||||
=cut
|
||||
|
||||
101
lib/WGDev/Command/Export.pm
Normal file
101
lib/WGDev/Command/Export.pm
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package WGDev::Command::Export;
|
||||
# ABSTRACT: Exports assets to files
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
stdout
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
|
||||
my $wgd_asset = $self->wgd->asset;
|
||||
for my $asset_spec ( $self->arguments ) {
|
||||
my $asset = eval { $wgd_asset->find($asset_spec) }
|
||||
|| eval { $wgd_asset->validate_class($asset_spec) };
|
||||
if ( !$asset ) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
my $asset_text = $self->wgd->asset->serialize($asset);
|
||||
if ( $self->option('stdout') ) {
|
||||
print $asset_text;
|
||||
}
|
||||
else {
|
||||
my $filename = $self->export_filename($asset);
|
||||
print "Writing $filename...\n";
|
||||
open my $fh, '>', $filename
|
||||
or WGDev::X::IO::Write->throw( path => $filename );
|
||||
print {$fh} $asset_text;
|
||||
close $fh
|
||||
or WGDev::X::IO::Write->throw( path => $filename );
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub export_filename {
|
||||
my $self = shift;
|
||||
my $asset = shift;
|
||||
my $class = ref $asset || $asset;
|
||||
my $short_class = $class;
|
||||
$short_class =~ s/.*:://msx;
|
||||
my $extension = lc $short_class;
|
||||
$extension =~ tr/aeiouy//d;
|
||||
$extension =~ tr/a-z//s;
|
||||
my $filename;
|
||||
|
||||
if ( ref $asset ) {
|
||||
$filename = ( split m{/}msx, $asset->get('url') )[-1];
|
||||
}
|
||||
else {
|
||||
$filename = 'new-' . lc $short_class;
|
||||
}
|
||||
$filename .= ".$extension";
|
||||
return $filename;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd export [--stdout] <asset> [<asset> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Exports asset to files.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--stdout>
|
||||
|
||||
Exports to standard out instead of a file. This only makes sense with a single asset specified.
|
||||
|
||||
=item C<< <asset> >>
|
||||
|
||||
Either an asset URL, ID, class name. As many can be specified as desired.
|
||||
Prepending with a slash will force it to be interpreted as a URL. Asset
|
||||
classes will generate skeletons of export files for the given class.
|
||||
|
||||
=back
|
||||
|
||||
=method C<export_filename ( $asset_or_class )>
|
||||
|
||||
Calculates the file name to export an asset as. Accepts a parameter of the
|
||||
asset object or an asset class name. The file name will be the last portion
|
||||
of the asset's URL, with an extension based on the asset's class name. If
|
||||
provided only a class name, the file name will also be based on the class
|
||||
name.
|
||||
|
||||
=cut
|
||||
|
||||
119
lib/WGDev/Command/Export/Branch.pm
Normal file
119
lib/WGDev/Command/Export/Branch.pm
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package WGDev::Command::Export::Branch;
|
||||
# ABSTRACT: Export a branch of assets
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base::Verbosity);
|
||||
|
||||
use File::Spec ();
|
||||
use Cwd ();
|
||||
use constant LINEAGE_LEVEL_LENGTH => 6;
|
||||
|
||||
sub config_options {
|
||||
return (
|
||||
shift->SUPER::config_options, qw(
|
||||
to|t=s
|
||||
hier!
|
||||
) );
|
||||
}
|
||||
|
||||
sub parse_params {
|
||||
my $self = shift;
|
||||
my $result = $self->SUPER::parse_params(@_);
|
||||
$self->set_option_default( 'hier', 1 );
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
require File::Path;
|
||||
|
||||
my $wgd_asset = $self->wgd->asset;
|
||||
|
||||
my $base_dir = $self->option('to') || Cwd::cwd;
|
||||
my $heir = $self->option('hier');
|
||||
|
||||
for my $asset_spec ( $self->arguments ) {
|
||||
my $base_asset = eval { $wgd_asset->find($asset_spec) };
|
||||
if ( !$base_asset ) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
$self->report( 'Exporting "' . $base_asset->get('title') . "...\n" );
|
||||
if ( $self->verbosity ) {
|
||||
$self->tab_level(1);
|
||||
}
|
||||
my $iter
|
||||
= $base_asset->getLineageIterator( [ 'self', 'descendants' ] );
|
||||
my $base_depth
|
||||
= length( $base_asset->get('lineage') ) / LINEAGE_LEVEL_LENGTH;
|
||||
while ( my $asset = $iter->() ) {
|
||||
my @url_segments;
|
||||
if ($heir) {
|
||||
my $parent = $asset;
|
||||
my $depth
|
||||
= length( $asset->get('lineage') ) / LINEAGE_LEVEL_LENGTH;
|
||||
while (1) {
|
||||
my $url_part = $parent->get('url');
|
||||
$url_part =~ s{.*/}{}msx;
|
||||
unshift @url_segments, $url_part;
|
||||
last
|
||||
if --$depth < $base_depth;
|
||||
$parent = $parent->getParent;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@url_segments = split m{/}msx, $asset->get('url');
|
||||
}
|
||||
my $extension = $wgd_asset->export_extension($asset);
|
||||
my $filename = ( pop @url_segments ) . ".$extension";
|
||||
$self->report( 0,
|
||||
File::Spec->catfile( @url_segments, $filename ) . "\n" );
|
||||
my $dir = File::Spec->catdir( $base_dir, @url_segments );
|
||||
my $full_path = File::Spec->catfile( $dir, $filename );
|
||||
File::Path::mkpath($dir);
|
||||
my $asset_text = $wgd_asset->serialize($asset);
|
||||
open my $fh, '>', $full_path
|
||||
or WGDev::X::IO::Write->throw( path => $full_path );
|
||||
print {$fh} $asset_text;
|
||||
close $fh
|
||||
or WGDev::X::IO::Write->throw( path => $full_path );
|
||||
}
|
||||
if ( $self->verbosity ) {
|
||||
$self->tab_level(-1);
|
||||
}
|
||||
$self->report("Done.\n");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd export-branch [--no-hier] [--to=<output dir>] <asset> [<asset> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Exports a branch of assets as serialized files.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--[no-]hier>
|
||||
|
||||
Exports assets in a directories based on their hierarchy in the
|
||||
asset tree. If not enabled, the serialized assets' location is
|
||||
based directly on their URLs. Enabled by default.
|
||||
|
||||
=item C<-t> C<--to=>
|
||||
|
||||
Output directory to place the exported files in. If not specified,
|
||||
files are placed in the current directory.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
193
lib/WGDev/Command/For/Each.pm
Normal file
193
lib/WGDev/Command/For/Each.pm
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package WGDev::Command::For::Each;
|
||||
# ABSTRACT: Run command for each available config file
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->{commands} = [];
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub needs_config {
|
||||
return;
|
||||
}
|
||||
|
||||
sub needs_root {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub config_options {
|
||||
return (
|
||||
shift->SUPER::config_options, qw(
|
||||
exec|e=s
|
||||
print|p:s
|
||||
print0|0:s
|
||||
wgd|c|w=s
|
||||
|
||||
force|f
|
||||
) );
|
||||
}
|
||||
|
||||
sub option_exec {
|
||||
my $self = shift;
|
||||
my ($command) = @_;
|
||||
push @{ $self->{commands} },
|
||||
command_exec => [$command];
|
||||
}
|
||||
|
||||
sub option_print {
|
||||
my $self = shift;
|
||||
my $format = shift;
|
||||
push @{ $self->{commands} },
|
||||
command_print => [
|
||||
format => $format,
|
||||
];
|
||||
}
|
||||
|
||||
sub option_print0 {
|
||||
my $self = shift;
|
||||
my $format = shift;
|
||||
push @{ $self->{commands} },
|
||||
command_print => [
|
||||
separator => "\0",
|
||||
format => $format,
|
||||
];
|
||||
}
|
||||
|
||||
sub option_wgd {
|
||||
my $self = shift;
|
||||
my ($command) = @_;
|
||||
push @{ $self->{commands} },
|
||||
command_wgd => [$command];
|
||||
}
|
||||
|
||||
sub command_print {
|
||||
my $self = shift;
|
||||
my %options = @_;
|
||||
my $separator = $options{separator} || "\n";
|
||||
my $format = $options{format} || '%s';
|
||||
$format .= $separator;
|
||||
printf $format, $self->wgd->config_file;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub command_exec {
|
||||
my $self = shift;
|
||||
my $command = shift;
|
||||
local %ENV = %ENV;
|
||||
$self->wgd->set_environment(localized => 1);
|
||||
system $command
|
||||
and WGDev::X::System->throw('Error running shell command.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub command_wgd {
|
||||
my $self = shift;
|
||||
my $command = shift;
|
||||
my $wgd = $self->wgd;
|
||||
require Text::ParseWords;
|
||||
|
||||
my @command_line = (
|
||||
'-R' . $wgd->root,
|
||||
'-F' . $wgd->config_file,
|
||||
Text::ParseWords::shellwords($command),
|
||||
);
|
||||
|
||||
return WGDev::Command->run(@command_line);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
|
||||
my @commands = @{ $self->{commands} };
|
||||
my $force = $self->option('force');
|
||||
if (! @commands ) {
|
||||
@commands = (command_print => []);
|
||||
}
|
||||
|
||||
my $root = $self->wgd->root;
|
||||
SITES: for my $config ( $self->wgd->list_site_configs ) {
|
||||
my $wgd = eval { WGDev->new( $root, $config ) };
|
||||
if ( $wgd ) {
|
||||
##no critic (ProhibitCStyleForLoops ProhibitLocalVars)
|
||||
local $self->{wgd} = $wgd;
|
||||
COMMANDS: for (my $i = 0; $i <= $#commands; $i += 2) {
|
||||
my $command = $commands[$i];
|
||||
my @params = @{ $commands[$i + 1] };
|
||||
my $success = eval {
|
||||
$self->$command(@params) || 1;
|
||||
};
|
||||
if ( $success ) {
|
||||
# nothing
|
||||
}
|
||||
elsif ( $force ) {
|
||||
warn $@;
|
||||
}
|
||||
else {
|
||||
WGDev::X->inflate($@);
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ($force) {
|
||||
warn $@;
|
||||
}
|
||||
else {
|
||||
WGDev::X->inflate($@);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd for-each [ --print0 | --exec=command ] [ -f ]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Runs a command for each available WebGUI config file. By default,
|
||||
the names of the config files will be output.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-f> C<--force>
|
||||
|
||||
Continue processing config files if there is an error
|
||||
|
||||
=item C<-0> C<--print0[=format]>
|
||||
|
||||
Prints the config file name followed by an ASCII C<NUL> character
|
||||
instead of a carriage return.
|
||||
|
||||
An optional L<perlfunc/sprintf> formatting string can be specified.
|
||||
|
||||
=item C<-p> C<--print[=format]>
|
||||
|
||||
Prints the config file name. This is the default option if no other
|
||||
options are specified.
|
||||
|
||||
An optional L<perlfunc/sprintf> formatting string can be specified.
|
||||
|
||||
=item C<-e> C<--exec=>
|
||||
|
||||
Runs the given command using the shell for each config file. The
|
||||
WEBGUI_ROOT and WEBGUI_CONFIG environment variables will be set
|
||||
while this command is run.
|
||||
|
||||
=item C<-w> C<-c> C<--wgd=>
|
||||
|
||||
Runs the given WGDev command for each config file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
115
lib/WGDev/Command/Group.pm
Normal file
115
lib/WGDev/Command/Group.pm
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package WGDev::Command::Group;
|
||||
# ABSTRACT: Utilities for manipulating WebGUI Groups
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
list|l
|
||||
format|f=s
|
||||
long
|
||||
hidden
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $session = $wgd->session();
|
||||
|
||||
if ( $self->option('list') ) {
|
||||
my $format = $self->option('format');
|
||||
if ( $self->option('long') ) {
|
||||
$format
|
||||
= 'Name: %groupName% %%n Id: %groupId% %%n Description: %description% %%n';
|
||||
}
|
||||
elsif ( !$format ) {
|
||||
$format = '%groupName%';
|
||||
}
|
||||
my $show_in_forms = $self->option('hidden');
|
||||
my $group_ids = $session->db->buildArrayRef(
|
||||
'select groupId from groups order by groupName');
|
||||
for my $group_id ( @{$group_ids} ) {
|
||||
my $group = WebGUI::Group->new( $session, $group_id );
|
||||
if ( !$group ) {
|
||||
warn "Unable to instantiate group via groupId: $group_id";
|
||||
next;
|
||||
}
|
||||
next if !$show_in_forms && !$group->showInForms;
|
||||
|
||||
my $output = $self->format_output( $format, $group );
|
||||
print $output . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub format_output {
|
||||
my ( $self, $format, $group ) = @_;
|
||||
$format =~ s/%%n/\n/msxg;
|
||||
{
|
||||
no warnings 'uninitialized';
|
||||
$format =~ s{% (?: (\w+) (?: :(-?\d+) )? )? %}{
|
||||
my $replace;
|
||||
if ($1) {
|
||||
$replace = $group->get($1);
|
||||
if ($2) {
|
||||
$replace = sprintf('%*2$s', $replace, $2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$replace = '%';
|
||||
}
|
||||
$replace;
|
||||
}msxeg;
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd group [--list [--long] [--hidden]]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Utilities for manipulating WebGUI Groups
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-l> C<--list>
|
||||
|
||||
List groups. This is currently the only supported action.
|
||||
|
||||
=item C<--long>
|
||||
|
||||
Use long list format, which includes group name, ID, and description.
|
||||
|
||||
=item C<-f> C<--format=>
|
||||
|
||||
Use arbitrary formatting. Format looks like C<%description:30%>, where 'C<description>' is
|
||||
the field to display, and 30 is the length to left pad/cut to. Negative
|
||||
lengths can be specified for right padding. Percent signs can be included by
|
||||
using C<%%>. Newlines can be included by using C<%%n>
|
||||
|
||||
=item C<--hidden>
|
||||
|
||||
Include groups that are normally hidden from WebGUI forms.
|
||||
|
||||
=back
|
||||
|
||||
=method C<format_output ( $format, $group )>
|
||||
|
||||
Returns the formatted information about a group. C<$format> is
|
||||
the format to output as specified in the L<format option|/-f>.
|
||||
|
||||
=cut
|
||||
|
||||
70
lib/WGDev/Command/Guid.pm
Normal file
70
lib/WGDev/Command/Guid.pm
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package WGDev::Command::Guid;
|
||||
# ABSTRACT: Generates GUIDs via WebGUI's $session->id->generate API
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
number|n=i
|
||||
dashes!
|
||||
toHex
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $session = $wgd->session();
|
||||
my $id = $session->id;
|
||||
|
||||
if ( $self->option('toHex') ) {
|
||||
foreach my $guid ( $self->arguments ) {
|
||||
printf "%s : %s\n", $guid, $id->toHex($guid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
my $number = $self->option('number') || 1;
|
||||
$self->set_option_default( dashes => 1 );
|
||||
|
||||
for ( 1 .. $number ) {
|
||||
my $guid = $id->generate();
|
||||
if ( !$self->option('dashes') && $guid =~ /[-_]/msx ) {
|
||||
redo;
|
||||
}
|
||||
print "$guid\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd guid [-n <quantity>] [--no-dashes]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Generates GUIDs via WebGUI's C<$session->id->generate> API. Optionally
|
||||
excludes GUIDs with dashes (for easy double-click copy/pasting).
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-n> C<--number>
|
||||
|
||||
Number of GUIDs to generate. Defaults to 1.
|
||||
|
||||
=item C<--[no-]dashes>
|
||||
|
||||
Whether or not to filter GUIDs containing dashes (for easy double-click copy/pasting)
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
78
lib/WGDev/Command/Help.pm
Normal file
78
lib/WGDev/Command/Help.pm
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package WGDev::Command::Help;
|
||||
# ABSTRACT: Displays C<perldoc> help for WGDev command
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::Command ();
|
||||
use WGDev::X ();
|
||||
|
||||
sub needs_root {
|
||||
return;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my ($command) = $self->arguments;
|
||||
if ( !defined $command ) {
|
||||
print WGDev::Command->usage(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $command_module;
|
||||
if ( $command eq 'wgd' ) {
|
||||
$command_module = 'WGDev::Command';
|
||||
}
|
||||
else {
|
||||
$command_module = WGDev::Command->get_command_module($command);
|
||||
}
|
||||
|
||||
if ( !$command_module ) {
|
||||
WGDev::X::CommandLine::BadCommand->throw(
|
||||
usage => $self->usage,
|
||||
command_name => $command,
|
||||
);
|
||||
}
|
||||
|
||||
if ( $command_module->can('help') ) {
|
||||
return $command_module->help;
|
||||
}
|
||||
|
||||
require WGDev::Help;
|
||||
WGDev::Help::package_perldoc( $command_module,
|
||||
'!AUTHOR|LICENSE|METHODS|SUBROUTINES' );
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd help <command>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Displays C<perldoc> page for WGDev command.
|
||||
|
||||
More or less equivalent to running
|
||||
|
||||
wgd command --help
|
||||
|
||||
Except that the help message is displayed via Pod::Perldoc
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<< <command> >>
|
||||
|
||||
The sub-command to display help information about.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
79
lib/WGDev/Command/Import.pm
Normal file
79
lib/WGDev/Command/Import.pm
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package WGDev::Command::Import;
|
||||
# ABSTRACT: Import assets from files
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
|
||||
my $wgd_asset = $self->wgd->asset;
|
||||
my $version_tag;
|
||||
for my $asset_file ( $self->arguments ) {
|
||||
open my $fh, '<:encoding(UTF-8)', $asset_file or next;
|
||||
my $asset_text = do { local $/; <$fh> };
|
||||
close $fh or next;
|
||||
$version_tag ||= do {
|
||||
require WebGUI::VersionTag;
|
||||
my $vt = WebGUI::VersionTag->getWorking( $self->wgd->session );
|
||||
$vt->set( { name => 'WGDev Asset Import' } );
|
||||
$vt;
|
||||
};
|
||||
my $asset_data = $wgd_asset->deserialize($asset_text);
|
||||
my $parent;
|
||||
if ( $asset_data->{parent} ) {
|
||||
$parent = eval { $wgd_asset->find( $asset_data->{parent} ) };
|
||||
}
|
||||
my $asset;
|
||||
my $mode;
|
||||
|
||||
if ( eval { $asset = $wgd_asset->by_id( $asset_data->{assetId} ) } ) {
|
||||
$mode = 'Updating';
|
||||
$asset->addRevision( $asset_data, undef,
|
||||
{ skipAutoCommitWorkflows => 1, skipNotification => 1 } );
|
||||
if ( $asset_data->{parent} ) {
|
||||
if ($parent) {
|
||||
$asset->setParent($parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$mode = 'Adding';
|
||||
$parent ||= $wgd_asset->import_node;
|
||||
$asset = $parent->addChild( $asset_data, $asset_data->{assetId},
|
||||
undef,
|
||||
{ skipAutoCommitWorkflows => 1, skipNotification => 1 } );
|
||||
}
|
||||
printf "%8s: %-30s (%22s) %s\n", $mode,
|
||||
$asset->get('url'), $asset->getId, $asset->get('title');
|
||||
}
|
||||
if ($version_tag) {
|
||||
$version_tag->commit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd import <asset file> [<asset file> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Imports asset from files.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<< <asset file> >>
|
||||
|
||||
File to import.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
108
lib/WGDev/Command/Intro.pm
Normal file
108
lib/WGDev/Command/Intro.pm
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package WGDev::Command::Intro;
|
||||
# ABSTRACT: Introduction to WGDev
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub needs_root {
|
||||
return;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
return $self->help;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd edit default_article
|
||||
wgd package home
|
||||
wgd reset --build
|
||||
wgd reset --dev
|
||||
wgd db
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
WGDev provides a variety of commands useful for WebGUI developers.
|
||||
|
||||
=head1 GETTING STARTED
|
||||
|
||||
The first step in using WGDev is getting it to find your WebGUI
|
||||
root directory and site config file. For this, you can either use
|
||||
the C<WEBGUI_ROOT> and C<WEBGUI_CONFIG>/C<WEBGUI_SITENAME> environment
|
||||
variables, setting the C<command.webgui_root> and
|
||||
C<command.webgui_config>/C<command.webgui_sitename> options via the
|
||||
L<config command|WGDev::Command::Config>, using command line
|
||||
parameters (see C<wgd help>), or (for the root path) relying on
|
||||
auto-detection.
|
||||
|
||||
Auto-detection works by searching upward from the current directory
|
||||
for a valid WebGUI directory. The config file cannot be detected
|
||||
and must be specified.
|
||||
|
||||
The WebGUI config file can be specified relative to the current
|
||||
directory, relative to WebGUI's etc directory, or as an absolute
|
||||
path.
|
||||
|
||||
Once you have the root and config file set or otherwise specified,
|
||||
you can use any of the WGDev commands.
|
||||
|
||||
=head1 GETTING HELP
|
||||
|
||||
A summary of a command's options is available by running the command
|
||||
with the C<--help> option. Full documentation is available using
|
||||
the C<wgd help> command. A full list of available commands is
|
||||
available by running C<wgd commands>.
|
||||
|
||||
=head1 SPECIFYING ASSETS
|
||||
|
||||
When specifying assets as parameters to commands, either an asset
|
||||
URL or an asset ID can be specified. Some commands will also accept
|
||||
a class name, treating it as an new asset of that type.
|
||||
|
||||
=head1 COMMON COMMANDS
|
||||
|
||||
=head2 C<< wgd edit <asset> >>
|
||||
|
||||
Edits the specified asset in your prefered text editor. When you
|
||||
exit the editor, the asset on the WebGUI site will be updated with
|
||||
the new data. Multiple assets can be specified.
|
||||
|
||||
=head2 C<< wgd package <asset> >>
|
||||
|
||||
The package command will generate a package for asset specified.
|
||||
Additionally, the --import option allows you to import package
|
||||
files, and --upgrade will export a package and put it into the correct
|
||||
package directory for the next WebGUI release. Multiple assets can
|
||||
be specified.
|
||||
|
||||
=head2 C<wgd reset --dev>
|
||||
|
||||
Resets a site to its defaults and sets it up for development. The
|
||||
site started is disabled, leaving the admin login with the default
|
||||
password of C<123qwe>. Additionally, all of the default example
|
||||
content is cleared from the site giving you a blank slate to work
|
||||
from.
|
||||
|
||||
=head2 C<wgd reset --build>
|
||||
|
||||
Resets a site it its defaults and prepares it to generate a site
|
||||
creation script. The site starter is enabled, and old version tags
|
||||
and revisions of content are cleaned up.
|
||||
|
||||
=head2 C<wgd db>
|
||||
|
||||
Starts the C<mysql> client in the site's database, using the login
|
||||
information from the site config file.
|
||||
|
||||
=head2 C<< wgd export <asset> >>
|
||||
|
||||
Exports assets to files. You can export to standard out by using
|
||||
the C<--stdout> option. Multiple assets can be specified.
|
||||
|
||||
=cut
|
||||
|
||||
224
lib/WGDev/Command/Ls.pm
Normal file
224
lib/WGDev/Command/Ls.pm
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package WGDev::Command::Ls;
|
||||
# ABSTRACT: List WebGUI assets
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
format|f=s
|
||||
long|l
|
||||
recursive|r
|
||||
excludeClass=s@
|
||||
includeOnlyClass=s@
|
||||
limit=n
|
||||
isa=s
|
||||
filter=s
|
||||
);
|
||||
}
|
||||
|
||||
sub option_filter {
|
||||
my $self = shift;
|
||||
my $filter = shift;
|
||||
|
||||
my ( $filter_prop, $filter_sense, $filter_match )
|
||||
= $filter =~ m{%(\w+)% \s* ([~!])~ \s* (.*)}msx;
|
||||
if ( !defined $filter_prop
|
||||
|| !defined $filter_sense
|
||||
|| !defined $filter_match )
|
||||
{
|
||||
WGDev::X->throw("Invalid filter specified: $filter");
|
||||
}
|
||||
if ( $filter_match =~ m{\A/(.*)/\Z}msx ) {
|
||||
eval { $filter_match = qr/$1/msx; }
|
||||
|| WGDev::X->throw(
|
||||
"Specified filter is not a valid regular expression: $1");
|
||||
}
|
||||
else {
|
||||
$filter_match = qr/\A\Q$filter_match\E\z/msx;
|
||||
}
|
||||
$self->{filter_property} = $filter_prop;
|
||||
$self->{filter_sense} = $filter_sense eq q{~};
|
||||
$self->{filter_match} = $filter_match;
|
||||
return;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $format = $self->option('format');
|
||||
if ( $self->option('long') ) {
|
||||
$format = '%assetId% %url:-35% %title%';
|
||||
}
|
||||
elsif ( !$format ) {
|
||||
$format = '%url%';
|
||||
}
|
||||
|
||||
my $relatives = $self->option('recursive') ? 'descendants' : 'children';
|
||||
my @parents = $self->arguments;
|
||||
my $show_header = @parents > 1;
|
||||
my $exclude_classes = $self->option('excludeClass');
|
||||
my $include_only_classes = $self->option('includeOnlyClass');
|
||||
my $limit = $self->option('limit');
|
||||
my $isa = $self->option('isa');
|
||||
|
||||
my $error;
|
||||
PARENT:
|
||||
while ( my $parent = shift @parents ) {
|
||||
my $asset;
|
||||
if ( !eval { $asset = $wgd->asset->find($parent) } ) {
|
||||
warn "wgd ls: $parent: No such asset\n";
|
||||
$error++;
|
||||
next;
|
||||
}
|
||||
if ($show_header) {
|
||||
print "$parent:\n";
|
||||
}
|
||||
my $child_iter = $asset->getLineageIterator(
|
||||
[$relatives],
|
||||
{
|
||||
$exclude_classes ? ( excludeClasses => $exclude_classes )
|
||||
: (),
|
||||
$include_only_classes
|
||||
? ( includeOnlyClasses => $include_only_classes )
|
||||
: (),
|
||||
defined $limit
|
||||
&& !defined $self->{filter_match} ? ( limit => $limit )
|
||||
: (),
|
||||
$isa ? ( isa => $isa ) : (),
|
||||
} );
|
||||
while ( my $child = $child_iter->() ) {
|
||||
next
|
||||
if !$self->pass_filter($child);
|
||||
|
||||
# Handle limit ourselves because smartmatch filtering happens
|
||||
# *after* getLineage returns its results
|
||||
last PARENT
|
||||
if defined $limit && $limit-- <= 0;
|
||||
|
||||
my $output = $self->format_output( $format, $child );
|
||||
print $output . "\n";
|
||||
}
|
||||
if (@parents) {
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
return (! $error);
|
||||
}
|
||||
|
||||
sub pass_filter {
|
||||
my ( $self, $asset ) = @_;
|
||||
my $filter_prop = $self->{filter_property};
|
||||
my $filter_sense = $self->{filter_sense};
|
||||
my $filter_match = $self->{filter_match};
|
||||
|
||||
return 1
|
||||
if !defined $filter_match;
|
||||
|
||||
{
|
||||
no warnings 'uninitialized';
|
||||
if ($filter_sense) {
|
||||
return $asset->get($filter_prop) =~ $filter_match;
|
||||
}
|
||||
else {
|
||||
return $asset->get($filter_prop) !~ $filter_match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub format_output {
|
||||
my ( $self, $format, $asset ) = @_;
|
||||
{
|
||||
no warnings 'uninitialized';
|
||||
$format =~ s{% (?: (\w+) (?: :(-?\d+) )? )? %}{
|
||||
my $replace;
|
||||
if ($1) {
|
||||
$replace = $asset->get($1);
|
||||
if ($2) {
|
||||
$replace = sprintf('%*2$s', $replace, $2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$replace = '%';
|
||||
}
|
||||
$replace;
|
||||
}msxeg;
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd ls [-l] [--format=<format>] [-r] <asset> [<asset> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Lists children of WebGUI assets
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-l> C<--long>
|
||||
|
||||
Use long list format, which includes asset ID, URL, and title.
|
||||
|
||||
=item C<-f> C<--format=>
|
||||
|
||||
Use arbitrary formatting. Format looks like C<%url:30%>, where 'C<url>' is
|
||||
the field to display, and 30 is the length to left pad/cut to. Negative
|
||||
lengths can be specified for right padding. Percent signs can be included by
|
||||
using C<%%>.
|
||||
|
||||
=item C<-r> C<--recursive>
|
||||
|
||||
Recursively list all descendants (by default we only list children).
|
||||
|
||||
=item C<--includeOnlyClass=>
|
||||
|
||||
Specify one or more times to limit the results to a certain set of asset classes.
|
||||
|
||||
=item C<--excludeClass=>
|
||||
|
||||
Specify one or more times to filter out certain asset class(es) from the results.
|
||||
|
||||
=item C<--limit=>
|
||||
|
||||
The maximum amount of entries to return
|
||||
|
||||
=item C<--isa=>
|
||||
|
||||
A class name where you can look for classes of a similar base class.
|
||||
For example, if you're looking for Donations, Subscriptions, Products
|
||||
and other subclasses of L<WebGUI::Asset::Sku>, then specify the
|
||||
parameter C<--isa=WebGUI::Asset::Sku>.
|
||||
|
||||
=item C<--filter=>
|
||||
|
||||
Apply smart match filtering against the results. Format looks like
|
||||
C<%url% ~~ smartmatch>, where C<url> is the field to filter against,
|
||||
and C<smartmatch> is either a Perl regular expression such as
|
||||
C</(?i:partial_match)/> or a string such as C<my_exact_match>.
|
||||
|
||||
=back
|
||||
|
||||
=method C<format_output ( $format, $asset )>
|
||||
|
||||
Returns the formatted information about an asset. C<$format> is
|
||||
the format to output as specified in the L<format option|/-f>.
|
||||
|
||||
=method C<option_filter ( $filter )>
|
||||
|
||||
Takes a filter specification, verifies that it is specified properly, and saves it.
|
||||
|
||||
=method C<pass_filter ( $asset )>
|
||||
|
||||
Checks if a given asset passes the saved filter. Returns true or false.
|
||||
|
||||
=cut
|
||||
|
||||
168
lib/WGDev/Command/Mail.pm
Normal file
168
lib/WGDev/Command/Mail.pm
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package WGDev::Command::Mail;
|
||||
# ABSTRACT: Sends emails via the WebGUI::Mail::Send API
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
use Carp;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
list|l
|
||||
delete
|
||||
processQueue
|
||||
queue|q
|
||||
toUser=s
|
||||
toGroup=s
|
||||
subject|s=s
|
||||
from=s
|
||||
cc=s
|
||||
bcc=s
|
||||
replyTo=s
|
||||
returnPath=s
|
||||
contentType=s
|
||||
messageId=s
|
||||
inReplyTo=s
|
||||
isInbox
|
||||
verbose|v
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $verbose = $self->option('verbose');
|
||||
|
||||
# Handle special cases
|
||||
if ( !$self->arguments ) {
|
||||
my $dbh = $wgd->db->connect;
|
||||
my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM mailQueue');
|
||||
print "Mail queue has @{[ $count || 'no' ]} message(s).\n";
|
||||
|
||||
if ( $self->option('list') ) {
|
||||
for my $message (
|
||||
@{ $dbh->selectcol_arrayref('SELECT message FROM mailQueue') }
|
||||
)
|
||||
{
|
||||
print $message . "\n";
|
||||
}
|
||||
}
|
||||
elsif ( $self->option('delete') ) {
|
||||
$dbh->do('DELETE FROM mailQueue');
|
||||
print "Deleted all messages from mail queue.\n";
|
||||
}
|
||||
elsif ( $self->option('processQueue') ) {
|
||||
my $WORKFLOW_ID = 'pbworkflow000000000007';
|
||||
my $found
|
||||
= $dbh->selectrow_array(
|
||||
'SELECT count(*) FROM Workflow WHERE workflowId = ?',
|
||||
{}, $WORKFLOW_ID, );
|
||||
if ( !$found ) {
|
||||
WGDev::X->throw(
|
||||
q{The default "Send Queued Email Messages" Workflow was not found,}
|
||||
. q{ unable to run.} );
|
||||
}
|
||||
require WebGUI::Workflow::Instance;
|
||||
my $session = $wgd->session;
|
||||
WebGUI::Workflow::Instance->create( $session,
|
||||
{ workflowId => $WORKFLOW_ID, } )->start;
|
||||
print
|
||||
qq{Triggered Workflow, the mail queue should be being processed as we speak.\n};
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $session = $wgd->session;
|
||||
my $to = join q{,}, $self->arguments;
|
||||
my $body;
|
||||
while ( my $line = <STDIN> ) {
|
||||
last if $line eq ".\n";
|
||||
$body .= $line;
|
||||
}
|
||||
|
||||
# We are going to pass pretty much all options into WebGUI::Mail::Send::create
|
||||
my $options = $self->{options};
|
||||
$options->{to} = $to;
|
||||
|
||||
# Pull out the non-api options (or short-hands)
|
||||
if ( my $s = delete $options->{s} ) {
|
||||
$options->{subject} = $s;
|
||||
}
|
||||
my $queue = delete $options->{q} || delete $options->{queue};
|
||||
|
||||
if ($verbose) {
|
||||
print $queue ? 'Queueing' : 'Sending', " message:\n";
|
||||
print $body;
|
||||
print "Using the following options:\n";
|
||||
print Data::Dumper::Dumper( $self->{options} );
|
||||
print 'SMTP Server: ' . $session->setting->get('smtpServer') . "\n";
|
||||
print "emailToLog: 1\n" if $session->config->get('emailToLog');
|
||||
}
|
||||
require WebGUI::Mail::Send;
|
||||
my $msg = WebGUI::Mail::Send->create( $session, $options );
|
||||
WGDev::X->throw('Unable to instantiate message') unless $msg;
|
||||
|
||||
$msg->addText($body);
|
||||
|
||||
my $status;
|
||||
if ($queue) {
|
||||
$status = $msg->queue;
|
||||
}
|
||||
else {
|
||||
$status = $msg->send;
|
||||
}
|
||||
print "Status: $status\n" if $verbose;
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd mail
|
||||
wgd mail -s test pat@patspam.com
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Sends emails via the L<WebGUI::Mail::Send> API
|
||||
|
||||
If run with no arguments, displays the number of messages currently
|
||||
in the mail queue.
|
||||
|
||||
Accepts all options supported by
|
||||
L<WebGUI::Mail::Send::create|WebGUI::Mail::Send/create>, plus the
|
||||
following additional items:
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-l> C<--list>
|
||||
|
||||
List (print) the raw contents of the mail queue.
|
||||
|
||||
=item C<--delete>
|
||||
|
||||
Delete the contents of the mail queue.
|
||||
|
||||
=item C<--processQueue>
|
||||
|
||||
Trigger the default "Send Queued Email Messages" Workflow. This
|
||||
will send all of the messages in the mail queue.
|
||||
|
||||
=item C<-q> C<--queue>
|
||||
|
||||
Add the message to the queue rather than sending it immediately.
|
||||
|
||||
=item C<-s>
|
||||
|
||||
Short-hand for C<--subject>.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
195
lib/WGDev/Command/Optimize.pm
Normal file
195
lib/WGDev/Command/Optimize.pm
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
package WGDev::Command::Optimize;
|
||||
# ABSTRACT: Scans your site and suggests various optimizations
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base::Verbosity);
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
assets
|
||||
macros
|
||||
db
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
if ( $self->option('assets') ) {
|
||||
$self->optimise_assets();
|
||||
}
|
||||
|
||||
if ( $self->option('macros') ) {
|
||||
$self->optimise_macros();
|
||||
}
|
||||
|
||||
if ( $self->option('db') ) {
|
||||
$self->optimise_db();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub optimise_assets {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $session = $wgd->session();
|
||||
|
||||
my @assets;
|
||||
for my $asset ( sort keys %{ $session->config->get('assets') } ) {
|
||||
if (
|
||||
!$session->db->quickScalar(
|
||||
'select count(*) from asset where className = ?', [$asset] ) )
|
||||
{
|
||||
push @assets, $asset;
|
||||
}
|
||||
}
|
||||
|
||||
if (@assets) {
|
||||
$self->report(
|
||||
"The following Assets do not appear in your Asset table:\n");
|
||||
for my $asset (@assets) {
|
||||
$self->report("\t$asset\n");
|
||||
}
|
||||
my $config = $wgd->config_file();
|
||||
my $message = <<"END_MESSAGE";
|
||||
If you are sure any of these Assets are not being used on your site,
|
||||
you can reduce memory usage by removing them from the "assets" section of
|
||||
your site config file, which is located at:
|
||||
\t$config
|
||||
|
||||
Keep in mind:
|
||||
*) Some assets such as FilePile will not appear in your Assets table but
|
||||
are still used to provide funcitonality (in the case of FilePile
|
||||
providing a way for users to upload multiple Files).
|
||||
END_MESSAGE
|
||||
$self->report("$message");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub optimise_macros {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $session = $wgd->session();
|
||||
|
||||
my @macros;
|
||||
for my $macro ( sort keys %{ $session->config->get('macros') } ) {
|
||||
if (
|
||||
!$session->db->quickScalar(
|
||||
'select count(*) from template where template like ? or template like ?',
|
||||
[ "%^$macro;%", "%^$macro(%" ] ) )
|
||||
{
|
||||
push @macros, $macro;
|
||||
}
|
||||
}
|
||||
|
||||
if (@macros) {
|
||||
my $macros = join q{}, map {"\t$_\n"} @macros;
|
||||
my $config = $wgd->config_file();
|
||||
my $message = <<"END_MESSAGE";
|
||||
The following Macros do not appear in the template field of the template table:
|
||||
$macros
|
||||
|
||||
If you are sure any of these Macros are not being used on your site,
|
||||
you can reduce memory usage by removing them from the "macros" section of
|
||||
your site config file, which is located at:
|
||||
\t$config
|
||||
|
||||
Keep in mind:
|
||||
*) Macros can be references from lots of places other then just Templates,
|
||||
for example the mailFooter setting in the Settings table
|
||||
END_MESSAGE
|
||||
$self->report($message);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
use constant OPTIMIZE_TABLES_LIMIT => 10;
|
||||
|
||||
sub optimise_db {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $session = $wgd->session();
|
||||
|
||||
my $sth = $session->db->read('show table status');
|
||||
|
||||
my @tables;
|
||||
while ( my $r = $sth->hashRef ) {
|
||||
push @tables, [ $r->{Name}, $r->{Data_length}, $r->{Rows} ];
|
||||
}
|
||||
|
||||
$self->report("Top 10 Tables, sorted by Data_length\n");
|
||||
my $ctr;
|
||||
for my $table ( sort { $b->[1] <=> $a->[1] } @tables ) {
|
||||
## no critic (ProhibitParensWithBuiltins)
|
||||
$self->report( sprintf( "%10d\t%s\n", $table->[1], $table->[0] ) );
|
||||
last
|
||||
if ++$ctr == OPTIMIZE_TABLES_LIMIT;
|
||||
}
|
||||
$self->report("\n");
|
||||
|
||||
$self->report("Top 10 Tables, sorted by Rows\n");
|
||||
$ctr = 0;
|
||||
for my $table ( sort { $b->[2] <=> $a->[2] } @tables ) {
|
||||
## no critic (ProhibitParensWithBuiltins)
|
||||
$self->report( sprintf( "%10d\t%s\n", $table->[2], $table->[0] ) );
|
||||
last
|
||||
if ++$ctr == OPTIMIZE_TABLES_LIMIT;
|
||||
}
|
||||
$self->report("\n");
|
||||
|
||||
$self->report(<<'END_ADVICE');
|
||||
To reduce row count, you may want to investigate deleting old/unused data.
|
||||
To reduce row size, apart from deleting rows, you might want to investigate mysql's "optimize table" command.
|
||||
END_ADVICE
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd optimize [--assets] [--macros]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Scans your site and suggests various optimizations
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--assets>
|
||||
|
||||
Suggests Assets that you might be able to disable to reduce memory consumption
|
||||
|
||||
=item C<--macros>
|
||||
|
||||
Suggests Macros that you might be able to disable to reduce memory consumption
|
||||
|
||||
=item C<--db>
|
||||
|
||||
Suggests database tables that may be able to be adjusted to increase speed.
|
||||
|
||||
=back
|
||||
|
||||
=method C<optimise_assets>
|
||||
|
||||
Suggests Assets that you might be able to disable to reduce memory consumption
|
||||
|
||||
=method C<optimise_macros>
|
||||
|
||||
Suggests Macros that you might be able to disable to reduce memory consumption
|
||||
|
||||
=method C<optimise_db>
|
||||
|
||||
Suggests database tables that may be able to be adjusted to increase speed.
|
||||
|
||||
=cut
|
||||
|
||||
161
lib/WGDev/Command/Package.pm
Normal file
161
lib/WGDev/Command/Package.pm
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package WGDev::Command::Package;
|
||||
# ABSTRACT: Export assets for upgrade
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use File::Spec ();
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
import|i=s@
|
||||
parent=s
|
||||
overwrite
|
||||
|
||||
upgrade|u
|
||||
to=s
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
require File::Copy;
|
||||
if ( $self->arguments ) {
|
||||
my $package_dir = $self->option('to') || q{.};
|
||||
if ( $self->option('upgrade') ) {
|
||||
my $version = $wgd->version->module;
|
||||
my $wg8 = $version =~ /^8[.]/msx;
|
||||
if ($wg8) {
|
||||
require WebGUI::Paths;
|
||||
my $old_version = $wgd->version->db_script;
|
||||
$package_dir = File::Spec->catdir( WebGUI::Paths->upgrades,
|
||||
$old_version . q{-} . $version );
|
||||
}
|
||||
else {
|
||||
$package_dir = File::Spec->catdir( $wgd->root, 'docs', 'upgrades',
|
||||
'packages-' . $wgd->version->module );
|
||||
}
|
||||
if ( !-d $package_dir ) {
|
||||
mkdir $package_dir;
|
||||
}
|
||||
}
|
||||
if ( !-d $package_dir ) {
|
||||
WGDev::X::IO->throw(
|
||||
error => 'Directory does not exist',
|
||||
path => $package_dir
|
||||
);
|
||||
}
|
||||
for my $asset_spec ( $self->arguments ) {
|
||||
my $asset = eval { $wgd->asset->find($asset_spec) } || do {
|
||||
warn "Unable to find asset $asset_spec!\n";
|
||||
next;
|
||||
};
|
||||
|
||||
my $storage = $asset->exportPackage;
|
||||
my $filename = $storage->getFiles->[0];
|
||||
my $filepath = $storage->getPath($filename);
|
||||
File::Copy::copy( $filepath,
|
||||
File::Spec->catfile( $package_dir, $filename ) );
|
||||
printf "Building package %27s for %27s.\n", $filename,
|
||||
$asset->get('title');
|
||||
}
|
||||
}
|
||||
if ( $self->option('import') ) {
|
||||
my $parent
|
||||
= $self->option('parent')
|
||||
? eval { $wgd->asset->find( $self->option('parent') ) }
|
||||
: $wgd->asset->import_node;
|
||||
if ( !$parent ) {
|
||||
warn "Unable to find parent node!\n";
|
||||
return 0;
|
||||
}
|
||||
require WebGUI::Storage;
|
||||
require WebGUI::VersionTag;
|
||||
|
||||
my $version_tag = WebGUI::VersionTag->getWorking( $wgd->session );
|
||||
$version_tag->set( { name => 'WGDev package import' } );
|
||||
my $import_options = {};
|
||||
if ($self->option('overwrite')) {
|
||||
$import_options->{'overwriteLatest'} = 1;
|
||||
}
|
||||
for my $package ( @{ $self->option('import') } ) {
|
||||
my $storage = WebGUI::Storage->createTemp( $wgd->session );
|
||||
$storage->addFileFromFilesystem($package);
|
||||
my $asset = $parent->importPackage($storage, $import_options);
|
||||
if ( ! ref $asset ) {
|
||||
# importPackage returns a string for errors (ugh)
|
||||
WGDev::X::BadPackage->throw(
|
||||
package => $package,
|
||||
message => $asset,
|
||||
);
|
||||
}
|
||||
elsif ( ! eval { $asset->isa('WebGUI::Asset') } ) {
|
||||
# not an asset or an error? this shouldn't ever happen.
|
||||
WGDev::X::BadPackage->throw(
|
||||
package => $package,
|
||||
message => 'Strange result from package import: '
|
||||
. ref($asset),
|
||||
);
|
||||
}
|
||||
print "Imported '$package' to " . $asset->get('url') . "\n";
|
||||
}
|
||||
$version_tag->commit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd package [--to=<dir>] [--upgrade] [<asset> ...]
|
||||
wgd package [--parent=<asset>] [--import=<package file>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Exports or imports assets as packages, optionally placing them in the current
|
||||
upgrade path.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Assets specified as standalone arguments are exported as packages.
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-i> C<--import=>
|
||||
|
||||
Package file (or files) to import. Will be imported to the import node if no
|
||||
other parent is specified.
|
||||
|
||||
=item C<--overwrite>
|
||||
|
||||
Forces the assets in this package to be the latest version on the
|
||||
site. This option only works in conjunction with C<--import> and
|
||||
requires WebGUI 7.8.1 or higher.
|
||||
|
||||
=item C<--parent=>
|
||||
|
||||
Specify the parent asset to import packages into.
|
||||
|
||||
=item C<-u> C<--upgrade>
|
||||
|
||||
If specified, packages will be exported to the directory for the upgrade to
|
||||
the current local version.
|
||||
|
||||
=item C<--to=>
|
||||
|
||||
Specify a directory to output the package files to. If neither C<--upgrade>
|
||||
or C<--to> is specified, packages will be output to the current directory.
|
||||
|
||||
=item C<< <asset> >>
|
||||
|
||||
Either an asset ID or an asset URL to specify an asset.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1155
lib/WGDev/Command/Reset.pm
Normal file
1155
lib/WGDev/Command/Reset.pm
Normal file
File diff suppressed because it is too large
Load diff
40
lib/WGDev/Command/Run.pm
Normal file
40
lib/WGDev/Command/Run.pm
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package WGDev::Command::Run;
|
||||
# ABSTRACT: Run arbitrary shell command
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
$self->wgd->set_environment;
|
||||
my @arguments = $self->arguments;
|
||||
my $command = shift @arguments;
|
||||
my $result = system {$command} $command, @arguments;
|
||||
return $result ? 0 : 1;
|
||||
}
|
||||
|
||||
sub parse_params {
|
||||
my ( $self, @args ) = @_;
|
||||
$self->arguments( \@args );
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd run <command>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Runs an arbitrary command, but sets the C<WEBGUI_CONFIG>, C<WEBGUI_ROOT>, and
|
||||
C<PERL5LIB> environment variables first.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Has no options of its own. All options are passed on to specified command.
|
||||
|
||||
=cut
|
||||
|
||||
73
lib/WGDev/Command/Self/Upgrade.pm
Normal file
73
lib/WGDev/Command/Self/Upgrade.pm
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package WGDev::Command::Self::Upgrade;
|
||||
# ABSTRACT: Upgrade WGDev script
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
use WGDev::X;
|
||||
use WGDev::Command;
|
||||
|
||||
sub needs_root { return }
|
||||
sub config_options { () }
|
||||
|
||||
sub is_runnable {
|
||||
# use the presence of fatpacker to detect single script install
|
||||
# this command is not meant for upgrading module install
|
||||
return $App::WGDev::PACKED;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $file = $0;
|
||||
require File::Temp;
|
||||
require LWP::UserAgent;
|
||||
if (! -w $file) {
|
||||
WGDev::X::IO::Write->throw( path => $file );
|
||||
}
|
||||
my $our_version = WGDev::Command->VERSION;
|
||||
print "Current version: $our_version\n";
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my $res = $ua->get('http://haarg.org/wgd');
|
||||
if (! $res->is_success) {
|
||||
WGDev::X->throw('Unable to download new version');
|
||||
}
|
||||
my $content = $res->decoded_content;
|
||||
my $new_version = do {
|
||||
my $temp_script = File::Temp->new;
|
||||
$temp_script->autoflush(1);
|
||||
print { $temp_script } $content;
|
||||
open my $fh, q{-|}, $^X, q{--}, $temp_script->filename, '-V'
|
||||
or WGDev::X::IO->throw;
|
||||
my $output = do { local $/; <$fh> };
|
||||
close $fh
|
||||
or WGDev::X::IO->throw;
|
||||
my ($script_version) = ($output =~ /(\d[\d.]+)/msx);
|
||||
$script_version;
|
||||
};
|
||||
print "New version: $new_version\n";
|
||||
if ($our_version eq $new_version) {
|
||||
print "Already up to date.\n";
|
||||
return 1;
|
||||
}
|
||||
print "Upgrading.\n";
|
||||
open my $fh, '>', $file
|
||||
or WGDev::X::IO->throw;
|
||||
print { $fh } $content;
|
||||
close $fh
|
||||
or WGDev::X::IO->throw;
|
||||
exec $^X, $file, '-V';
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd self-upgrade
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Upgrades the WGDev script.
|
||||
|
||||
=cut
|
||||
|
||||
75
lib/WGDev/Command/Setting.pm
Normal file
75
lib/WGDev/Command/Setting.pm
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package WGDev::Command::Setting;
|
||||
# ABSTRACT: Returns WebGUI settings from the database.
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
sub config_options {
|
||||
return qw();
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $dbh = $wgd->db->connect;
|
||||
my $sth
|
||||
= $dbh->prepare('SELECT name, value FROM settings WHERE name LIKE ?');
|
||||
my $sth_set;
|
||||
foreach my $argument ( $self->arguments ) {
|
||||
my $new_value;
|
||||
if ( $argument =~ s/=(.*)//msx ) {
|
||||
$new_value = $1;
|
||||
}
|
||||
$sth->execute($argument);
|
||||
while ( my ( $setting, $value ) = $sth->fetchrow_array ) {
|
||||
if ( !defined $value ) {
|
||||
$value = '(NULL)';
|
||||
}
|
||||
if ( defined $new_value ) {
|
||||
$sth_set ||= $dbh->prepare(
|
||||
'UPDATE settings SET value = ? WHERE name = ?');
|
||||
$sth_set->execute( $new_value, $setting );
|
||||
$sth_set->finish;
|
||||
printf "%-39s %s => %s\n", $setting, $value, $new_value;
|
||||
}
|
||||
else {
|
||||
printf "%-39s %s\n", $setting, $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd setting <setting>[=<value>] [<setting> ...]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Prints settings from the WebGUI settings table. This is handy for doing quick lookups,
|
||||
or for using as part of other C<wgd> commands. Can also the the value of settings.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<< <setting> >>
|
||||
|
||||
The name of the setting to display. Can also contain SQL wildcards
|
||||
to show multiple settings. Using a setting of C<%> will display
|
||||
all settings.
|
||||
|
||||
=item C<< <value> >>
|
||||
|
||||
The value to set the setting to. If specified, the old value and
|
||||
new value will be included in the output.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
154
lib/WGDev/Command/Test.pm
Normal file
154
lib/WGDev/Command/Test.pm
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package WGDev::Command::Test;
|
||||
# ABSTRACT: Run WebGUI tests
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use File::Spec ();
|
||||
|
||||
sub config_parse_options { return qw(gnu_getopt pass_through) }
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
all|A
|
||||
slow
|
||||
live|L
|
||||
debug
|
||||
reset:s
|
||||
cover|C:s
|
||||
coverOptions:s
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
require Cwd;
|
||||
require App::Prove;
|
||||
|
||||
my $wgd = $self->wgd;
|
||||
$wgd->set_environment;
|
||||
|
||||
if ( defined $self->option('reset') ) {
|
||||
my $reset_options = $self->option('reset');
|
||||
if ( $reset_options eq q{} ) {
|
||||
$reset_options = '--quiet --backup --delcache --import --upgrade';
|
||||
}
|
||||
require WGDev::Command::Reset;
|
||||
my $reset = WGDev::Command::Reset->new($wgd);
|
||||
$reset->parse_params_string($reset_options);
|
||||
$reset->process;
|
||||
}
|
||||
|
||||
##no critic (RequireLocalizedPunctuationVars)
|
||||
local $ENV{CODE_COP} = 1
|
||||
if $self->option('slow');
|
||||
local $ENV{TEST_SYNTAX} = 1
|
||||
if $self->option('slow');
|
||||
local $ENV{TEST_POD} = 1
|
||||
if $self->option('slow');
|
||||
|
||||
local $ENV{WEBGUI_LIVE} = 1
|
||||
if $self->option('live');
|
||||
|
||||
local $ENV{WEBGUI_TEST_DEBUG} = 1
|
||||
if $self->option('debug');
|
||||
|
||||
local $ENV{HARNESS_PERL_SWITCHES} = $ENV{HARNESS_PERL_SWITCHES};
|
||||
my $cover_dir;
|
||||
if ( defined $self->option('cover') ) {
|
||||
$cover_dir = $self->option('cover') || 'cover_db';
|
||||
if ( -e $cover_dir ) {
|
||||
system 'cover', '-silent', '-delete', $cover_dir;
|
||||
}
|
||||
my $cover_options = $self->option('coverOptions')
|
||||
|| '-select,WebGUI,+ignore,^t';
|
||||
if ( $ENV{HARNESS_PERL_SWITCHES} ) {
|
||||
$ENV{HARNESS_PERL_SWITCHES} .= q{ };
|
||||
}
|
||||
else {
|
||||
$ENV{HARNESS_PERL_SWITCHES} = q{};
|
||||
}
|
||||
$ENV{HARNESS_PERL_SWITCHES} .= '-MDevel::Cover=' . join q{,},
|
||||
-silent => 1,
|
||||
$cover_options, -db => $cover_dir;
|
||||
}
|
||||
|
||||
my $prove = App::Prove->new;
|
||||
my @args = $self->arguments;
|
||||
@args = ( '-r', grep { $_ ne '-r' } @args );
|
||||
my $orig_dir;
|
||||
if ( $self->option('all') ) {
|
||||
$orig_dir = Cwd::cwd();
|
||||
chdir $wgd->root;
|
||||
unshift @args, 't';
|
||||
}
|
||||
$prove->process_args(@args);
|
||||
my $result = $prove->run;
|
||||
if ($orig_dir) {
|
||||
chdir $orig_dir;
|
||||
}
|
||||
if ( defined $cover_dir ) {
|
||||
system 'cover', '-silent', $cover_dir;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd test [-ASCL] [--debug] [<prove options>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Runs WebGUI tests, setting the needed environment variables beforehand.
|
||||
Includes quick options for running all tests, and including slow tests.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Unrecognized options will be passed through to prove.
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-A> C<--all>
|
||||
|
||||
Run all tests recursively. Otherwise, tests will need to be specified.
|
||||
|
||||
=item C<--slow>
|
||||
|
||||
Includes slow tests by defining CODE_COP, TEST_SYNTAX, and TEST_POD.
|
||||
|
||||
=item C<-L> C<--live>
|
||||
|
||||
Includes live tests by defining WEBGUI_LIVE.
|
||||
|
||||
=item C<--debug>
|
||||
|
||||
After a test, output the number of assets, version tags, users, groups, sessions
|
||||
and session scratch variables, to determine when tests leak objects that can interfere
|
||||
with downstream tests.
|
||||
|
||||
This option is really only useful when passing the --verbose switch through to prove.
|
||||
|
||||
=item C<--reset=>
|
||||
|
||||
Perform a site reset before running the tests. The value specified is used
|
||||
as the command line parameters for the L<C<reset> command|WGDev::Command::Reset>.
|
||||
With no value, will use the options C<--delcache --backup --import --upgrade> to do a
|
||||
fast site reset.
|
||||
|
||||
=item C<-C> C<--cover=>
|
||||
|
||||
Run coverage using Devel::Cover. The value specified is used as the directory to
|
||||
put the coverage data and defaults to C<cover_db>.
|
||||
|
||||
=item C<--coverOptions=>
|
||||
|
||||
Options to pass to L<Devel::Cover>. Defaults to C<-select,WebGUI,+ignore,^t>.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
110
lib/WGDev/Command/Trash.pm
Normal file
110
lib/WGDev/Command/Trash.pm
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package WGDev::Command::Trash;
|
||||
# ABSTRACT: Trash assets by URL/assetId
|
||||
use strict;
|
||||
use warnings;
|
||||
use WGDev::Command::Ls;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
purge
|
||||
restore
|
||||
list
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
if ($self->option('list')) {
|
||||
return $self->list_trash;
|
||||
}
|
||||
else {
|
||||
return $self->trash;
|
||||
}
|
||||
}
|
||||
|
||||
sub trash {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $error;
|
||||
my $method = $self->option('purge') ? 'purge'
|
||||
: $self->option('restore') ? 'restore'
|
||||
: 'trash';
|
||||
my @asset_specs = $self->arguments;
|
||||
ASSET:
|
||||
while ( my $asset_spec = shift @asset_specs ) {
|
||||
my $asset;
|
||||
if ( !eval { $asset = $wgd->asset->find($asset_spec) } ) {
|
||||
warn "wgd trash: $asset_spec: No such asset\n";
|
||||
$error++;
|
||||
next ASSET;
|
||||
}
|
||||
my $success = $asset->$method;
|
||||
if ($method ne 'restore' && ! $success) {
|
||||
warn "wgd trash: unable to $method $asset_spec\n";
|
||||
++$error;
|
||||
}
|
||||
}
|
||||
|
||||
return (! $error);
|
||||
}
|
||||
|
||||
sub list_trash {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $root = $wgd->asset->root;
|
||||
my $trashed_assets = $root->getAssetsInTrash();
|
||||
my $ls = WGDev::Command::Ls->new($wgd);
|
||||
my $format = '%assetId% %url:-35% %title%';
|
||||
ASSET:
|
||||
foreach my $asset ( @{ $trashed_assets } ) {
|
||||
next ASSET unless $asset;
|
||||
my $output = $ls->format_output( $format, $asset );
|
||||
print $output . "\n";
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd trash [--purge] [--restore] <asset> [<asset> ...]
|
||||
wgd trash [--list]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Methods for working with assets in the trash.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--purge>
|
||||
|
||||
Purges the assets from the system instead of putting it into the trash.
|
||||
|
||||
=item C<--restore>
|
||||
|
||||
Restores the assets that have been trashed to the regular, published state.
|
||||
|
||||
=item C<--list>
|
||||
|
||||
Lists all assets in the trash.
|
||||
|
||||
=item C<< <asset> >>
|
||||
|
||||
Either an asset URL or an ID. As many can be specified as desired.
|
||||
Prepending with a slash will force it to be interpreted as a URL.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
||||
130
lib/WGDev/Command/User.pm
Normal file
130
lib/WGDev/Command/User.pm
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package WGDev::Command::User;
|
||||
# ABSTRACT: Utilities for manipulating WebGUI Users
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
findByPassword=s
|
||||
findByDictionary=s
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my $session = $wgd->session();
|
||||
|
||||
if ( my $password = $self->option('findByPassword') ) {
|
||||
return $self->find_by_password($password);
|
||||
}
|
||||
|
||||
if ( my $dictionary = $self->option('findByDictionary') ) {
|
||||
return $self->find_by_dictionary($dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
sub find_by_password {
|
||||
my $self = shift;
|
||||
my $password = shift;
|
||||
my $session = $self->wgd->session();
|
||||
|
||||
require Digest::MD5;
|
||||
require Encode;
|
||||
my $hash = Digest::MD5::md5_base64( Encode::encode_utf8($password) );
|
||||
print "Password:\t$password\n";
|
||||
print "Hashes to:\t$hash\n";
|
||||
my @user_ids = $session->db->buildArray(
|
||||
'select userId from authentication where fieldName = ? and fieldData = ?',
|
||||
[ 'identifier', $hash ] );
|
||||
print "Matching users:\t";
|
||||
print @user_ids ? "\n" : "None\n";
|
||||
|
||||
for my $user_id (@user_ids) {
|
||||
my $user = WebGUI::User->new( $session, $user_id );
|
||||
my $username = $user->username;
|
||||
print " * $user_id ($username)\n";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub find_by_dictionary {
|
||||
my $self = shift;
|
||||
my $dict = shift;
|
||||
my $session = $self->wgd->session();
|
||||
|
||||
my @hashed_passwords
|
||||
= $session->db->buildArray(
|
||||
'select fieldData from authentication where fieldName = ?',
|
||||
['identifier'] );
|
||||
my %hashed_passwords = map { $_ => 1 } @hashed_passwords;
|
||||
open my $d, '<', $dict
|
||||
or WGDev::X::IO::Read->throw(
|
||||
path => $dict,
|
||||
message => 'Unable to open dictionary file',
|
||||
);
|
||||
while ( my $word = <$d> ) {
|
||||
chomp $word;
|
||||
my $hash = Digest::MD5::md5_base64( Encode::encode_utf8($word) );
|
||||
if ( $hashed_passwords{$hash} ) {
|
||||
print "\n*** HIT ***\n";
|
||||
$self->find_by_password($word);
|
||||
}
|
||||
}
|
||||
close $d
|
||||
or WGDev::X::IO::Read->throw(
|
||||
path => $dict,
|
||||
message => 'Unable to open dictionary file',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd user [--findByPassword <password>] [--findByDictionary <dictionary>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Utilities for manipulating WebGUI Users
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<--findByPassword>
|
||||
|
||||
Return a list of users that are using the given password (assumes
|
||||
WebGUI authentication module).
|
||||
|
||||
=item C<--findByDictionary>
|
||||
|
||||
Use a dictionary file to do a brute-force search for users using
|
||||
any password in the dictionary (assumes WebGUI authentication
|
||||
module). For example, Linux distributions typically have a dictionary
|
||||
file in C</usr/share/dict/> or C</var/lib/dict/>
|
||||
|
||||
=back
|
||||
|
||||
=method find_by_password
|
||||
|
||||
Hashes the given password and sees if any user IDs in the C<authentication> table
|
||||
match. This check will become less efficient once WebGUI implements password salting.
|
||||
|
||||
=method find_by_dictionary
|
||||
|
||||
Search through the given dictionary file, hashing words one by one and
|
||||
checking them against all known hashed passwords.
|
||||
|
||||
Does not try to be efficient or clever - for common dictionary files it's
|
||||
plenty fast enough.
|
||||
|
||||
=cut
|
||||
|
||||
84
lib/WGDev/Command/Util.pm
Normal file
84
lib/WGDev/Command/Util.pm
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package WGDev::Command::Util;
|
||||
# ABSTRACT: Run a utility script
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base::Verbosity);
|
||||
|
||||
use File::Spec ();
|
||||
|
||||
sub config_parse_options { return qw(gnu_getopt pass_through) }
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
$wgd->set_environment;
|
||||
|
||||
my @args = $self->arguments;
|
||||
my $command = shift @args;
|
||||
|
||||
unshift @args, '--configFile=' . $wgd->config_file_relative;
|
||||
|
||||
my $sbin_path = File::Spec->catdir( $wgd->root, 'sbin' );
|
||||
|
||||
if ( -e $command ) {
|
||||
$command = File::Spec->rel2abs($command);
|
||||
}
|
||||
elsif ( -e File::Spec->rel2abs( $command, $sbin_path ) ) {
|
||||
$command = File::Spec->rel2abs( $command, $sbin_path );
|
||||
}
|
||||
else {
|
||||
WGDev::X->throw("Unable to find $command.");
|
||||
}
|
||||
|
||||
if ( !-x $command ) {
|
||||
unshift @args, $command;
|
||||
|
||||
# $^X is the name of the current perl executable
|
||||
$command = $^X;
|
||||
}
|
||||
|
||||
my $pid = fork;
|
||||
if ( !$pid ) {
|
||||
if ( $self->verbosity < 1 ) {
|
||||
##no critic (RequireCheckedOpen)
|
||||
open STDIN, '<', File::Spec->devnull;
|
||||
open STDOUT, '>', File::Spec->devnull;
|
||||
open STDERR, '>', File::Spec->devnull;
|
||||
}
|
||||
chdir $sbin_path;
|
||||
exec {$command} $command, @args;
|
||||
}
|
||||
waitpid $pid, 0;
|
||||
|
||||
# $? is the child's exit value
|
||||
return $? ? 0 : 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd util [-q] <command>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Runs a utility script. The script will be run from WebGUI's F<sbin>
|
||||
directory, and will be passed a C<--configFile> option.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Any options not handled by this command are passed to the utility script.
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-q> C<--quiet>
|
||||
|
||||
If specified, will silence all output from the utility script.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
217
lib/WGDev/Command/Version.pm
Normal file
217
lib/WGDev/Command/Version.pm
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
package WGDev::Command::Version;
|
||||
# ABSTRACT: Reports and updates version numbers
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use parent qw(WGDev::Command::Base);
|
||||
|
||||
use WGDev::X ();
|
||||
use File::Spec ();
|
||||
|
||||
sub needs_config {
|
||||
return;
|
||||
}
|
||||
|
||||
sub config_parse_options { return qw(no_getopt_compat) }
|
||||
|
||||
sub config_options {
|
||||
return qw(
|
||||
create|c
|
||||
bare|b
|
||||
dist|d
|
||||
);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my $wgd = $self->wgd;
|
||||
|
||||
my ($ver) = $self->arguments;
|
||||
|
||||
if ( $self->option('create') ) {
|
||||
$ver = $self->update_version($ver);
|
||||
}
|
||||
|
||||
my $wgv = $wgd->version;
|
||||
my ( $perl_version, $perl_status ) = $wgv->module;
|
||||
if ( $self->option('dist') ) {
|
||||
print $perl_version, q{-}, $perl_status, "\n";
|
||||
return 1;
|
||||
}
|
||||
if ( $self->option('bare') ) {
|
||||
print $perl_version, "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $db_version = $wgv->database_script;
|
||||
my ( $change_file, $change_version ) = $wgv->changelog;
|
||||
my ( $up_file, undef, $up_file_ver, $up_version ) = $wgv->upgrade;
|
||||
my $db_live_version = eval { $wgv->database( $wgd->db->connect ) };
|
||||
|
||||
my $err_count = 0;
|
||||
my $expect_ver = $ver || $perl_version;
|
||||
if ( $perl_version ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$perl_version = _colored( $perl_version, 'red' );
|
||||
}
|
||||
if ( $db_version ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$db_version = _colored( $db_version, 'magenta' );
|
||||
}
|
||||
if ( $change_version ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$change_version = _colored( $change_version, 'red' );
|
||||
}
|
||||
if ( $up_version ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$up_version = _colored( $up_version, 'red' );
|
||||
}
|
||||
if ( $up_file_ver ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$up_file = _colored( $up_file, 'red' );
|
||||
}
|
||||
if ( !defined $db_live_version ) {
|
||||
$err_count++;
|
||||
$db_live_version = _colored( 'Not available', 'magenta' );
|
||||
}
|
||||
elsif ( $db_live_version ne $expect_ver ) {
|
||||
$err_count++;
|
||||
$db_live_version = _colored( $db_live_version, 'red' );
|
||||
}
|
||||
|
||||
print <<"END_REPORT";
|
||||
Perl version: $perl_version - $perl_status
|
||||
Database version: $db_live_version
|
||||
Database script version: $db_version
|
||||
Changelog version: $change_version
|
||||
Upgrade script version: $up_version
|
||||
Upgrade script filename: $up_file
|
||||
END_REPORT
|
||||
|
||||
if ($err_count) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub update_version {
|
||||
my $self = shift;
|
||||
my $ver = shift;
|
||||
my $wgd = $self->wgd;
|
||||
my $root = $wgd->root;
|
||||
my $wgv = $wgd->version;
|
||||
my $old_version = $wgv->module;
|
||||
my $new_version = $ver || do {
|
||||
my @parts = split /[.]/msx, $old_version;
|
||||
$parts[-1]++;
|
||||
join q{.}, @parts;
|
||||
};
|
||||
|
||||
open my $fh, '<', File::Spec->catfile( $root, 'lib', 'WebGUI.pm' )
|
||||
or WGDev::X::IO::Read->throw( path => 'WebGUI.pm' );
|
||||
my @pm_content = do { local $/; <$fh> };
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => 'WebGUI.pm' );
|
||||
open $fh, '>', File::Spec->catfile( $root, 'lib', 'WebGUI.pm' )
|
||||
or WGDev::X::IO::Write->throw( path => 'WebGUI.pm' );
|
||||
for my $line (@pm_content) {
|
||||
$line =~ s/(\$VERSION\s*=)[^\n]*;/$1 '$new_version';/msx;
|
||||
print {$fh} $line;
|
||||
}
|
||||
close $fh
|
||||
or WGDev::X::IO::Write->throw( path => 'WebGUI.pm' );
|
||||
|
||||
my ($change_file) = $wgv->changelog;
|
||||
open $fh, '<',
|
||||
File::Spec->catfile( $root, 'docs', 'changelog', $change_file )
|
||||
or WGDev::X::IO::Read->throw( path => $change_file );
|
||||
my $change_content = do { local $/; <$fh> };
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => $change_file );
|
||||
|
||||
open $fh, '>',
|
||||
File::Spec->catfile( $root, 'docs', 'changelog', $change_file )
|
||||
or WGDev::X::IO::Write->throw( path => $change_file );
|
||||
print {$fh} $new_version . "\n\n" . $change_content;
|
||||
close $fh
|
||||
or WGDev::X::IO::Write->throw( path => $change_file );
|
||||
|
||||
open my $in, '<',
|
||||
File::Spec->catfile( $root, 'docs', 'upgrades', '_upgrade.skeleton' )
|
||||
or WGDev::X::IO::Read->throw( path => '_upgrade.skeleton' );
|
||||
open my $out, '>',
|
||||
File::Spec->catfile( $root, 'docs', 'upgrades',
|
||||
"upgrade_$old_version-$new_version.pl" )
|
||||
or WGDev::X::IO::Write->throw(
|
||||
path => "upgrade_$old_version-$new_version.pl" );
|
||||
while ( my $line = <$in> ) {
|
||||
$line =~ s/(\$toVersion\s*=)[^\n]*$/$1 '$new_version';/xms;
|
||||
print {$out} $line;
|
||||
}
|
||||
close $out
|
||||
or WGDev::X::IO::Write->throw(
|
||||
path => "upgrade_$old_version-$new_version.pl" );
|
||||
close $in
|
||||
or WGDev::X::IO::Read->throw( path => '_upgrade.skeleton' );
|
||||
return $new_version;
|
||||
}
|
||||
|
||||
sub _colored {
|
||||
no warnings 'redefine';
|
||||
if ( eval { require Term::ANSIColor; 1 } ) {
|
||||
*_colored = \&Term::ANSIColor::colored;
|
||||
}
|
||||
else {
|
||||
*_colored = sub { $_[0] };
|
||||
}
|
||||
goto &_colored;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
wgd version [-b | -c | -d] [<version>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Reports the current versions of the F<WebGUI.pm> module, F<create.sql> database
|
||||
script, change log, and upgrade file. Non-matching versions will be noted
|
||||
in red if possible.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 8
|
||||
|
||||
=item C<-c> C<--create>
|
||||
|
||||
Adds a new section to the change log for the new version, updates the version
|
||||
number in F<WebGUI.pm>, and creates a new upgrade script. The version number
|
||||
to update to can be specified on the command line. If not specified, defaults
|
||||
to incrementing the patch level by one.
|
||||
|
||||
=item C<-d> C<--dist>
|
||||
|
||||
Output the version number and status of the current WebGUI, joined by a dash.
|
||||
If the version is passed as well, it will be ignored.
|
||||
|
||||
=item C<-b> C<--bare>
|
||||
|
||||
Outputs the version number taken from F<WebGUI.pm> only
|
||||
|
||||
=item C<< <version> >>
|
||||
|
||||
The version number to compare against or create
|
||||
|
||||
=back
|
||||
|
||||
=method C<update_version ( $new_version )>
|
||||
|
||||
Updates WebGUI's version number to the specified version. If not provided,
|
||||
the patch level of the version number is incremented. The version number in
|
||||
F<WebGUI.pm> is changed, a new upgrade script is created, and a heading is
|
||||
added to the change log.
|
||||
|
||||
=cut
|
||||
|
||||
223
lib/WGDev/Database.pm
Normal file
223
lib/WGDev/Database.pm
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
package WGDev::Database;
|
||||
# ABSTRACT: Database connectivity and DSN parsing for WGDev
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub username { return shift->{username} }
|
||||
sub password { return shift->{password} }
|
||||
sub database { return shift->{database} }
|
||||
sub hostname { return shift->{hostname} }
|
||||
sub port { return shift->{port} }
|
||||
sub dsn { return shift->{dsn} }
|
||||
|
||||
sub user { goto &username }
|
||||
sub pass { goto &password }
|
||||
sub host { goto &hostname }
|
||||
sub name { goto &database }
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $config = shift;
|
||||
my $self = bless {}, $class;
|
||||
|
||||
my $dsn = $self->{dsn} = $config->get('dsn');
|
||||
$self->{username} = $config->get('dbuser');
|
||||
$self->{password} = $config->get('dbpass');
|
||||
$self->{database} = ( split /[:;]/msx, $dsn )[2];
|
||||
$self->{hostname} = 'localhost';
|
||||
$self->{port} = '3306';
|
||||
while ( $dsn =~ /([^=;:]+)=([^;:]+)/msxg ) {
|
||||
if ( $1 eq 'host' || $1 eq 'hostname' ) {
|
||||
$self->{hostname} = $2;
|
||||
}
|
||||
elsif ( $1 eq 'db' || $1 eq 'database' || $1 eq 'dbname' ) {
|
||||
$self->{database} = $2;
|
||||
}
|
||||
elsif ( $1 eq 'port' ) {
|
||||
$self->{port} = $2;
|
||||
}
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub command_line {
|
||||
my $self = shift;
|
||||
my @params = (
|
||||
'-h' . $self->hostname,
|
||||
'-P' . $self->port,
|
||||
'-u' . $self->username,
|
||||
( $self->password ? '-p' . $self->password : () ),
|
||||
'--default-character-set=utf8',
|
||||
@_,
|
||||
$self->database,
|
||||
);
|
||||
return wantarray ? @params : join q{ }, map {"'$_'"} @params;
|
||||
}
|
||||
|
||||
sub connect { ## no critic (ProhibitBuiltinHomonyms)
|
||||
my $self = shift;
|
||||
require DBI;
|
||||
if ( $self->{dbh} && !$self->{dbh}->ping ) {
|
||||
delete $self->{dbh};
|
||||
}
|
||||
return $self->{dbh} ||= DBI->connect(
|
||||
$self->dsn,
|
||||
$self->username,
|
||||
$self->password,
|
||||
{
|
||||
RaiseError => 1,
|
||||
PrintWarn => 0,
|
||||
PrintError => 0,
|
||||
mysql_enable_utf8 => 1,
|
||||
} );
|
||||
}
|
||||
sub dbh { return shift->{dbh} }
|
||||
sub open { goto &connect } ## no critic (ProhibitBuiltinHomonyms)
|
||||
|
||||
sub disconnect {
|
||||
my $self = shift;
|
||||
if ( my $dbh = delete $self->{dbh} ) {
|
||||
$dbh->disconnect;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub close { ## no critic (ProhibitBuiltinHomonyms ProhibitAmbiguousNames)
|
||||
goto &disconnect;
|
||||
}
|
||||
|
||||
sub clear {
|
||||
my $self = shift;
|
||||
my $dbh = $self->connect;
|
||||
my $sth = $dbh->table_info( undef, undef, q{%} );
|
||||
my @tables = map { @{$_} } @{ $sth->fetchall_arrayref( [2] ) };
|
||||
$dbh->do('SET FOREIGN_KEY_CHECKS = 0');
|
||||
for my $table (@tables) {
|
||||
$dbh->do( 'DROP TABLE ' . $dbh->quote_identifier($table) );
|
||||
}
|
||||
$dbh->do('SET FOREIGN_KEY_CHECKS = 1');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $self = shift;
|
||||
my $dumpfile = shift;
|
||||
system 'mysql', $self->command_line( '-e' . 'source ' . $dumpfile )
|
||||
and WGDev::X::System->throw('Error running mysql');
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub dump { ## no critic (ProhibitBuiltinHomonyms)
|
||||
my $self = shift;
|
||||
my $dumpfile = shift;
|
||||
system 'mysqldump', $self->command_line( '-r' . $dumpfile )
|
||||
and WGDev::X::System->throw('Error running mysqldump');
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $dsn = $wgd->database->connect;
|
||||
my $username = $wgd->database->username;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Has methods to access various parts of the DSN that can be used for other
|
||||
programs such as command line C<mysql>. Also has methods to easily connect
|
||||
and reuse a database connection.
|
||||
|
||||
=method C<new ( $wgd )>
|
||||
|
||||
Creates a new WGDev::Database object.
|
||||
|
||||
=for :list
|
||||
= C<$wgd>
|
||||
An instantiated WGDev object.
|
||||
|
||||
=method C<dsn>
|
||||
|
||||
Returns the DSN for the database.
|
||||
|
||||
=method C<database>
|
||||
|
||||
Returns the name of the database.
|
||||
|
||||
=method C<name>
|
||||
|
||||
Alias for the L</database> method.
|
||||
|
||||
=method C<hostname>
|
||||
|
||||
Returns the host name for the database connection.
|
||||
|
||||
=method C<host>
|
||||
|
||||
Alias for the L</hostname> method.
|
||||
|
||||
=method C<password>
|
||||
|
||||
Returns the password for the database connection.
|
||||
|
||||
=method C<pass>
|
||||
|
||||
Alias for the L</password> method.
|
||||
|
||||
=method C<port>
|
||||
|
||||
Returns the port for the database connection.
|
||||
|
||||
=method C<username>
|
||||
|
||||
Returns the user name for the database connection.
|
||||
|
||||
=method C<user>
|
||||
|
||||
Alias for the L</username> method.
|
||||
|
||||
=method C<command_line>
|
||||
|
||||
Returns command line options suitable for passing to the F<mysql>
|
||||
or F<mysqldump> command line programs to connect to the database.
|
||||
|
||||
=method C<connect>
|
||||
|
||||
Connects to the database if it hasn't been connected to yet and
|
||||
returns the database handle for the connection.
|
||||
|
||||
=method C<open>
|
||||
|
||||
Alias for the L</connect> method.
|
||||
|
||||
=method C<dbh>
|
||||
|
||||
Returns the database handle of the current connection, or C<undef> if
|
||||
there is no active connection.
|
||||
|
||||
=method C<disconnect>
|
||||
|
||||
Closes the active database connection. If there is no active
|
||||
connection, does nothing.
|
||||
|
||||
=method C<close>
|
||||
|
||||
Alias for the L</disconnect> method.
|
||||
|
||||
=method C<clear>
|
||||
|
||||
Removes all tables from the database, leaving it empty.
|
||||
|
||||
=method C<dump ( $dumpfile )>
|
||||
|
||||
Dumps the database content to the specified file.
|
||||
|
||||
=method C<load ( $dumpfile )>
|
||||
|
||||
Loads the specified database script into the database.
|
||||
|
||||
=cut
|
||||
|
||||
98
lib/WGDev/File.pm
Normal file
98
lib/WGDev/File.pm
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package WGDev::File;
|
||||
# ABSTRACT: File utility functions
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use constant STAT_FILESIZE => 7;
|
||||
|
||||
use WGDev::X;
|
||||
use File::Spec ();
|
||||
|
||||
sub sync_dirs {
|
||||
my $class = shift;
|
||||
my $from_dir = shift;
|
||||
my $to_dir = shift;
|
||||
|
||||
require File::Copy;
|
||||
require File::Path;
|
||||
|
||||
File::Path::mkpath($to_dir);
|
||||
|
||||
# recurse through destination and delete files that don't exist in source
|
||||
$class->matched_find($to_dir, $from_dir, sub {
|
||||
my ($to_path, $from_path) = @_;
|
||||
return
|
||||
if -e $from_path;
|
||||
if ( -d $to_path ) {
|
||||
File::Path::rmtree($to_path);
|
||||
}
|
||||
else {
|
||||
unlink $to_path;
|
||||
}
|
||||
});
|
||||
|
||||
# copy files that don't exist or are different
|
||||
$class->matched_find($from_dir, $to_dir, sub {
|
||||
my ($from_path, $to_path) = @_;
|
||||
return
|
||||
if -d $from_path;
|
||||
my $from_size = ( stat _ )[STAT_FILESIZE];
|
||||
return
|
||||
if -e $to_path && ( stat _ )[STAT_FILESIZE] == $from_size;
|
||||
|
||||
my $to_parent = File::Spec->catpath(
|
||||
( File::Spec->splitpath($to_path) )[ 0, 1 ] );
|
||||
File::Path::mkpath($to_parent);
|
||||
File::Copy::copy( $from_path, $to_path );
|
||||
});
|
||||
}
|
||||
|
||||
sub matched_find {
|
||||
my $class = shift;
|
||||
my $from_dir = shift;
|
||||
my $to_dir = shift;
|
||||
my $cb = shift;
|
||||
|
||||
require File::Find;
|
||||
|
||||
my $matched_cb = sub {
|
||||
no warnings 'once';
|
||||
my $from_path = $File::Find::name;
|
||||
my ( undef, undef, $filename ) = File::Spec->splitpath($from_path);
|
||||
if ( $filename eq '.svn' || $filename eq 'temp' ) {
|
||||
$File::Find::prune = 1;
|
||||
return;
|
||||
}
|
||||
my $rel_path = File::Spec->abs2rel( $from_path, $from_dir );
|
||||
my $to_path = File::Spec->rel2abs( $rel_path, $to_dir );
|
||||
$cb->($from_path, $to_path);
|
||||
};
|
||||
File::Find::find( { no_chdir => 1, wanted => $matched_cb }, $from_dir );
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
WGDev::File->sync_dirs($from, $to);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Performs common actions on files.
|
||||
|
||||
=method C<sync_dirs ( $from_dir, $to_dir )>
|
||||
|
||||
Synchronizes two directories. Deletes any additional files in the
|
||||
destination that don't exist in the source. Checks for file
|
||||
differences by size before copying.
|
||||
|
||||
=method C<matched_find ( $from_dir, $to_dir, $callback )>
|
||||
|
||||
Recurses through C<$from_dir>, calling C<$callback> for each file
|
||||
or directory found. The callback is passed two parameters, the
|
||||
file found, and a file name relative to C<$to_dir> based on the found
|
||||
file's path relative to C<$from_dir>.
|
||||
|
||||
=cut
|
||||
|
||||
213
lib/WGDev/Help.pm
Normal file
213
lib/WGDev/Help.pm
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
package WGDev::Help;
|
||||
# ABSTRACT: Generate help text for WGDev
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use WGDev::X ();
|
||||
use File::Spec ();
|
||||
use Try::Tiny;
|
||||
|
||||
sub package_usage {
|
||||
my $package = shift;
|
||||
my $verbosity = shift;
|
||||
require WGDev::Pod::Usage;
|
||||
if ( !defined $verbosity ) {
|
||||
$verbosity = 1;
|
||||
}
|
||||
my $parser = WGDev::Pod::Usage->new;
|
||||
$parser->verbosity($verbosity);
|
||||
my $pod = package_pod($package);
|
||||
return $parser->parse_from_string($pod);
|
||||
}
|
||||
|
||||
sub package_perldoc {
|
||||
my $package = shift;
|
||||
my $sections = shift;
|
||||
require Pod::Perldoc;
|
||||
require File::Temp;
|
||||
File::Temp->VERSION(0.19); ##no critic (ProhibitMagicNumbers)
|
||||
require File::Path;
|
||||
my $pod = package_pod( $package, $sections );
|
||||
|
||||
my $pid = fork;
|
||||
if ( !$pid ) {
|
||||
# perldoc will try to drop privs anyway, so do it ourselves so the
|
||||
# temp file has the correct owner
|
||||
Pod::Perldoc->new->drop_privs_maybe;
|
||||
|
||||
# Make a path that plays nice with Perldoc internals. Will format nicer.
|
||||
my $tmpdir = File::Temp->newdir( TMPDIR => 1 );
|
||||
|
||||
# construct a path that Perldoc will interperet as a package name
|
||||
my @path_parts = split /::/msx, $package;
|
||||
my $filename = pop @path_parts;
|
||||
my $path = File::Spec->catdir( $tmpdir->dirname, 'perl', @path_parts );
|
||||
File::Path::mkpath($path);
|
||||
my $out_file = File::Spec->catfile( $path, $filename );
|
||||
|
||||
open my $out, '>', $out_file
|
||||
or WGDev::X::IO->throw('Unable to create temp file');
|
||||
print {$out} $pod;
|
||||
close $out or return q{};
|
||||
|
||||
# perldoc doesn't understand darwin's stty output.
|
||||
# copy and paste but it seems to work
|
||||
my @extra_args;
|
||||
if ($^O eq 'darwin') {
|
||||
##no critic (ProhibitBacktick ProhibitMagicNumbers)
|
||||
if (`stty -a` =~ /(\d+)[ ]columns;/msx) {
|
||||
my $cols = $1;
|
||||
my $c = $cols * 39 / 40;
|
||||
$cols = $c > $cols - 2 ? $c : $cols -2;
|
||||
if ( $cols > 80 ) {
|
||||
push @extra_args, '-n', 'nroff -rLL=' . (int $c) . 'n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local @ARGV = ( @extra_args, '-w', 'section:3', '-F', $out_file );
|
||||
exit Pod::Perldoc->run;
|
||||
}
|
||||
waitpid $pid, 0;
|
||||
|
||||
# error status of subprocess
|
||||
if ($?) {
|
||||
WGDev::X->throw('Error displaying help!');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
my %pod;
|
||||
|
||||
sub package_pod {
|
||||
my $package = shift;
|
||||
my $sections = shift;
|
||||
my $raw_pod = $pod{$package};
|
||||
if ( !$raw_pod ) {
|
||||
$raw_pod = read_lib($package);
|
||||
$pod{$package} = $raw_pod;
|
||||
}
|
||||
|
||||
return $raw_pod
|
||||
if !$sections;
|
||||
|
||||
open my $pod_in, '<', \$raw_pod
|
||||
or WGDev::X::IO->throw;
|
||||
my @sections = ref $sections ? @{$sections} : $sections;
|
||||
require Pod::Select;
|
||||
my $parser = Pod::Select->new;
|
||||
$parser->select(@sections);
|
||||
my $pod = q{};
|
||||
open my $pod_out, '>', \$pod
|
||||
or WGDev::X::IO->throw;
|
||||
$parser->parse_from_filehandle( $pod_in, $pod_out );
|
||||
close $pod_out
|
||||
or WGDev::X::IO->throw;
|
||||
close $pod_in
|
||||
or WGDev::X::IO->throw;
|
||||
return $pod;
|
||||
}
|
||||
|
||||
sub read_lib {
|
||||
my $module = shift;
|
||||
if ($module =~ /\A\w+(?:::\w+)*\z/msx) {
|
||||
$module .= '.pm';
|
||||
$module =~ s{::}{/}msxg;
|
||||
}
|
||||
my $data;
|
||||
if ($INC{$module}) {
|
||||
$data = _read_file($module, $INC{$module});
|
||||
}
|
||||
else {
|
||||
for my $inc (@INC) {
|
||||
if (! ref $inc) {
|
||||
my $filename = $inc . q{/} . $module;
|
||||
if (-f $filename) {
|
||||
$data = _read_file($module, $filename);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data = _read_file($module, $inc);
|
||||
}
|
||||
last
|
||||
if defined $data;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub _read_file {
|
||||
my ($module, $inc) = @_;
|
||||
my ($fh, $cb, $state);
|
||||
##no critic (ProhibitCascadingIfElse)
|
||||
if (! ref $inc) {
|
||||
open $fh, '<', $inc
|
||||
or return;
|
||||
}
|
||||
elsif (ref $inc eq 'CODE') {
|
||||
($fh, $cb, $state) = $inc->($inc, $module);
|
||||
}
|
||||
elsif (ref $inc eq 'ARRAY') {
|
||||
($fh, $cb, $state) = $inc->[0]->($inc, $module);
|
||||
}
|
||||
elsif ($inc->can('INC')) {
|
||||
($fh, $cb, $state) = $inc->INC($module);
|
||||
}
|
||||
my $data;
|
||||
if ($cb || $fh) {
|
||||
local $_;
|
||||
$data = q{};
|
||||
while (1) {
|
||||
last
|
||||
if ($fh && !defined ($_ = <$fh>));
|
||||
last
|
||||
if ($cb && !$cb->($cb, $state));
|
||||
$data .= $_;
|
||||
}
|
||||
if ($fh) {
|
||||
close $fh
|
||||
or WGDev::X::IO->throw;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use WGDev::Help;
|
||||
|
||||
my $usage = WGDev::Help::package_usage( 'My::Class' );
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Reads help information from modules but filters to only pick relevant
|
||||
sections when multiple POD documents exist in a single file.
|
||||
|
||||
=func C<package_usage ( $package [, $verbosity] )>
|
||||
|
||||
Returns usage information for a package, using L<Pod::Usage>. Can be used on
|
||||
packages that have been combined into a single file.
|
||||
|
||||
=func C<package_perldoc ( $package [, $sections] )>
|
||||
|
||||
Displays documentation for a package using L<Pod::Perldoc>. Can be used on
|
||||
packages that have been combined into a single file.
|
||||
|
||||
=for :list
|
||||
= C<$sections>
|
||||
Passed on to L</package_pod> to limit the sections output.
|
||||
|
||||
=func C<package_pod ( $package [, $sections] )>
|
||||
|
||||
Filters out the POD for a specific package from the module file for the package.
|
||||
|
||||
=for :list
|
||||
= C<$sections>
|
||||
|
||||
Limits sections to include based on L<Pod::Select/SECTION SPECIFICATIONS|Pod::Select's rules>.
|
||||
Can be either a scalar value or an array reference.
|
||||
|
||||
=cut
|
||||
162
lib/WGDev/Pod/Usage.pm
Normal file
162
lib/WGDev/Pod/Usage.pm
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package WGDev::Pod::Usage;
|
||||
# ABSTRACT: Produce usage documentation for WGDev commands
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use constant OPTION_INDENT => 4;
|
||||
use constant OPTION_TEXT_INDENT => 24;
|
||||
|
||||
use parent qw(Pod::PlainText Pod::Select);
|
||||
|
||||
use WGDev::X ();
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
|
||||
my $self = $proto->SUPER::new( indent => 0 );
|
||||
$self->verbosity(1);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub verbosity {
|
||||
my $self = shift;
|
||||
my $verbosity = shift;
|
||||
if ($verbosity) {
|
||||
$self->select(qw(NAME SYNOPSIS OPTIONS/!.+));
|
||||
}
|
||||
else {
|
||||
$self->select(qw(NAME SYNOPSIS));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub command {
|
||||
my $self = shift;
|
||||
my $command = shift;
|
||||
$self->{_last_command} = $command;
|
||||
return $self->SUPER::command( $command, @_ );
|
||||
}
|
||||
|
||||
sub cmd_head1 {
|
||||
my $self = shift;
|
||||
my $head = shift;
|
||||
my $para = shift;
|
||||
$head =~ s/\s+$//msx;
|
||||
$self->{_last_head1} = $head;
|
||||
if ( $head eq 'NAME' ) {
|
||||
return;
|
||||
}
|
||||
elsif ( $head eq 'SYNOPSIS' ) {
|
||||
$head = 'USAGE';
|
||||
}
|
||||
$head = lc $head;
|
||||
$head =~ s/\b(.)/uc($1)/msxe;
|
||||
$head .= q{:};
|
||||
my $output = $self->interpolate( $head, $para );
|
||||
$self->output( $output . "\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
sub textblock {
|
||||
my $self = shift;
|
||||
my $text = shift;
|
||||
my $para = shift;
|
||||
if ( $self->{_last_head1} eq 'NAME' ) {
|
||||
$text =~ s/^[\w:]+\Q - //msx;
|
||||
}
|
||||
if ( $self->{_last_command} eq 'item' && !$self->{ITEM} ) {
|
||||
return;
|
||||
}
|
||||
return $self->SUPER::textblock( $text, $para );
|
||||
}
|
||||
|
||||
sub verbatim {
|
||||
my $self = shift;
|
||||
if ( $self->{_last_command} eq 'item' && !$self->{ITEM} ) {
|
||||
return;
|
||||
}
|
||||
return $self->SUPER::verbatim(@_);
|
||||
}
|
||||
|
||||
sub item {
|
||||
my $self = shift;
|
||||
my $item = shift;
|
||||
my $tag = delete $self->{ITEM};
|
||||
my $margin = $self->{MARGIN};
|
||||
local $self->{MARGIN} = 0; ## no critic (ProhibitLocalVars)
|
||||
|
||||
$tag = $self->reformat($tag);
|
||||
$tag =~ s/\n*\z//msx;
|
||||
|
||||
$item =~ s/[.].*//msx;
|
||||
{
|
||||
## no critic (ProhibitLocalVars)
|
||||
local $self->{width} = $self->{width} - OPTION_TEXT_INDENT;
|
||||
$item = $self->reformat($item);
|
||||
}
|
||||
$item =~ s/\n*\z//msx;
|
||||
my $option_indent_string = q{ } x OPTION_TEXT_INDENT;
|
||||
$item =~ s/\n/\n$option_indent_string/msxg;
|
||||
|
||||
my $indent_string = q{ } x OPTION_INDENT;
|
||||
if ( $item eq q{} ) {
|
||||
$self->output( $indent_string . $tag . "\n" );
|
||||
}
|
||||
else {
|
||||
my $option_name_length = OPTION_TEXT_INDENT - OPTION_INDENT - 1;
|
||||
$self->output( $indent_string . sprintf "%-*s %s\n",
|
||||
$option_name_length, $tag, $item );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub seq_c {
|
||||
return $_[1];
|
||||
}
|
||||
|
||||
sub parse_from_string {
|
||||
my $self = shift;
|
||||
my $pod = shift;
|
||||
my $output = q{};
|
||||
open my $out_fh, '>', \$output
|
||||
or WGDev::X::IO->throw;
|
||||
open my $in_fh, '<', \$pod
|
||||
or WGDev::X::IO->throw;
|
||||
$self->parse_from_filehandle( $in_fh, $out_fh );
|
||||
close $in_fh
|
||||
or WGDev::X::IO->throw;
|
||||
close $out_fh
|
||||
or WGDev::X::IO->throw;
|
||||
return $output;
|
||||
}
|
||||
|
||||
# this/these are methods that Pod::PlainText used to implement, but no long does
|
||||
|
||||
sub cmd_method { my $self = shift; $self->item(@_); } # 'cmd_' . $pod_node_name is a computed method name; handle =method entires the same way as =item entries
|
||||
|
||||
# done
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use WGDev::Pod::Usage;
|
||||
my $parser = WGDev::Pod::Usage->new;
|
||||
my $usage = $parser->parse_from_string($pod);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Formats POD documentation into a format suitable for showing as
|
||||
usage text. WGDev::Pod::Usage is a subclass of L<Pod::Select>.
|
||||
|
||||
=for Pod::Coverage
|
||||
cmd_head1
|
||||
new
|
||||
parse_from_string
|
||||
seq_c
|
||||
textblock
|
||||
verbosity
|
||||
|
||||
=cut
|
||||
|
||||
216
lib/WGDev/Version.pm
Normal file
216
lib/WGDev/Version.pm
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
package WGDev::Version;
|
||||
# ABSTRACT: Extract version information from WebGUI
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use File::Spec;
|
||||
use WGDev::X ();
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $dir = shift || WGDev::X::NoWebGUIRoot->throw;
|
||||
my $self = bless \$dir, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub pm { goto &module }
|
||||
|
||||
sub module {
|
||||
my $dir = ${ +shift };
|
||||
my $version;
|
||||
my $status;
|
||||
open my $fh, '<', File::Spec->catfile( $dir, 'lib', 'WebGUI.pm' )
|
||||
or WGDev::X::IO::Read->throw( path => 'WebGUI.pm' );
|
||||
while ( my $line = <$fh> ) {
|
||||
##no critic (ProhibitStringyEval)
|
||||
if ( $line =~ /\$VERSION\s*=(.*)$/msx ) {
|
||||
$version = eval $1;
|
||||
}
|
||||
if ( $line =~ /\$STATUS\s*=(.*)$/msx ) {
|
||||
$status = eval $1;
|
||||
}
|
||||
last
|
||||
if $version && $status;
|
||||
}
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => 'WebGUI.pm' );
|
||||
return wantarray ? ( $version, $status ) : $version;
|
||||
}
|
||||
|
||||
sub db_script { goto &database_script }
|
||||
|
||||
sub database_script {
|
||||
my $self = shift;
|
||||
my $dir = ${ $self };
|
||||
my $wg8 = $self->module =~ /^8[.]/msx;
|
||||
my $version;
|
||||
my $db_file = $wg8 ? do {
|
||||
require WebGUI::Paths;
|
||||
WebGUI::Paths->defaultCreateSQL;
|
||||
} : File::Spec->catfile( $dir, 'docs', 'create.sql' );
|
||||
open my $fh, '<', $db_file
|
||||
or WGDev::X::IO::Read->throw( path => $db_file );
|
||||
while ( my $line = <$fh> ) {
|
||||
if (
|
||||
##no critic (ProhibitComplexRegexes);
|
||||
$line =~ m{
|
||||
(?:(?i)\QINSERT INTO\E) \s+
|
||||
(`?)webguiVersion\1 \s+
|
||||
.+? \s+?
|
||||
(?i)VALUES \s+
|
||||
\Q('\E ( [^']+ ) [']
|
||||
}msx
|
||||
) {
|
||||
$version = $2;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => $db_file );
|
||||
return $version;
|
||||
}
|
||||
|
||||
sub db { goto &database }
|
||||
|
||||
sub database {
|
||||
my $dir = ${ +shift };
|
||||
my $dbh = shift;
|
||||
require version;
|
||||
my $sth = $dbh->prepare('SELECT webguiVersion FROM webguiVersion');
|
||||
$sth->execute;
|
||||
my @versions = map { $_->[0] }
|
||||
sort { $a->[1] <=> $b->[1] }
|
||||
map { [ $_, version->new($_) ] }
|
||||
map { @{$_} } @{ $sth->fetchall_arrayref( [0] ) };
|
||||
$sth->finish;
|
||||
my $version = pop @versions;
|
||||
return $version;
|
||||
}
|
||||
|
||||
sub changelog {
|
||||
my $dir = ${ +shift };
|
||||
require version;
|
||||
my @changelogs;
|
||||
opendir my $dh, File::Spec->catdir( $dir, 'docs', 'changelog' )
|
||||
or WGDev::X::IO::Read->throw( path => 'docs/changelog' );
|
||||
while ( my $file = readdir $dh ) {
|
||||
if ( $file =~ /^( [x\d]+ [.] [x\d]+ [.] [x\d]+ ) \Q.txt\E $/msx ) {
|
||||
( my $v = $1 ) =~ tr/x/0/;
|
||||
push @changelogs, [ $file, version->new($v) ];
|
||||
}
|
||||
}
|
||||
closedir $dh
|
||||
or WGDev::X::IO::Read->throw( path => 'docs/changelog' );
|
||||
@changelogs = sort { $a->[1] <=> $b->[1] } @changelogs;
|
||||
my $latest = pop @changelogs;
|
||||
open my $fh, '<',
|
||||
File::Spec->catfile( $dir, 'docs', 'changelog', $latest->[0] )
|
||||
or WGDev::X::IO::Read->throw( path => "docs/changelog/$latest->[0]" );
|
||||
while ( my $line = <$fh> ) {
|
||||
if ( $line =~ /^(\d+[.]\d+[.]\d+)$/msx ) {
|
||||
$latest->[1] = $1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close $fh
|
||||
or WGDev::X::IO::Read->throw( path => "docs/changelog/$latest->[0]" );
|
||||
return @{$latest};
|
||||
}
|
||||
|
||||
# returns ($upgrade_file, $from_version, $to_version, $to_version_file)
|
||||
sub upgrade {
|
||||
my $dir = ${ +shift };
|
||||
require version;
|
||||
my @upgrades;
|
||||
opendir my $dh, File::Spec->catdir( $dir, 'docs', 'upgrades' )
|
||||
or WGDev::X::IO::Read->throw( path => 'docs/upgrades' );
|
||||
while ( my $file = readdir $dh ) {
|
||||
if ( $file =~ /^upgrade_ ([.\d]+) - ([.\d]+) \Q.pl\E$/msx ) {
|
||||
push @upgrades, [ $file, version->new($1), version->new($2) ];
|
||||
}
|
||||
}
|
||||
closedir $dh
|
||||
or WGDev::X::IO::Read->throw( path => 'docs/upgrades' );
|
||||
@upgrades = sort { $a->[2] <=> $b->[2] } @upgrades;
|
||||
my $latest = pop @upgrades;
|
||||
open my $fh, '<',
|
||||
File::Spec->catfile( $dir, 'docs', 'upgrades', $latest->[0] )
|
||||
or
|
||||
WGDev::X::IO::Read->throw( path => 'docs/upgrades/' . $latest->[0] );
|
||||
while ( my $line = <$fh> ) {
|
||||
if ( $line =~ /\$toVersion\s*=(.*)$/msx ) {
|
||||
##no critic (ProhibitStringyEval RequireCheckingReturnValueOfEval)
|
||||
push @{$latest}, eval $1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close $fh
|
||||
or
|
||||
WGDev::X::IO::Read->throw( path => 'docs/upgrades/' . $latest->[0] );
|
||||
return @{$latest};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $wgv = WGDev::Version->new('/data/WebGUI');
|
||||
print "You have WebGUI " . $wgv->module . "\n";
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Extracts version information from various places in WebGUI: the change log,
|
||||
the upgrade script, the WebGUI module, the database creation script, or a
|
||||
live database.
|
||||
|
||||
=method C<new ( $webgui_root )>
|
||||
|
||||
Creates a new WGDev::Version object. Needs a WebGUI directory to be specified.
|
||||
|
||||
=for :list
|
||||
= C<$webgui_root>
|
||||
The root of the WebGUI directory to use for finding each file.
|
||||
|
||||
=method C<module>
|
||||
|
||||
In scalar context, returns the version number from the F<lib/WebGUI.pm>
|
||||
module. In array context, returns the version number and the status
|
||||
(beta/stable).
|
||||
|
||||
=method C<pm>
|
||||
|
||||
An alias for the L</module> method.
|
||||
|
||||
=method C<changelog>
|
||||
|
||||
Returns the most recent version number noted in the change log.
|
||||
|
||||
=method C<upgrade>
|
||||
|
||||
my ($upgrade_file, $from_version, $to_version, $to_version_file) = $wgv->upgrade;
|
||||
|
||||
Finds the most recent upgrade script and returns an array of
|
||||
information about it. The array contains the script's file name,
|
||||
the version number it will upgrade from and to based on its file name,
|
||||
and the version it will upgrade to noted in the script itself.
|
||||
|
||||
=method C<database_script>
|
||||
|
||||
Returns the version noted in the F<create.sql> database script.
|
||||
|
||||
=method C<db_script>
|
||||
|
||||
An alias for the L</database_script> method.
|
||||
|
||||
=method C<database ( $dbh )>
|
||||
|
||||
Accepts a database handle, and returns the latest version from the
|
||||
C<webguiVersion> table.
|
||||
|
||||
=method C<db>
|
||||
|
||||
An alias for the L</database> method.
|
||||
|
||||
=cut
|
||||
|
||||
263
lib/WGDev/X.pm
Normal file
263
lib/WGDev/X.pm
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
package WGDev::X;
|
||||
# ABSTRACT: WGDev Exceptions
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use Exception::Class (
|
||||
'WGDev::X' => { description => 'A general WGDev error', },
|
||||
'WGDev::X::CommandLine' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'An error with the command line.',
|
||||
fields => ['usage'],
|
||||
},
|
||||
'WGDev::X::CommandLine::BadCommand' => {
|
||||
isa => 'WGDev::X::CommandLine',
|
||||
description => 'An invalid command was requested.',
|
||||
fields => ['command_name'],
|
||||
},
|
||||
'WGDev::X::BadCommand' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'An invalid command was requested.',
|
||||
fields => ['command_name'],
|
||||
},
|
||||
'WGDev::X::CommandLine::BadParams' => {
|
||||
isa => 'WGDev::X::CommandLine',
|
||||
description => 'Invalid parameters were passed to a command.',
|
||||
},
|
||||
'WGDev::X::System' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'System error',
|
||||
fields => ['errno_string'],
|
||||
},
|
||||
'WGDev::X::IO' => {
|
||||
isa => 'WGDev::X::System',
|
||||
description => 'IO error',
|
||||
fields => ['path'],
|
||||
},
|
||||
'WGDev::X::IO::Read' => {
|
||||
isa => 'WGDev::X::IO',
|
||||
description => 'Read error',
|
||||
},
|
||||
'WGDev::X::IO::Write' => {
|
||||
isa => 'WGDev::X::IO',
|
||||
description => 'Write error',
|
||||
},
|
||||
'WGDev::X::NoWebGUIConfig' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'No WebGUI config file available.',
|
||||
},
|
||||
'WGDev::X::NoWebGUIRoot' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'No WebGUI root directory available.',
|
||||
},
|
||||
'WGDev::X::BadParameter' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'Bad parameter provided.',
|
||||
fields => [ 'parameter', 'value' ],
|
||||
},
|
||||
'WGDev::X::AssetNotFound' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'Specified asset not found',
|
||||
fields => ['asset'],
|
||||
},
|
||||
'WGDev::X::BadAssetClass' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'Bad asset class specified',
|
||||
fields => ['class'],
|
||||
},
|
||||
'WGDev::X::Module' => {
|
||||
isa => 'WGDev::X',
|
||||
description => 'Error loading module',
|
||||
fields => ['module', 'using_module'],
|
||||
},
|
||||
'WGDev::X::Module::Find' => {
|
||||
isa => 'WGDev::X::Module',
|
||||
description => q{Can't find module},
|
||||
},
|
||||
'WGDev::X::Module::Parse' => {
|
||||
isa => 'WGDev::X::Module',
|
||||
description => q{Error compiling module},
|
||||
},
|
||||
'WGDev::X::BadPackage' => {
|
||||
isa => 'WGDev::X',
|
||||
description => q{Error importing a package},
|
||||
fields => ['message', 'package'],
|
||||
},
|
||||
);
|
||||
|
||||
BEGIN {
|
||||
if ( $ENV{WGDEV_DEBUG} ) {
|
||||
WGDev::X->Trace(1);
|
||||
}
|
||||
##no critic (ProhibitMagicNumbers)
|
||||
if ( !eval { Exception::Class->VERSION(1.27) } ) {
|
||||
|
||||
# work around bad behavior of Exception::Class < 1.27
|
||||
# where it defaults the message to $!
|
||||
no warnings 'once';
|
||||
*WGDev::X::new = sub {
|
||||
my $errno = qq{$!};
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
if ( $self->{message} eq $errno ) {
|
||||
$self->{message} = q{};
|
||||
}
|
||||
return $self;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
##no critic (ProhibitQualifiedSubDeclarations)
|
||||
|
||||
sub _format_file_as_module {
|
||||
my $file = shift;
|
||||
if ($file =~ s/[.]pm$//msx) {
|
||||
$file =~ s{/}{::}msxg;
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
sub WGDev::X::inflate {
|
||||
my $class = shift;
|
||||
if (@_ == 1 && ref $_[0] && $_[0]->can('throw')) {
|
||||
$_[0]->throw;
|
||||
}
|
||||
if (@_ == 1 && !ref $_[0]) {
|
||||
my $e = shift;
|
||||
##no critic (ProhibitComplexRegexes);
|
||||
if ($e =~ m{
|
||||
\ACan't[ ]locate[ ](.*?)[ ]in[ ][@]INC[ ]
|
||||
.*[ ]at[ ](.*?)[ ]line[ ]\d+[.]
|
||||
}msx) {
|
||||
my $module = $1;
|
||||
my $using_module = $2;
|
||||
$module = _format_file_as_module($module);
|
||||
$using_module = _format_file_as_module($using_module);
|
||||
WGDev::X::Module::Find->throw(message => $e, module => $module, using_module => $using_module);
|
||||
}
|
||||
elsif ( $e =~ s{
|
||||
(at[ ](.*?)[.]pm[ ]line[ ]\d+[.])
|
||||
\s+Compilation[ ]failed[ ]in[ ]require[ ]at[ ]
|
||||
(.*?)[ ]line[ ]\d+[.].*?\z
|
||||
}{$1}msx ) {
|
||||
my $module = $2;
|
||||
my $using_module = $3;
|
||||
$module = _format_file_as_module($module);
|
||||
$using_module = _format_file_as_module($using_module);
|
||||
WGDev::X::Module::Parse->throw(message => $e, module => $module, using_module => $using_module);
|
||||
}
|
||||
}
|
||||
$class->throw(@_);
|
||||
}
|
||||
|
||||
sub WGDev::X::full_message {
|
||||
my $self = shift;
|
||||
return $self->message || $self->description;
|
||||
}
|
||||
|
||||
sub WGDev::X::CommandLine::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->message;
|
||||
if ( defined $self->usage ) {
|
||||
if ($message) {
|
||||
$message =~ s/[\n\r]*\z/\n\n/msx;
|
||||
}
|
||||
$message .= $self->usage;
|
||||
}
|
||||
$message =~ s/[\n\r]*\z/\n\n/msx;
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::BadParameter::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->SUPER::message || $self->description;
|
||||
if ( $self->parameter ) {
|
||||
$message .= q{ } . $self->parameter;
|
||||
}
|
||||
if ( $self->value ) {
|
||||
$message .= q{: } . $self->value;
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::CommandLine::BadCommand::full_message {
|
||||
my $self = shift;
|
||||
my $message
|
||||
= defined $self->command_name
|
||||
? "Can't find command @{[ $self->command_name ]}!\n"
|
||||
: "No command specified!\n";
|
||||
if ( defined $self->usage ) {
|
||||
$message .= "\n" . $self->usage;
|
||||
}
|
||||
$message =~ s/[\n\r]*\z/\n\n/msx;
|
||||
$message
|
||||
.= "Try the running 'wgd commands' for a list of available commands.\n\n";
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::System::new {
|
||||
my $class = shift;
|
||||
my $errno_string = qq{$!};
|
||||
my $self = $class->SUPER::new(@_);
|
||||
if ( !defined $self->errno_string ) {
|
||||
$self->{errno_string} = $errno_string;
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub WGDev::X::System::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->SUPER::full_message;
|
||||
$message .= ' - ' . $self->errno_string;
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::IO::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->SUPER::message || $self->description;
|
||||
if ( $self->path ) {
|
||||
$message .= ' Path: ' . $self->path;
|
||||
}
|
||||
$message .= ' - ' . $self->errno_string;
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::AssetNotFound::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->SUPER::full_message;
|
||||
if ( $self->asset ) {
|
||||
$message .= ' - ' . $self->asset;
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::Module::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->description . q{ } . $self->module
|
||||
. q{ for } . $self->using_module . ":\n" . $self->SUPER::message;
|
||||
$message =~ s/[\n\r]*\z/\n\n/msx;
|
||||
return $message;
|
||||
}
|
||||
|
||||
sub WGDev::X::Module::Find::full_message {
|
||||
my $self = shift;
|
||||
my $message = $self->description . q{ } . $self->module
|
||||
. q{ for } . $self->using_module;
|
||||
$message =~ s/[\n\r]*\z/\n\n/msx;
|
||||
return $message;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use WGDev::X;
|
||||
WGDev::X->throw();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Exceptions for WGDev
|
||||
|
||||
=cut
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ sub getEditForm {
|
|||
my $i18n = WebGUI::International->new($self->session,"Asset_Poll");
|
||||
my ($i, $answers);
|
||||
for ($i=1; $i<=20; $i++) {
|
||||
if ($self->get('a'.$i) =~ /\C/) {
|
||||
if ($self->get('a'.$i) =~ /./) {
|
||||
$answers .= $self->getValue("a".$i)."\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -489,7 +489,7 @@ sub view {
|
|||
$var{"form.end"} = WebGUI::Form::formFooter($self->session,);
|
||||
$totalResponses = 1 if ($totalResponses < 1);
|
||||
for (my $i=1; $i<=20; $i++) {
|
||||
if ($self->get('a'.$i) =~ /\C/) {
|
||||
if ($self->get('a'.$i) =~ /./) {
|
||||
my ($tally) = $self->session->db->quickArray("select count(*) from Poll_answer where answer='a"
|
||||
.$i."' and assetId=".$self->session->db->quote($self->getId)." group by answer");
|
||||
push(@answers,{
|
||||
|
|
|
|||
246
lib/WebGUI/Paths.pm
Normal file
246
lib/WebGUI/Paths.pm
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
package WebGUI::Paths;
|
||||
|
||||
=head1 LEGAL
|
||||
|
||||
-------------------------------------------------------------------
|
||||
WebGUI is Copyright 2001-2012 Plain Black Corporation.
|
||||
-------------------------------------------------------------------
|
||||
Please read the legal notices (docs/legal.txt) and the license
|
||||
(docs/license.txt) that came with this distribution before using
|
||||
this software.
|
||||
-------------------------------------------------------------------
|
||||
http://www.plainblack.com info@plainblack.com
|
||||
-------------------------------------------------------------------
|
||||
|
||||
=cut
|
||||
|
||||
our $VERSION = '0.0.1';
|
||||
use 5.010;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Carp qw(croak);
|
||||
use Cwd qw(realpath);
|
||||
use File::Spec::Functions qw(catdir splitpath catpath splitpath updir catfile);
|
||||
use Try::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Package WebGUI::Paths
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Locations for WebGUI files
|
||||
|
||||
=head1 IMPORT OPTIONS
|
||||
|
||||
=head2 -inc
|
||||
|
||||
use WebGUI::Paths -inc;
|
||||
|
||||
Loads all of the entries from the preload.custom file into @INC
|
||||
|
||||
=head2 -preload
|
||||
|
||||
Loads all of the entries from the preload.custom file into @INC,
|
||||
and loads all modules in the WebGUI namespace.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
These methods are available from this class:
|
||||
|
||||
=head2 configBase
|
||||
|
||||
Returns the base directory for WebGUI site config files.
|
||||
|
||||
=head2 logConfig
|
||||
|
||||
Returns the file path of the log configuration file.
|
||||
|
||||
=head2 spectreConfig
|
||||
|
||||
Returns the file path of the Spectre configuration file.
|
||||
|
||||
=head2 preloadCustom
|
||||
|
||||
Returns the file path of the preload.custom file to use.
|
||||
|
||||
=head2 preloadExclusions
|
||||
|
||||
Returns the file path of the preload.exclude file to use.
|
||||
|
||||
=head2 upgrades
|
||||
|
||||
Returns the base directory that contains the upgrade scripts.
|
||||
|
||||
=head2 extras
|
||||
|
||||
Returns the base directory of the WebGUI extra web files.
|
||||
|
||||
=head2 defaultUploads
|
||||
|
||||
Returns the base directory of the default site uploads content.
|
||||
|
||||
=head2 defaultCreateSQL
|
||||
|
||||
Returns the file path of the default site create.sql script.
|
||||
|
||||
=head2 share
|
||||
|
||||
Returns the base directory for WebGUI auxiliary files.
|
||||
|
||||
=cut
|
||||
|
||||
BEGIN {
|
||||
my $root = realpath(catdir(
|
||||
catpath((splitpath(__FILE__))[0,1], ''), (updir) x 2
|
||||
));
|
||||
my %paths = (
|
||||
configBase => catdir($root, 'etc'),
|
||||
logConfig => catfile($root, 'etc', 'log.conf'),
|
||||
spectreConfig => catfile($root, 'etc', 'spectre.conf'),
|
||||
preloadCustom => catfile($root, 'etc', 'preload.custom'),
|
||||
preloadExclusions => catfile($root, 'etc', 'preload.exclude'),
|
||||
upgrades => catdir($root, 'share', 'upgrades'),
|
||||
extras => catdir($root, 'www', 'extras'),
|
||||
defaultUploads => catdir($root, 'www', 'uploads'),
|
||||
defaultCreateSQL => catdir($root, 'share', 'create.sql'),
|
||||
share => catdir($root, 'share'),
|
||||
defaultPSGI => catdir($root, 'share', 'site.psgi'),
|
||||
);
|
||||
for my $sub (keys %paths) {
|
||||
my $path = $paths{$sub};
|
||||
no strict;
|
||||
*{ $sub } = sub { $path };
|
||||
}
|
||||
}
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
my @invalid;
|
||||
for my $param (@_) {
|
||||
if ($param eq '-inc') {
|
||||
$class->includePreloads;
|
||||
}
|
||||
elsif ($param eq '-preload') {
|
||||
$class->preloadAll;
|
||||
}
|
||||
else {
|
||||
push @invalid, $param;
|
||||
}
|
||||
}
|
||||
if (@invalid) {
|
||||
croak 'Invalid options ' . join(', ', @invalid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
=head2 siteConfigs
|
||||
|
||||
Returns the absolute paths of all of the config files inside L</configBase> as an array.
|
||||
|
||||
=cut
|
||||
|
||||
sub siteConfigs {
|
||||
my $class = shift;
|
||||
opendir my $dh, $class->configBase;
|
||||
my @configs;
|
||||
while ( my $file = readdir $dh ) {
|
||||
my $fullPath = realpath( catfile( $class->configBase, $file ) );
|
||||
if ( -d $fullPath
|
||||
|| $file !~ /\.conf$/
|
||||
|| $fullPath eq realpath($class->logConfig)
|
||||
|| $fullPath eq realpath($class->spectreConfig) )
|
||||
{
|
||||
next;
|
||||
}
|
||||
push @configs, $fullPath;
|
||||
}
|
||||
return @configs;
|
||||
} ## end sub siteConfigs
|
||||
|
||||
=head2 preloadPaths
|
||||
|
||||
Returns the list of paths in the preload.custom file as an array.
|
||||
|
||||
=cut
|
||||
|
||||
sub preloadPaths {
|
||||
my $class = shift;
|
||||
my @paths;
|
||||
try {
|
||||
for my $path ( _readTextLines($class->preloadCustom) ) {
|
||||
if (-d $path) {
|
||||
push @paths, $path;
|
||||
}
|
||||
else {
|
||||
warn "WARNING: Not adding lib directory '$path' from "
|
||||
. $class->preloadCustom . ": Directory does not exist.\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
return @paths;
|
||||
}
|
||||
|
||||
=head2 includePreloads
|
||||
|
||||
Adds the paths from preload.custom to @INC.
|
||||
|
||||
=cut
|
||||
|
||||
sub includePreloads {
|
||||
my $class = shift;
|
||||
unshift @INC, $class->preloadPaths;
|
||||
}
|
||||
|
||||
=head2 preloadExclude
|
||||
|
||||
Returns the list of modules to exclude from preloading as an array.
|
||||
|
||||
=cut
|
||||
|
||||
sub preloadExclude {
|
||||
my $class = shift;
|
||||
my @excludes = _readTextLines($class->preloadExclusions);
|
||||
push @excludes, 'WebGUI::Upgrade', 'WebGUI::Upgrade::*';
|
||||
push @excludes, 'WebGUI::Test', 'WebGUI::Test::*';
|
||||
return @excludes;
|
||||
}
|
||||
|
||||
=head2 preloadAll
|
||||
|
||||
Preloads all of the modules in the WebGUI namespace into memory.
|
||||
|
||||
=cut
|
||||
|
||||
sub preloadAll {
|
||||
my $class = shift;
|
||||
$class->includePreloads;
|
||||
|
||||
require WebGUI::Pluggable;
|
||||
|
||||
my @exclusions = $class->preloadExclude;
|
||||
WebGUI::Pluggable::findAndLoad( 'WebGUI', {
|
||||
exclude => \@exclusions,
|
||||
onLoadFail => sub { warn sprintf "Error loading %s: %s\n", @_ },
|
||||
});
|
||||
}
|
||||
|
||||
no namespace::clean;
|
||||
sub _readTextLines {
|
||||
my $file = shift;
|
||||
my @lines;
|
||||
open my $fh, '<', $file or return;
|
||||
while (my $line = <$fh>) {
|
||||
$line =~ s/#.*//;
|
||||
$line =~ s/^\s+//;
|
||||
$line =~ s/\s+$//;
|
||||
next
|
||||
if !$line;
|
||||
push @lines, $line;
|
||||
}
|
||||
return @lines;
|
||||
}
|
||||
use namespace::clean;
|
||||
|
||||
1;
|
||||
|
|
@ -137,7 +137,7 @@ checkModule('JavaScript::Packer', '1.002' );
|
|||
checkModule('CSS::Packer', '1.000' );
|
||||
checkModule('HTML::Packer', "1.000" );
|
||||
checkModule('Business::Tax::VAT::Validation', '0.20' );
|
||||
checkModule('Crypt::SSLeay', '0.57' );
|
||||
#checkModule('Crypt::SSLeay', '0.57' );
|
||||
checkModule('Scope::Guard', '0.20' );
|
||||
checkModule('Digest::SHA', '5.47' );
|
||||
checkModule("CSS::Minifier::XS", "0.03" );
|
||||
|
|
|
|||
19
sbin/wgd
Executable file
19
sbin/wgd
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/perl
|
||||
package
|
||||
App::WGDev;
|
||||
# ABSTRACT: WebGUI Developer Utilities
|
||||
use strict;
|
||||
use warnings;
|
||||
use 5.008008;
|
||||
|
||||
use WGDev::Command;
|
||||
|
||||
our $PACKED;
|
||||
our @PACKED;
|
||||
|
||||
my $return = eval { WGDev::Command->run(@ARGV) };
|
||||
if ( my $message = $@ ) {
|
||||
$message =~ s/\n?\z/\n/msx;
|
||||
die $message;
|
||||
}
|
||||
exit( $return ? 0 : 1 );
|
||||
19
share/nginx-default-server
Normal file
19
share/nginx-default-server
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
server {
|
||||
listen 8080 default_server;
|
||||
access_log /dev/stdout main;
|
||||
index index.htm index.html;
|
||||
root /data/WebGUI/www;
|
||||
|
||||
location / {
|
||||
proxy_pass http://webgui-upstream;
|
||||
}
|
||||
|
||||
location /extras/ {
|
||||
add_header Cache-Control public;
|
||||
expires 24h;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
|
||||
location /uploads/filepump { expires max; }
|
||||
|
||||
}
|
||||
25
share/nginx-main
Normal file
25
share/nginx-main
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
worker_processes 1;
|
||||
|
||||
error_log /dev/stdout warn;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /dev/stdout main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
include /etc/nginx/streams.d/*.conf;
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
|
||||
3
share/upstream-webgui.conf
Normal file
3
share/upstream-webgui.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
upstream webgui-upstream {
|
||||
server 127.0.0.1:80;
|
||||
}
|
||||
38
www/maintenance.html
Normal file
38
www/maintenance.html
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Site Down For Maintenance</title>
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Expires" content="Sat, 01 Dec 2001 00:00:00 GMT">
|
||||
<style type="text/css">
|
||||
.text {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
font-family: georgia, verdana, helvetica, arial, sans-serif;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
top: 40%;
|
||||
width: 95%;
|
||||
text-align: center;
|
||||
text-shadow: black 2px 2px 2px; /* safari */
|
||||
filter:progid:DXImageTransform.Microsoft.dropshadow(offX=1, offY=1, color='black', positive='true') /* ie */
|
||||
}
|
||||
.header {
|
||||
font-size: 30px;
|
||||
}
|
||||
.body {
|
||||
background-color: #6974DE;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="text">
|
||||
<div class="header">Maintenance</div>
|
||||
<p> This site is currently undergoing maintenance. Please check back again shortly. </p>
|
||||
</div>
|
||||
<img src="/extras/background.jpg" border="0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 5;" />
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue