view release on metacpan or search on metacpan
0.31 2014-02-16
- Implementented Ado::Plugin::MarkdownRenderer
- Included "Semantic UI" CSS and JavaScript framework.
It will be the default UI framwework for Ado.
- Implemented simple functional documentation system (/help)
and used Ado::Plugin::MarkdownRenderer and Semantic UI
to render documentation. Added dummy "lorem ipsum" content.
- $CODENAME changed to
"ÐÑки" - U+2C01 GLAGOLITIC CAPITAL LETTER BUKY (â°)
because we have the first end-user application in Ado now.
- We started adding end-user features now.
- Added accessor 'CODENAME' to Ado.
- Added accessors 'keywords' and 'description' to Ado::Control.
- Upgraded to Mojolicious 4.81.
0.30 2014-02-02
- Fixed tests by making sure touched files have proper permissions.
- Removed unneeded t/etc.
0.29 2014-02-02
- Upgraded to Mojolicious 4.75.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
use strict;
use warnings;
use FindBin;
use File::Spec::Functions qw(catdir updir catfile);
use Cwd qw(abs_path);
_set_env();
# Start command line interface for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Ado');
sub _set_env {
$ENV{MOJO_MODE} ||= 'development';
#$ENV{MOJO_MODE}='production';
$ENV{MOJO_HOME} ||= abs_path(catdir($FindBin::Bin, updir));
$ENV{MOJO_CONFIG} ||= catfile($ENV{MOJO_HOME}, 'etc', 'ado.conf');
# Try to find a local lib
my @libs = (
etc/ado-sqlite-schema.sql view on Meta::CPAN
-- 'id of who created this user.'
created_by INTEGER REFERENCES users(id),
-- 'Who modified this user the last time?'
changed_by INTEGER REFERENCES users(id),
-- 'last modification time'
-- 'All dates are stored as seconds since the epoch(1970) in GMT. In Perl we use gmtime as object from Time::Piece'
tstamp INTEGER NOT NULL DEFAULT 0,
-- 'registration time',,
reg_date INTEGER NOT NULL DEFAULT 0,
disabled INT(1) NOT NULL DEFAULT 1,
start_date INTEGER NOT NULL DEFAULT 0,
stop_date INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX user_start_date ON users(start_date);
CREATE INDEX user_stop_date ON users(stop_date);
-- 'Which user to which group belongs'
DROP TABLE IF EXISTS user_group;
CREATE TABLE IF NOT EXISTS user_group (
-- 'ID of the user belonging to the group with group_id.'
user_id INTEGER REFERENCES users(id),
-- 'ID of the group to which the user with user_id belongs.'
group_id INTEGER REFERENCES groups(id),
$app->secrets([Mojo::Util::sha1_sum($app->moniker . $mode . $home),]);
unshift @$renderer_paths, $site_templates if -d $site_templates;
$app->controller_class("${CLASS}::Control");
$app->routes->namespaces(["${CLASS}::Control"]);
$app->plugins->namespaces(['Mojolicious::Plugin', "${CLASS}::Plugin",]);
unshift @{$app->commands->namespaces}, "${CLASS}::Command";
return $app;
}
# This method will run once at server start
sub startup {
shift->_initialise()->load_config()->load_plugins()->load_routes()->define_mime_types();
return;
}
#load ado.conf
sub load_config {
my ($app) = @_;
my $app_config = $app->home->rel_file('etc/' . $app->moniker . '.conf');
$ENV{MOJO_CONFIG} //=
-s $app_config
=encoding utf8
=head1 NAME
Ado - a rapid active commotion (framework for web-projects on Mojolicious)
=head1 SYNOPSIS
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Ado');
=head1 DESCRIPTION
L<Ado> is a framework for web-projects based on L<Mojolicious>, written in the
L<Perl programming language|http://www.perl.org/>. This is the base
application class. Ado C<ISA> L<Mojolicious>. For a more detailed description
on what Ado is and how to get started with Ado see B<L<Ado::Manual>>.
=head1 ATTRIBUTES
Ado inherits all attributes from Mojolicious and implements the following new
ones.
=head2 ado_home
Returns an instance of L<Mojo::Home> pointing to the base directory where
L<Ado> is installed.
#/where/is/your_app/rootdir
$app->home;
Returns the root directory into which $app is installed. The guessing order is
the following:
=over
=item 1. If C<$ENV{MOJO_HOME}> is defined, it is honored.
=item 2. The upper directory of the directory in which the starting executable
C<$app-E<gt>moniker> is found, e.g. C<bin/..>. This may happen to be the same
as L</ado_home>.
=item 3. Fallback to L<Mojo/home>. This is the usual behavior of any L<Mojo>
application.
=back
=head2 sessions
By default (no configuration in C<etc/ado.conf>)
a L<Mojolicious::Sessions> is returned.
=head1 METHODS
Ado inherits all methods from Mojolicious and implements
the following new ones.
=head2 startup
Sets various default paths like C<templates>, C<site_templates>, C<public>.
Defines L<Mojolicious/secrets> as sha1_sum of C<$moniker.$mode. $home>. Sets
L<Mojolicious/controller_class> and L<Mojolicious::Routes/namespaces> to
L<${CLASS}::Control>. Sets L<Mojolicious::Plugins/namespaces> to
C<['Mojolicious::Plugin', "${CLASS}::Plugin"]>. Sets
L<Mojolicious::Commands/namespaces> to C<${CLASS}::Command>. C<$CLASS> is
usually L<Ado>. Then calls the following methods in the order they are listed.
Returns void.
lib/Ado/Command/adduser.pm view on Meta::CPAN
#define some defaults
has args => sub {
my $t = time;
{ changed_by => 1,
created_by => 1,
disabled => 1,
#TODO: add funcionality for notifying users on account expiration
#TODO: document this
stop_date => $t + ONE_YEAR, #account expires after one year
start_date => $t,
login_password => rand($t) . $$ . {} . $t,
};
};
sub init {
my ($self, @args) = @_;
$self->SUPER::init();
unless (@args) { Carp::croak($self->usage); }
my $args = $self->args;
my $ret = GetOptionsFromArray(
\@args,
'u|login_name=s' => \$args->{login_name},
'p|login_password=s' => \$args->{login_password},
'e|email=s' => \$args->{email},
'g|ingroup=s' => \$args->{ingroup},
'd|disabled:i' => \$args->{disabled},
'f|first_name=s' => \$args->{first_name},
'l|last_name=s' => \$args->{last_name},
'start_date=s' => sub {
$args->{start_date} =
$_[1] ? Time::Piece->strptime('%Y-%m-%d', $_[1])->epoch : time;
},
);
# Assume an UTF-8 terminal. TODO: make this more clever
utf8::decode($args->{login_name})
if ($args->{login_name} && !utf8::is_utf8($args->{login_name}));
utf8::decode($args->{first_name})
if ($args->{first_name} && !utf8::is_utf8($args->{first_name}));
utf8::decode($args->{last_name})
lib/Ado/Command/adduser.pm view on Meta::CPAN
On the commandline C<ado adduser> accepts the following options:
'u|login_name=s' #username (mandatory)
'p|login_password=s' #the user password (optional, random is generated)
'e|email=s' #user email (mandatory)
'g|ingroup=s' #existing users can be added to other groups too
'd|disabled:i' #is user disabled? (1 by default)
'f|first_name=s' #user's first name (mandatory)
'l|last_name=s' #user's last name (mandatory)
'start_date=s' #format: %Y-%m-%d (optional, today by default)
=head1 METHODS
L<Ado::Command::adduser> inherits all methods from
L<Ado::Command> and implements the following new ones.
=head2 init
Calls the default parent L<Ado::Command/init> and parses the arguments
passed on the command-line. Returns true on success.
lib/Ado/Command/generate/crud.pm view on Meta::CPAN
This tool's purpose is to promote
L<RAD|http://en.wikipedia.org/wiki/Rapid_application_development>
by generating the boilerplate code for controllers (C)
and help programmers new to L<Ado> and L<Mojolicious> to quickly create
well structured, fully functional applications.
In the generated actions you will find I<eventually working> code
for reading, creating, updating and deleting records from the tables you
specified on the command-line.
The generated code is just boilerplate to give you a jump start, so you can
concentrate on writing your business-specific code. It is assumed that you will
modify the generated code to suit your specific needs.
=head1 OPTIONS
Below are the options this command accepts, described in L<Getopt::Long> notation.
=head2 C|controller_namespace=s
Optional. The namespace for the controller classes to be generated.
lib/Ado/Control.pm view on Meta::CPAN
data => 'validate_input'
}
)
: (output => $v->output)
)
};
}
sub user {
my ($c, $user) = @_;
state $delete_fields = [qw(login_password created_by changed_by disabled start_date email)];
if ($user) {
# Remove as much as possible user data.
delete @{$user->data}{@$delete_fields};
$c->{user} = $user;
return $c;
}
elsif ($c->{user}) {
return $c->{user};
}
lib/Ado/Control.pm view on Meta::CPAN
return $c->render(
status => 400,
json => $result->{json}
) if $result->{errors};
=head2 user
Returns the current user. This is the user C<guest> for not authenticated
users. Note that this instance is not meant for manipulation and some fields
are not available for security reasons. The fields are: C<login_password
created_by changed_by disabled start_date>. TODO: move as much as possible
checks and fields retrieval in SQL, not in Perl.
$c->user(Ado::Model::Users->by_login_name($login_name));
my $names = $c->user->name;
=head1 SEE ALSO
L<Mojolicious::Controller>, L<Ado::Manual::Controllers>,
lib/Ado/Manual/Contributing.pod view on Meta::CPAN
Ado is a community project.
Changes and improvements need to be
discussed and well understood by the pumpkin-holder and at least
two other members of the core development team before proceeding
to implementation. We follow
L<Lean principles|https://en.wikipedia.org/wiki/Lean_software_development>
of software development.
=head1 RULES
This document still evolves but we have to start from somewhere...
The rules outlined in L<Mojolicious::Guides::Contributing>
apply for L<Ado> too.
For specific to L<Ado> rules see below.
=head2 PERL STYLE
The code must look uniformly as if it is written by one person.
No matter if you write code for the L<Ado> core or you write a plugin,
your code must be passed through perltidy using the C<./Build perltidy>
lib/Ado/Manual/Intro.pod view on Meta::CPAN
=encoding utf8
=head1 NAME
Ado::Manual::Intro - General overview
=head1 What is Ado?
Ado was started in November 2013 as a rewrite of a previous project
(L<MYDLjE|https://github.com/kberov/MYDLjE>) based on Mojolicious 1.9x. MYDLjE
was too monolithic. It was not possible to start with minimum features,
disable some of them and re-enable them only if needed. Ado is much more
modular and flexible than MYDLjE and its name is not an acronym :).
Ado's purpose is the same as of MYDLjE â to quickly put together a lightweight
web application and/or site based on Mojolicious with scalability, performance
and growth in mind. An Ado system starts as a minimal application that can
turn into an ERP, a CMS, a CRM or all in one by just adding plugins along the
way as the organization which is using it grows.
It comes with default configuration file C<etc/ado.conf> and a model
L<Ado::Model>, loaded at L<Ado/startup> by L<Mojolicious::Plugin::DSC>. An
SQLite database is bundled with the distribution at C<etc/ado.sqlite> to get
started quickly.
Ado provides additional L<plugins|Ado::Plugin> and L<commands|Ado::Command>,
which promote
L<RAD|http://en.wikipedia.org/wiki/Rapid_application_development>, good
practices, and team-work. The default Ado page uses L<Semantic UI|http
://semantic-ui.com/> via L<Mojolicious::Plugin::SemanticUI> and is a good
place to get acquainted. In short, Ado can be used right away as a
L<CMS|http://en.wikipedia.org/wiki/Content_management_system> that can be
extended with L<plugins|Ado::Manual::Plugins> and L<commands|Ado::Command> or
as a L<CMF|http://en.wikipedia.org/wiki/List_of_content_management_frameworks>
on which to build many different specific applications.
Here is the directory structure. It does not contain the files for brevity. It
looks much the same as a full Mojolicious application would look and as
described in L<Mojolicious::Guides::Growing/Differences>. One noticeable
difference is that Ado has it's starter script in C<bin> instead of in
C<script> and this is only to ease the deployment. Another one is the
L<Ado::Control> namespace instead of Ado::Controlller. We did this for
brevity. The third difference is C<site_templates>, used for putting Ado
system-templates which you want to override, e.g put our own copy of
C<templates/partials/head.html.ep> in C<site_templates/partials/head.html.ep>
and modify it. This way you can create your own themes without fearing that on
the next upgrade your changes may be lost.
Ado # Application directory
âââ bin # Script directory
lib/Ado/Manual/Plugins.pod view on Meta::CPAN
=encoding utf8
=head1 NAME
Ado::Manual::Plugins - Ado plugins and how to write an Ado::Plugin
=head1 DESCRIPTION
C<@Ado::ISA=qw(Mojolicious)>. It is distributed together with a few plugins to
make it usable as a basic Mojolicious application. Theoretically all of the
plugins, distributed with L<Ado> could be disabled so you can start your project
only as a bare (I<but full>) L<Mojolicious> application, if you wish. Later you
can decide to enable some of them and eventually add (your own) L<Mojolicious>
or L<Ado> plugins. Here is how it looks.
=for HTML <img src="https://raw.githubusercontent.com/kberov/Ado/master/public/img/Ado-Building-Blocks.png" />
=head1 PLUGINS
Ado comes with the following default plugins. They can be used as examples and
for inspiration.
lib/Ado/Manual/Plugins.pod view on Meta::CPAN
â  âââ create.html.ep
â  âââ delete.html.ep
â  âââ list.html.ep
â  âââ read.html.ep
âââ t
âââ plugin
âââ blog-00.t
No worries, your plugin has everything needed to be installed from CPAN.
L<Ado::Plugin::Vest> was started using this command.
L<Ado> can be stripped down to a bare Mojolicious application by not loading
any plugins. And L<Ado> can be extended I<infinitely> just by adding helpers,
conditions, routes, templates and injecting code into hooks from plugins.
I<This is true for any Mojolicious application.>
=head1 SPONSORS
The original author
lib/Ado/Model/Users.pm view on Meta::CPAN
sub is_base_class { return 0 }
my $CLASS = __PACKAGE__;
my $TABLE_NAME = 'users';
sub TABLE { return $TABLE_NAME }
sub PRIMARY_KEY { return 'id' }
my $COLUMNS = [
'id', 'group_id', 'login_name', 'login_password',
'first_name', 'last_name', 'email', 'description',
'created_by', 'changed_by', 'tstamp', 'reg_date',
'disabled', 'start_date', 'stop_date'
];
sub COLUMNS { return $COLUMNS }
my $ALIASES = {};
sub ALIASES { return $ALIASES }
my $CHECKS = {
'changed_by' => {'allow' => qr/(?^x:^\d{1,}$)/},
'disabled' => {
'required' => 1,
lib/Ado/Model/Users.pm view on Meta::CPAN
'required' => 1,
'defined' => 1,
'allow' => $Email::Address::addr_spec
},
'group_id' => {'allow' => qr/(?^x:^-?\d{1,}$)/},
'reg_date' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^-?\d{1,}$)/
},
'start_date' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^-?\d{1,}$)/
},
'id' => {'allow' => qr/(?^x:\d{1,}$)/},
'login_name' => {
'required' => 1,
'defined' => 1,
'allow' => qr/(?^x:^.{1,100}$)/
},
lib/Ado/Model/Users.pm view on Meta::CPAN
last_name => $args->{last_name},
login_name => $args->{login_name},
login_password => $args->{login_password},
email => $args->{email},
disabled => $args->{disabled},
tstamp => time,
reg_date => time,
created_by => $args->{created_by},
changed_by => $args->{changed_by},
stop_date => $args->{stop_date},
start_date => $args->{start_date},
description => $args->{description},
group_id => $group->id,
);
#And link them additionally
Ado::Model::UserGroup->create(
user_id => $user->id,
group_id => $group->id
);
$dbix->commit;
lib/Ado/Model/Users.pm view on Meta::CPAN
$CLASS->SQL('user_id_by_group_name' => <<"UG");
SELECT user_id FROM user_group WHERE group_id =
(SELECT id FROM groups WHERE name = ?)
UG
$CLASS->SQL('by_group_name' => <<"SQL");
SELECT id, login_name, first_name, last_name, email
FROM ${\ $CLASS->TABLE }
WHERE id IN(${\ $CLASS->SQL('user_id_by_group_name') })
AND (disabled=0 AND (stop_date>? OR stop_date=0) AND start_date<?)
ORDER BY first_name, last_name ASC
SQL
#Selects users belonging to a group only.
#returns a list of hashes
sub by_group_name {
my ($class, $group, $limit, $offset) = @_;
state $SQL = $class->SQL('by_group_name') . $CLASS->SQL_LIMIT('?', '?');
lib/Ado/Model/Users.pm view on Meta::CPAN
=head2 created_by
=head2 changed_by
=head2 tstamp
=head2 reg_date
=head2 disabled
=head2 start_date
=head2 stop_date
=head1 ALIASES
none
=head1 METHODS
Ado::Model::Users inherits all methods from Ado::Model and provides the
lib/Ado/Model/Users.pm view on Meta::CPAN
=head2 by_email
Selects a user by email column.
my $user = Ado::Model::Users->by_email('user@example.com');
say $user->email if $user->id;
=head2 by_group_name
Selects active users (C<WHERE (disabled=0 AND (stop_date>$now OR stop_date=0)
AND start_date<$now )>) belonging to a given group only and within a given
range, ordered by C<first_name, last_name> alphabetically. C<$limit> defaults
to 500 and C<$offset> to 0. Only the following fields are retrieved: C<id,
login_name, first_name, last_name, email>.
Returns an array of hashes. The L</name> method is executed for each row in
the resultset and the evaluation is available via key 'name'.
#get contacts of the user 'berov'
my @users = Ado::Model::Users->by_group_name('vest_contacts_for_berov', $limit, $offset);
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
$c->redirect_to('/');
}
return;
}
sub _authenticate_oauth2_user {
my ($c, $user, $time) = @_;
if ( $user->disabled
|| ($user->stop_date != 0 && $user->stop_date < $time)
|| $user->start_date > $time)
{
$c->flash(login_message => $c->l('oauth2_disabled'));
$c->redirect_to('/');
return;
}
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
return 1;
}
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
else {
Carp::croak('Unknown provider info_url:' . $provider->{info_url});
}
$args{email} = $ui->{email};
$args{login_name} = $ui->{email};
$args{login_name} =~ s/[\@\.]+//g;
$args{login_password} =
Mojo::Util::sha1_hex($args{login_name} . Ado::Sessions->generate_id());
$args{description} = "Registered via $provider->{info_url}!";
$args{created_by} = $args{changed_by} = 1;
$args{start_date} = $args{disabled} = $args{stop_date} = 0;
return %args;
}
1;
=pod
=encoding utf8
lib/Ado/Plugin/I18n.pm view on Meta::CPAN
% language('bg');
=head1 TEMPLATES
L<Ado::Plugin::I18n> contains one embedded template.
=head2 partials/language_menu.html.ep
Generates HTML for a language menu.
If you want to modify the template you can inflate all templates and do that.
A usage example can be found at L<http://localhost:3000> after starting ado.
berov@u165:~/opt/public_dev/Ado$ bin/ado inflate
...
[exist] /home/berov/opt/public_dev/Ado/templates/partials
[write] /home/berov/opt/public_dev/Ado/templates/partials/language_menu.html.ep
#then choose the preferred way to switch languages...
%= include 'partials/language_menu'; # use default language_from => 'route'
%= include 'partials/language_menu', language_from => 'route';
%= include 'partials/language_menu', language_from => 'host';
lib/Ado/Plugin/I18n.pm view on Meta::CPAN
%# It Displays menu items with flags.
%# You can experiment and make it as one dropdown menu item.
%# See http://localhost:3000/perldoc/Ado/Plugin/I18n#partialslanguage_menuhtmlep
% my $stash = $self->stash;
% my $conf = config('Ado::Plugin::I18n');
% my @languages = @{$conf->{languages}};
% $language_from ||= 'route';
% #$c->debug('$language_from:' . $language_from);
% $language ||= $conf->{default_language};
<!-- language_menu start -->
<!-- language_from: <%=$language_from%> -->
<% head_css([$sui_path.'/menu.min.css', $sui_path.'/dropdown.min.css',
$sui_path.'/item.min.css',$sui_path.'/icon.min.css',
$sui_path.'/button.min.css']);
head_javascript($sui_path.'/dropdown.min.js'); %>
<div class="right compact menu" id="language_menu">
<div class="ui simple dropdown item">
<i class="translate icon"></i><%=l('Translate') %>
<div class="menu">
public/doc/bg/img/default_admin_screen.epz view on Meta::CPAN
<?xml version="1.0"?>
<Document xmlns="http://www.evolus.vn/Namespace/Pencil"><Properties/><Pages><Page><Properties><Property name="name">Untitled Page</Property><Property name="id">1391472647609_5759</Property><Property name="width">980</Property><Property name="height">...
]]></p:property><p:property name="textFont"><![CDATA[Arial|normal|normal|13px|none]]></p:property><p:property name="textColor"><![CDATA[#000000FF]]></p:property><p:property name="textAlign"><![CDATA[1,1]]></p:property></p:metadata>
<defs>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" p:name="linearFill" id="36be4582de964e90968b9d4a8d0823fa">
<stop style="stop-color: rgb(192, 192, 192); stop-opacity: 1;" offset="0" p:name="stop1" id="97ba4839a4a8438c832d3b69c28106b0"/>
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="1" p:name="stop2" id="4f79e103cdb646b8a85a0656bf5af6c8"/>
</linearGradient>
<rect width="1025" height="75" rx="0" ry="0" x="0" y="0" style="stroke-width: 2; fill: url("#36be4582de964e90968b9d4a8d0823fa") none; stroke: rgb(27, 50, 128); stroke-opacity: 1;" p:name="rrRect" id="65fcfb10486244958422f3bb...
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="shadingFilter" id="36fe3410734e42e0a6254778df8c1601">
<feGaussianBlur stdDeviation="1" in="SourceAlpha"/>
public/doc/en/intro.md view on Meta::CPAN
#Intro
<div class="ui hidden">
If you read this page out of /help/en/* the links in it will not work!
</div>
##Ado
A framework for web projects based on [Mojolicious](http://mojolicio.us/), written in the Perl programming language.
Ado[^ado_] was started in November 2013 as a rewrite of a previous project ([MYDLjE](https://github.com/kberov/MYDLjE)) based on Mojolicious 1.9x. MYDLjE was too monolithic. It was not possible to start with minimum features, disable some of them and...
##What is Ado?
Ado's purpose is the same as of MYDLjE â to quickly put together a lightweight web application and/or site based on Mojolicious with scalability, performance and growth in mind.
An Ado system starts as a minimal application that can turn into an ERP, a CMS, a CRM or all in one by just adding plugins along the way as the organization which is using it grows.
##Built-in features
Ado is a typical Mojo application. It comes with a configuration file and a model[^2] layer - Mojolicious::Plugin::DSC. An SQLite database is bundled in the distribution at etc/ado.sqlite to get started quickly. All plugins can be disabled and re-ena...
Ado has the following:
1. Configuration file with most of the sensible settings in place, such as controller_class, name-spaces for routes (urls), name-spaces for plugins and commands, session settings, default routes...
2. Ado plugins work the same way as Mojolicious::Plugins and share the same common base trough Ado::Plugin. But they have one small additional feature. They can load their own configuration from `$ENV{MOJO_HOME}/etc/plugins/plugin_name.conf`. Busines...
By default the following plugins are enabled:
1. All Mojolicious plugins which are otherwise enabled by default.
2. Mojolicious::Plugin::Charset â UTF-8.
3. Mojolicious::Plugin::DSC â a plugin which integrates DBIx::Simple::Class in the application. DBIx::Simple::Class is a very lightweight object-relational mapper based on DBIx::Simple. It abstracts the SQL from the programmer still allowing to...
4. Ado::Plugin::Auth is a plugin that authenticates users to an Ado system. Users can be authenticated locally or using (TODO!) Facebook, Google, Twitter and other authentication service-providers. A pre-made login form can be used directly or as a...
public/index.html view on Meta::CPAN
<html>
<head>
<title>Welcome to Ado!</title>
</head>
<body>
<h2>Welcome to <a href="/perldoc/Ado/Manual">Ado</a></h2>
<p>Welcome to <a href="/perldoc/Ado/Manual">Ado</a> - a framework
for web projects written in the <a href="http://www.perl.org">Perl programming language</a> and based on the <a href="/perldoc/Mojolicious/Guides">Mojolicious</a>
real-time web framework!</p>
<p>This is the static document "public/index.html",
<a href="/">click here</a> to get back to the start.</p>
</body>
</html>
public/vendor/pagedown/Markdown.Converter.js view on Meta::CPAN
addFalse: function (hookname) {
this[hookname] = returnFalse;
}
};
Markdown.HookCollection = HookCollection;
// g_urls and g_titles allow arbitrary user-entered strings as keys. This
// caused an exception (and hence stopped the rendering) when the user entered
// e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this
// (since no builtin property starts with "s_"). See
// http://meta.stackoverflow.com/questions/64655/strange-wmd-bug
// (granted, switching from Array() to Object() alone would have left only __proto__
// to be a problem)
function SaveHash() { }
SaveHash.prototype = {
set: function (key, value) {
this["s_" + key] = value;
},
get: function (key) {
return this["s_" + key];
public/vendor/pagedown/Markdown.Converter.js view on Meta::CPAN
var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
// First, look for nested blocks, e.g.:
// <div>
// <div>
// tags for inner block must be indented.
// </div>
// </div>
//
// The outermost tags must start at the left margin for this to match, and
// the inner nested divs must be indented.
// We need to do this before the next, more liberal match, because the next
// match will start at the first `<div>` and stop at the first `</div>`.
// attacklab: This regex can be expensive when it fails.
/*
text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_a) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*?\n // any number of lines, minimally matching
</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}};
*/
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
//
// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
//
/*
text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_b) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*? // any number of lines, minimally matching
.*</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}};
*/
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
// Special case just for <hr />. It was easier to make a special case than
// to make the other regex more complicated.
/*
text = text.replace(/
\n // Starting after a blank line
[ ]{0,3}
( // save in $1
(<(hr) // start tag = $2
\b // word break
([^<>])*?
\/?>) // the matching end tag
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,hashElement);
*/
text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
public/vendor/pagedown/Markdown.Converter.js view on Meta::CPAN
// Each time we enter a list, we increment it; when we leave a list,
// we decrement. If it's zero, we're not in a list anymore.
//
// We do this because when we're not inside a list, we want to treat
// something like this:
//
// I recommend upgrading to version
// 8. Oops, now this line is treated
// as a sub-list.
//
// As a single paragraph, despite the fact that the second line starts
// with a digit-period-space sequence.
//
// Whereas when we're inside a list (or sub-list), that line will be
// treated as the start of a sub-list. What a kludge, huh? This is
// an aspect of Markdown's syntax that's hard to parse perfectly
// without resorting to mind-reading. Perhaps the solution is to
// change the syntax rules such that sub-lists must start with a
// starting cardinal number; e.g. "1." or "a.".
g_list_level++;
// trim trailing blank lines:
list_str = list_str.replace(/\n{2,}$/, "\n");
// attacklab: add sentinel to emulate \z
list_str += "~0";
// In the original attacklab showdown, list_type was not given to this function, and anything
public/vendor/pagedown/Markdown.Converter.js view on Meta::CPAN
}
function _DoCodeBlocks(text) {
//
// Process Markdown `<pre><code>` blocks.
//
/*
text = text.replace(/
(?:\n\n|^)
( // $1 = the code block -- one or more lines, starting with a space/tab
(?:
(?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
.*\n+
)+
)
(\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
/g ,function(){...});
*/
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
public/vendor/pagedown/Markdown.Converter.js view on Meta::CPAN
return text;
}
function _DoBlockQuotes(text) {
/*
text = text.replace(/
( // Wrap whole match in $1
(
^[ \t]*>[ \t]? // '>' at the start of a line
.+\n // rest of the first line
(.+\n)* // subsequent consecutive lines
\n* // blanks
)+
)
/gm, function(){...});
*/
text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
function (wholeMatch, m1) {
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
// `defaultStrings` above, so you can just override some string displayed
// to the user on a case-by-case basis, or translate all strings to
// a different language.
//
// For backwards compatibility reasons, the `options` argument can also
// be just the `helpButton` object, and `strings.help` can also be set via
// `helpButton.title`. This should be considered legacy.
//
// The constructed editor object has the methods:
// - getConverter() returns the markdown converter object that was passed to the constructor
// - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
// - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
Markdown.Editor = function (markdownConverter, idPostfix, options) {
options = options || {};
if (typeof options.handler === "function") { //backwards compatible behavior
options = { helpButton: options };
}
options.strings = options.strings || {};
if (options.helpButton) {
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
forceRefresh();
};
}
// before: contains all the text in the input box BEFORE the selection.
// after: contains all the text in the input box AFTER the selection.
function Chunks() { }
// startRegex: a regular expression to find the start tag
// endRegex: a regular expresssion to find the end tag
Chunks.prototype.findTags = function (startRegex, endRegex) {
var chunkObj = this;
var regex;
if (startRegex) {
regex = util.extendRegExp(startRegex, "", "$");
this.before = this.before.replace(regex,
function (match) {
chunkObj.startTag = chunkObj.startTag + match;
return "";
});
regex = util.extendRegExp(startRegex, "^", "");
this.selection = this.selection.replace(regex,
function (match) {
chunkObj.startTag = chunkObj.startTag + match;
return "";
});
}
if (endRegex) {
regex = util.extendRegExp(endRegex, "", "$");
this.selection = this.selection.replace(regex,
function (match) {
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
var regexText;
var replacementText;
// chrome bug ... documented at: http://meta.stackoverflow.com/questions/63307/blockquote-glitch-in-editor-in-chrome-6-and-7/65985#65985
if (navigator.userAgent.match(/Chrome/)) {
"X".match(/()./);
}
this.selection = this.selection.replace(/(^\n*)/, "");
this.startTag = this.startTag + re.$1;
this.selection = this.selection.replace(/(\n*$)/, "");
this.endTag = this.endTag + re.$1;
this.startTag = this.startTag.replace(/(^\n*)/, "");
this.before = this.before + re.$1;
this.endTag = this.endTag.replace(/(\n*$)/, "");
this.after = this.after + re.$1;
if (this.before) {
regexText = replacementText = "";
while (nLinesBefore--) {
regexText += "\\n?";
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
// operation.
this.setInputAreaSelection = function () {
if (!util.isVisible(inputArea)) {
return;
}
if (inputArea.selectionStart !== undefined && !uaSniffed.isOpera) {
inputArea.focus();
inputArea.selectionStart = stateObj.start;
inputArea.selectionEnd = stateObj.end;
inputArea.scrollTop = stateObj.scrollTop;
}
else if (doc.selection) {
if (doc.activeElement && doc.activeElement !== inputArea) {
return;
}
inputArea.focus();
var range = inputArea.createTextRange();
range.moveStart("character", -inputArea.value.length);
range.moveEnd("character", -inputArea.value.length);
range.moveEnd("character", stateObj.end);
range.moveStart("character", stateObj.start);
range.select();
}
};
this.setInputAreaSelectionStartEnd = function () {
if (!panels.ieCachedRange && (inputArea.selectionStart || inputArea.selectionStart === 0)) {
stateObj.start = inputArea.selectionStart;
stateObj.end = inputArea.selectionEnd;
}
else if (doc.selection) {
stateObj.text = util.fixEolChars(inputArea.value);
// IE loses the selection in the textarea when buttons are
// clicked. On IE we cache the selection. Here, if something is cached,
// we take it.
var range = panels.ieCachedRange || doc.selection.createRange();
var fixedRange = util.fixEolChars(range.text);
var marker = "\x07";
var markedRange = marker + fixedRange + marker;
range.text = markedRange;
var inputText = util.fixEolChars(inputArea.value);
range.moveStart("character", -markedRange.length);
range.text = fixedRange;
stateObj.start = inputText.indexOf(marker);
stateObj.end = inputText.lastIndexOf(marker) - marker.length;
var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
if (len) {
range.moveStart("character", -fixedRange.length);
while (len--) {
fixedRange += "\n";
stateObj.end += 1;
}
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
inputArea.value = stateObj.text;
}
this.setInputAreaSelection();
inputArea.scrollTop = stateObj.scrollTop;
};
// Gets a collection of HTML chunks from the inptut textarea.
this.getChunks = function () {
var chunk = new Chunks();
chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
chunk.startTag = "";
chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
chunk.endTag = "";
chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
chunk.scrollTop = stateObj.scrollTop;
return chunk;
};
// Sets the TextareaState properties given a chunk of markdown.
this.setChunks = function (chunk) {
chunk.before = chunk.before + chunk.startTag;
chunk.after = chunk.endTag + chunk.after;
this.start = chunk.before.length;
this.end = chunk.before.length + chunk.selection.length;
this.text = chunk.before + chunk.selection + chunk.after;
this.scrollTop = chunk.scrollTop;
};
this.init();
};
function PreviewManager(converter, panels, previewRefreshCallback) {
var managerObj = this;
var timeout;
var elapsedTime;
var oldInputText;
var maxDelay = 3000;
var startType = "delayed"; // The other legal value is "manual"
// Adds event listeners to elements
var setupEvents = function (inputElem, listener) {
util.addEvent(inputElem, "input", listener);
inputElem.onpaste = listener;
inputElem.ondrop = listener;
util.addEvent(inputElem, "keypress", listener);
util.addEvent(inputElem, "keydown", listener);
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
};
// setTimeout is already used. Used as an event listener.
var applyTimeout = function () {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
if (startType !== "manual") {
var delay = 0;
if (startType === "delayed") {
delay = elapsedTime;
}
if (delay > maxDelay) {
delay = maxDelay;
}
timeout = setTimeout(makePreviewHtml, delay);
}
};
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
return title ? link + ' "' + title + '"' : link;
});
}
commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) {
chunk.trimWhitespace();
chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
var background;
if (chunk.endTag.length > 1 && chunk.startTag.length > 0) {
chunk.startTag = chunk.startTag.replace(/!?\[/, "");
chunk.endTag = "";
this.addLinkDef(chunk, null);
}
else {
// We're moving start and end tag back into the selection, since (as we're in the else block) we're not
// *removing* a link, but *adding* one, so whatever findTags() found is now back to being part of the
// link text. linkEnteredCallback takes care of escaping any brackets.
chunk.selection = chunk.startTag + chunk.selection + chunk.endTag;
chunk.startTag = chunk.endTag = "";
if (/\n\n/.test(chunk.selection)) {
this.addLinkDef(chunk, null);
return;
}
var that = this;
// The function to be executed when you enter a link and press OK or Cancel.
// Marks up the link and adds the ref.
var linkEnteredCallback = function (link) {
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
// (?= followed by
// [[\]] an opening or closing bracket
// )
//
// In other words, a non-escaped bracket. These have to be escaped now to make sure they
// don't count as the end of the link or similar.
// Note that the actual bracket has to be a lookahead, because (in case of to subsequent brackets),
// the bracket in one match may be the "not a backslash" character in the next match, so it
// should not be consumed by the first match.
// The "prepend a space and finally remove it" steps makes sure there is a "not a backslash" at the
// start of the string, so this also works if the selection begins with a bracket. We cannot solve
// this by anchoring with ^, because in the case that the selection starts with two brackets, this
// would mean a zero-width match at the start. Since zero-width matches advance the string position,
// the first bracket could then not act as the "not a backslash" for the second.
chunk.selection = (" " + chunk.selection).replace(/([^\\](?:\\\\)*)(?=[[\]])/g, "$1\\").substr(1);
var linkDef = " [999]: " + properlyEncoded(link);
var num = that.addLinkDef(chunk, linkDef);
chunk.startTag = isImage ? "![" : "[";
chunk.endTag = "][" + num + "]";
if (!chunk.selection) {
if (isImage) {
chunk.selection = that.getString("imagedescription");
}
else {
chunk.selection = that.getString("linkdescription");
}
}
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
// The original code uses a regular expression to find out how much of the
// text *directly before* the selection already was a blockquote:
/*
if (chunk.before) {
chunk.before = chunk.before.replace(/\n?$/, "\n");
}
chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
function (totalMatch) {
chunk.startTag = totalMatch;
return "";
});
*/
// This comes down to:
// Go backwards as many lines a possible, such that each line
// a) starts with ">", or
// b) is almost empty, except for whitespace, or
// c) is preceeded by an unbroken chain of non-empty lines
// leading up to a line that starts with ">" and at least one more character
// and in addition
// d) at least one line fulfills a)
//
// Since this is essentially a backwards-moving regex, it's susceptible to
// catstrophic backtracking and can cause the browser to hang;
// see e.g. http://meta.stackoverflow.com/questions/9807.
//
// Hence we replaced this by a simple state machine that just goes through the
// lines and checks for a), b), and c).
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
line;
if (chunk.before) {
var lines = chunk.before.replace(/\n$/, "").split("\n");
var inChain = false;
for (var i = 0; i < lines.length; i++) {
var good = false;
line = lines[i];
inChain = inChain && line.length > 0; // c) any non-empty line continues the chain
if (/^>/.test(line)) { // a)
good = true;
if (!inChain && line.length > 1) // c) any line that starts with ">" and has at least one more character starts the chain
inChain = true;
} else if (/^[ \t]*$/.test(line)) { // b)
good = true;
} else {
good = inChain; // c) the line is not empty and does not start with ">", so it matches if and only if we're in the chain
}
if (good) {
match += line + "\n";
} else {
leftOver += match + line;
match = "\n";
}
}
if (!/(^|\n)>/.test(match)) { // d)
leftOver += match;
match = "";
}
}
chunk.startTag = match;
chunk.before = leftOver;
// end of change
if (chunk.after) {
chunk.after = chunk.after.replace(/^\n?/, "\n");
}
chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
function (totalMatch) {
chunk.endTag = totalMatch;
return "";
}
);
var replaceBlanksInTags = function (useBracket) {
var replacement = useBracket ? "> " : "";
if (chunk.startTag) {
chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
function (totalMatch, markdown) {
return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
});
}
if (chunk.endTag) {
chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
function (totalMatch, markdown) {
return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
});
}
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
this.wrap(chunk, SETTINGS.lineLength - 2);
chunk.selection = chunk.selection.replace(/^/gm, "> ");
replaceBlanksInTags(true);
chunk.skipLines();
} else {
chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
this.unwrap(chunk);
replaceBlanksInTags(false);
if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
}
if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
}
}
chunk.selection = this.hooks.postBlockquoteCreation(chunk.selection);
if (!/\n/.test(chunk.selection)) {
chunk.selection = chunk.selection.replace(/^(> *)/,
function (wholeMatch, blanks) {
chunk.startTag += blanks;
return "";
});
}
};
commandProto.doCode = function (chunk, postProcessing) {
var hasTextBefore = /\S[ ]*$/.test(chunk.before);
var hasTextAfter = /^[ ]*\S/.test(chunk.after);
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
nLinesBack = 0;
}
if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
nLinesForward = 0;
}
chunk.skipLines(nLinesBack, nLinesForward);
if (!chunk.selection) {
chunk.startTag = " ";
chunk.selection = this.getString("codeexample");
}
else {
if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
if (/\n/.test(chunk.selection))
chunk.selection = chunk.selection.replace(/^/gm, " ");
else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
chunk.before += " ";
}
else {
chunk.selection = chunk.selection.replace(/^(?:[ ]{4}|[ ]{0,3}\t)/gm, "");
}
}
}
else {
// Use backticks (`) to delimit the code block.
chunk.trimWhitespace();
chunk.findTags(/`/, /`/);
if (!chunk.startTag && !chunk.endTag) {
chunk.startTag = chunk.endTag = "`";
if (!chunk.selection) {
chunk.selection = this.getString("codeexample");
}
}
else if (chunk.endTag && !chunk.startTag) {
chunk.before += chunk.endTag;
chunk.endTag = "";
}
else {
chunk.startTag = chunk.endTag = "";
}
}
};
commandProto.doList = function (chunk, postProcessing, isNumberedList) {
// These are identical except at the very beginning and end.
// Should probably use the regex extension function to make this clearer.
var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
function (_) {
return getItemPrefix();
});
return itemText;
};
chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
chunk.before += chunk.startTag;
chunk.startTag = "";
}
if (chunk.startTag) {
var hasDigits = /\d+[.]/.test(chunk.startTag);
chunk.startTag = "";
chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
this.unwrap(chunk);
chunk.skipLines();
if (hasDigits) {
// Have to renumber the bullet points if this is a numbered list.
chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
}
if (isNumberedList == hasDigits) {
return;
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
var nLinesDown = 1;
chunk.after = chunk.after.replace(nextItemsRegex,
function (itemText) {
nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
return getPrefixedItem(itemText);
});
chunk.trimWhitespace(true);
chunk.skipLines(nLinesUp, nLinesDown, true);
chunk.startTag = prefix;
var spaces = prefix.replace(/./g, " ");
this.wrap(chunk, SETTINGS.lineLength - spaces.length);
chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
};
commandProto.doHeading = function (chunk, postProcessing) {
// Remove leading/trailing whitespace and reduce internal spaces to single spaces.
chunk.selection = chunk.selection.replace(/\s+/g, " ");
chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
// If we clicked the button with no selected text, we just
// make a level 2 hash header around some default text.
if (!chunk.selection) {
chunk.startTag = "## ";
chunk.selection = this.getString("headingexample");
chunk.endTag = " ##";
return;
}
var headerLevel = 0; // The existing header level of the selected text.
// Remove any existing hash heading markdown and save the header level.
chunk.findTags(/#+[ ]*/, /[ ]*#+/);
if (/#+/.test(chunk.startTag)) {
headerLevel = re.lastMatch.length;
}
chunk.startTag = chunk.endTag = "";
// Try to get the current header level by looking for - and = in the line
// below the selection.
chunk.findTags(null, /\s?(-+|=+)/);
if (/=+/.test(chunk.endTag)) {
headerLevel = 1;
}
if (/-+/.test(chunk.endTag)) {
headerLevel = 2;
}
// Skip to the next line so we can create the header markdown.
chunk.startTag = chunk.endTag = "";
chunk.skipLines(1, 1);
// We make a level 2 header if there is no current header.
// If there is a header level, we substract one from the header level.
// If it's already a level 1 header, it's removed.
var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
if (headerLevelToCreate > 0) {
// The button only creates level 1 and 2 underline headers.
public/vendor/pagedown/Markdown.Editor.js view on Meta::CPAN
len = SETTINGS.lineLength;
}
chunk.endTag = "\n";
while (len--) {
chunk.endTag += headerChar;
}
}
};
commandProto.doHorizontalRule = function (chunk, postProcessing) {
chunk.startTag = "----------\n";
chunk.selection = "";
chunk.skipLines(2, 1, true);
}
})();
t/command/adduser.t view on Meta::CPAN
can_ok($class, 'adduser');
can_ok($class, 'usage');
can_ok($class, 'help');
};
my $opt_existing = {};
subtest 'Ado::Command::adduser/output_existing' => sub {
#user already exists
$opt_existing = {'--login_name' => 'test1'};
sub add_existing { $app->start('adduser', %$opt_existing) }
stdout_like(\&add_existing, qr/'test1' is already taken!/, 'user already exists');
#user is already in group
$opt_existing->{'--ingroup'} = 'test1';
stdout_is(
\&add_existing,
"'test1' is already taken!\nUser 'test1' is already in group 'test1'.$/",
'user is already in group'
);
};
my $opt = {};
subtest 'Ado::Command::adduser/output_insufficient_arguments' => sub {
#insufficient arguments 1
$opt = {'--login_name' => 'test3'};
sub add { $app->start('adduser', %$opt) }
output_like(
\&add,
qr/Minimal req.+/,
qr/ERROR adding user\(rolling back\):/sm,
'insufficient arguments 1'
);
#insufficient arguments 2
$opt->{'--email'} = 'test3@localhost';
output_like(
t/command/adduser.t view on Meta::CPAN
#subtest 'Ado::Command::adduser/ouput_invalid_arguments' =>
my $opt_ = {
'--login_name' => 'test3' . (1 x 96),
'--email' => 'test3atlocalhost',
'-f' => 'First',
'-l' => 'Last',
'-p' => 'asdasd',
};
subtest 'Ado::Command::adduser/stderr_invalid_arguments' => \&stderr_invalid_arguments;
sub add_ { $app->start('adduser', %$opt_) }
sub stderr_invalid_arguments {
stderr_like(\&add_, qr/ERROR adding user.+Key 'name'/sm, 'invalid group name');
#TODO: Add user friendly error messages when creating a user.
# and find why sometime with invalid arguments, user gets created
}
#Going deeper
subtest 'Ado::Command::adduser/direct_usage' => \&direct_usage;
t/command/adoplugin-00.t view on Meta::CPAN
my $command = 'Ado::Command::generate::adoplugin';
use_ok($command);
my $dir = getcwd;
my $tempdir = tempdir(CLEANUP => 1);
chdir $tempdir;
my $name = 'MyBlog';
my $class = "Ado::Plugin::$name";
my $decamelized = decamelize($name);
ok(my $c = $app->start("generate", "adoplugin", '-n' => $name), 'run() ok');
my $class_file = path(catfile($tempdir, "Ado-Plugin-$name/lib/Ado/Plugin", "$name.pm"))->slurp();
my $test_file =
path(catfile($tempdir, "Ado-Plugin-$name/t/plugin", "$decamelized-00.t"))->slurp();
my $build_file = path(catfile($tempdir, "Ado-Plugin-$name/Build.PL"))->slurp();
my $config_file =
path(catfile($tempdir, "Ado-Plugin-$name/etc/plugins", "$decamelized.conf"))->slurp();
like($class_file => qr/register.+initialise/sm, 'Class code is ok');
like($class_file => qr/$class - an A.+foooooo/, 'Class documentation is ok');
like($test_file => qr/$class.+isa_ok/sm, 'Test file is ok');
like($build_file => qr/Ado::BuildPlugin.+$class/sm, 'Build.PL file is ok');
t/command/adoplugin-00.t view on Meta::CPAN
/smx, 'warnings from Build.PL ok';
warn @_;
};
require_ok("Build.PL");
}
#not CamelCase name
$name = 'blog';
$class = "Ado::Plugin::$name";
ok($c = $app->start("generate", "adoplugin", '-n' => $name), 'run() ok');
#failing --crud
like(
(eval { $app->start("generate", "adoplugin", '-n' => $name, '-c' => 1) } || $@),
qr/--tables is mandatory when/,
'--tables is mandatory when option --crud is passed!'
);
#failin without --name
like(
(eval { $app->start("generate", "adoplugin") } || $@),
qr/On the command-line:/,
'--name is mandatory'
);
chdir $dir;
done_testing();
t/command/adoplugin-01.t view on Meta::CPAN
use_ok($class);
isa_ok(my $plugin = $class->new->register($t->app, {'аз' => 'Ñи'}), 'Ado::Plugin', $name);
ok($plugin->home_dir =~ /Ado-Plugin-$name$/, '$plugin->home_dir ends with ' . "Ado-Plugin-$name");
is( $plugin->config_dir,
catdir($tempdir, "Ado-Plugin-$name", 'etc', 'plugins'),
'right config directory'
);
# Remove generic routes (for this test only) so newly generated routes can match.
# In a normal application startup generic routes will always match last.
# It is expected all plugins to load their routes by the time the generic routes load.
# This means it is ok to keep them in ado.conf
#http://localhost:3000/perldoc/Mojolicious/Guides/Routing#Rearranging-routes
$ado->routes->find('controller')->remove();
$ado->routes->find('controlleraction')->remove();
$ado->routes->find('controlleractionid')->remove();
# $ado->start("routes");
# Test generated routes
$t->get_ok('/testatii.json')->status_is(200);
$t->get_ok('/testatii/list?format=html')->status_is(200)
->content_like(qr|table.+id</th>.+permissions</th.+Hello</td.+Hello2|smx);
# needs login
$t->post_ok(
'/testatii/create.html' => form => {
title => 'Hello3',
body =>
t/command/apache2htaccess.t view on Meta::CPAN
my $IS_DOS = ($^O eq 'MSWin32' or $^O eq 'dos' or $^O eq 'os2');
#plan skip_all => 'Not reliable test under this platform.' if $IS_DOS;
my $t = Test::Mojo->new('Ado');
my $app = $t->app;
my $command = 'Ado::Command::generate::apache2htaccess';
my $tempdir = tempdir(CLEANUP => 1);
my $config_file = catfile($tempdir, '.htaccess');
use_ok($command);
ok( my $c = $app->start("generate", "apache2htaccess", '-M' => 'cgi,fcgid', '-c' => $config_file),
'run() ok'
);
isa_ok($c, $command);
like($c->description, qr/Apache2\s+.htaccess/, 'description looks alike');
like($c->usage, qr/generate\sapache2htaccess.*?mod_fcgid/ms, 'usage looks alike');
ok(my $config_file_content = path($config_file)->slurp, 'generated $config_file');
like($config_file_content, qr/<IfModule\s+mod_cgi.+?"\^\(ado\)\$"/msx, 'mod_cgi block produced');
like($config_file_content, qr/<IfModule\s+mod_fcgid\.c/msx, 'mod_fcgid block produced');
t/command/apache2vhost.t view on Meta::CPAN
my $t = Test::Mojo->new('Ado');
my $app = $t->app;
my $command = 'Ado::Command::generate::apache2vhost';
my $tempdir = tempdir(CLEANUP => 1);
my $config_file = catfile($tempdir, 'example.com.conf');
use_ok($command);
ok( my $c = $app->start(
"generate", "apache2vhost",
'-n' => 'example.com',
'-c' => $config_file,
'-s'
),
'run() ok'
);
isa_ok($c, $command);
t/plugin/auth-02.t view on Meta::CPAN
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
use File::Find;
my $t = Test::Mojo->new('Ado');
my $ado = $t->app;
#Plugins are loaded already.
# Remove generic routes (for this test only) so newly generated routes can match.
# In a normal application startup generic routes will always match last.
# It is expected all plugins to load their routes by the time the generic routes load.
# This means it is ok to keep them in ado.conf
#http://localhost:3000/perldoc/Mojolicious/Guides/Routing#Rearranging-routes
$ado->routes->find('controller')->remove();
$ado->routes->find('controlleraction')->remove();
$ado->routes->find('controlleractionid')->remove();
$ado->routes->get('/test/ingroup')->over(ingroup => 'test1')->to('test#ingroup');
my $csrf_token =
$t->get_ok('/login')->tx->res->dom->at('#login_form [name="csrf_token"]')->{value};
templates/default/index_text.html.ep view on Meta::CPAN
Logging is more verbose within this environment.
You can rename the default <code>etc/ado.conf</code> to something known to you only
and keep it for restoring the configuration to its initial state.
Alternatively you can keep it but modify only <code>etc/ado.development.conf</code>.
Both files will be read but the environment specific configuration will override the
settings from the default file.
<blockquote>
Later, when you are ready to go officially in <i>production</i> mode,
copy <code>etc/ado.development.conf</code> to <code>etc/ado.production.conf</code>,
modify the settings (paths, secrets etc) in it accordingly and do not forget to set <code>MOJO_MODE='production'</code> in your ~/.bashrc
or copy <code>bin/ado</code> to <code>bin/ado.production.pl</code>, modify it and use it to start Ado.
</blockquote>
</li>
<li>Execute <code>ado inflate</code> to dump the embedded files and templates.</li>
<li>Create a folder <code>site_templates</code> in <code><%=$home%></code>.
The folder <code>site_templates</code> will be searched for templates first,
so you can copy files from folder <code>templates</code> and modify them for your needs.
</li>
<li>In <code>site_templates</code> create a folder <code>default</code>
and create a file named <code>index.html.ep</code> in it.
The new file will be used as template instead
of the file you are reading now. The full path should look like
<code><%=$home%>/site_templates/default/index.html.ep</code>
</li>
<li>Copy <code>etc/ado.sqlite</code> to <code>etc/ado.development.sqlite</code>.
This will be your development database.
<blockquote>
Later when you are ready to go officially in <i>production</i> mode,
copy <code>etc/ado.development.sqlite</code> to <code>etc/ado.production.sqlite</code>
and do not forget to set <code>MOJO_MODE='production'</code> in your .bashrc
or copy <code>bin/ado</code> to <code>bin/ado.production.pl</code>, modify it and use it to start Ado.
</blockquote>
</li>
<li>For end-user help look at <a href="<%=url_for('/help')%>">/help</a>!</li>
<li>For development help look at <a href="<%=url_for('/perldoc')%>">/perldoc</a>!</li>
<li>Develop your site, application or both! Read and follow recommendations from <%= link_to 'Mojolicious::Guides::Growing' => '/perldoc/Mojolicious/Guides/Growing', target=>'_blank' %>.</li>
<li>Enough ado, have fun with <b>Ado</b>!</li>
<li>Still here? Ado comes with some features which can be tried right away.
<ol>
<li>
Design some tables and use