Initial non-working

This commit is contained in:
Michael Chapman
2013-05-31 16:11:04 +10:00
commit 84189ac33a
139 changed files with 11385 additions and 0 deletions

1
.vagrant Normal file
View File

@@ -0,0 +1 @@
{"active":{"build":"b1db3058-5d8a-4211-a238-bbadc7486980","control_basevm":"ac323559-4a20-4bec-bea4-df8c51b84e82"}}

1
01apt-cacher-ng-proxy Normal file
View File

@@ -0,0 +1 @@
Acquire::http { Proxy "http://192.168.242.99:3142"; };

3
Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "vagrant", "~>1.0"
gem " librarian-puppet-simple"

105
Puppetfile Normal file
View File

@@ -0,0 +1,105 @@
# the account where the Openstack modules should come from
#
# this file also accepts a few environment variables
#
git_protocol=ENV['git_protocol'] || 'git'
#
# this modulefile has been configured to use two sets of repos.
# The downstream repos that Cisco has forked, or the upstream repos
# that they are derived from (and should be maintained in sync with)
#
#
# this is just targeting the upstream stackforge modules
# right now, and the logic for using downstream does not
# work yet
#
if ENV['repos_to_use'] == 'downstream'
# this assumes downstream which is the Cisco branches
branch_name = 'origin/grizzly'
openstack_module_branch = branch_name
openstack_module_account = 'CiscoSystems'
else
# use the upstream modules where they exist
branch_name = 'origin/grizzly'
openstack_module_branch = 'master'
openstack_module_account = 'stackforge'
end
base_url = "#{git_protocol}://github.com"
#
# Installer Manifests
#
user_name = 'CiscoSystems'
release = 'grizzly'
manifest_branch = 'multi-node'
mod 'manifests', :git => "#{base_url}/#{user_name}/#{release}-manifests", :ref => manifest_branch
#
# the stackforge openstack modules
#
openstack_repo_prefix = "#{base_url}/#{openstack_module_account}/puppet"
mod 'stackforge/openstack', :git => "#{openstack_repo_prefix}-openstack", :ref => openstack_module_branch
# openstack core modules
mod 'stackforge/cinder', :git => "#{openstack_repo_prefix}-cinder", :ref => openstack_module_branch
mod 'stackforge/glance', :git => "#{openstack_repo_prefix}-glance", :ref => openstack_module_branch
mod 'stackforge/keystone', :git => "#{openstack_repo_prefix}-keystone", :ref => openstack_module_branch
mod 'stackforge/horizon', :git => "#{openstack_repo_prefix}-horizon", :ref => openstack_module_branch
mod 'stackforge/nova', :git => "#{openstack_repo_prefix}-nova", :ref => openstack_module_branch
mod 'stackforge/quantum', :git => "#{openstack_repo_prefix}-quantum", :ref => openstack_module_branch
mod 'stackforge/swift', :git => "#{openstack_repo_prefix}-swift", :ref => openstack_module_branch
#
# the rest of the modules just come straight from their respective Cisco branches at the moment.
#
#
# coe specific modules
#
mod 'CiscoSystems/coe', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-coe", :ref => 'origin/grizzly'
mod 'CiscoSystems/openstack_admin', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-openstack_admin", :ref => 'origin/grizzly'
# middleware modules
mod 'CiscoSystems/apache', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-apache", :ref => branch_name
mod 'CiscoSystems/memcached', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-memcached", :ref => branch_name
#
# I cannot remember if this is necessary
#
mod 'CiscoSystems/mysql', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-mysql", :ref => 'master'
mod 'CiscoSystems/rabbitmq', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-rabbitmq", :ref => branch_name
# linux tools
mod 'CiscoSystems/apt', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-apt", :ref => branch_name
mod 'CiscoSystems/apt-cacher-ng', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-apt-cacher-ng", :ref => branch_name
mod 'CiscoSystems/cobbler', :git => "#{git_protocol}://github.com/bodepd/puppet-cobbler", :ref => 'origin/fix_cobbler_sync_issue'
mod 'CiscoSystems/collectd', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-collectd", :ref => branch_name
mod 'CiscoSystems/corosync', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-corosync", :ref => branch_name
mod 'CiscoSystems/dnsmasq', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-dnsmasq", :ref => branch_name
mod 'CiscoSystems/drbd', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-drbd", :ref => branch_name
mod 'CiscoSystems/graphite', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-graphite", :ref => branch_name
mod 'CiscoSystems/monit', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-monit", :ref => branch_name
mod 'CiscoSystems/naginator', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-naginator", :ref => branch_name
mod 'CiscoSystems/ntp', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-ntp", :ref => branch_name
mod 'CiscoSystems/pip', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-pip", :ref => branch_name
mod 'CiscoSystems/puppet', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-puppet", :ref => branch_name
mod 'CiscoSystems/rsync', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-rsync", :ref => branch_name
mod 'CiscoSystems/sysctl', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-sysctl", :ref => branch_name
mod 'CiscoSystems/vswitch', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-vswitch", :ref => branch_name
mod 'CiscoSystems/xinetd', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-xinetd", :ref => branch_name
mod 'CiscoSystems/network', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-network", :ref => branch_name
mod 'CiscoSystems/filemapper', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-filemapper", :ref => branch_name
mod 'CiscoSystems/boolean', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-boolean", :ref => branch_name
#mod 'CiscoSystems/ssh', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-ssh", :ref => branch_name
# puppet utilities
mod 'CiscoSystems/concat', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-concat", :ref => branch_name
# need the latest changes here
mod 'CiscoSystems/inifile', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-inifile", :ref => branch_name
mod 'CiscoSystems/stdlib', :git => "#{git_protocol}://github.com/CiscoSystems/puppet-stdlib", :ref => branch_name

69
README.md Normal file
View File

@@ -0,0 +1,69 @@
Grizzly-manifests
================
Project for building out OpenStack COE.
## Installing dependencies
This setup requires that a few additional dependencies are installed:
* virtualbox
* vagrant
## User instructions
git clone https://github.com/CiscoSystems/grizzly-manifests
cp grizzly-manifests/* /etc/puppet/manifests
## Developer instructions
Developers should be started by installing the following simple utility:
(I will eventually just have it bundled as a gem)
mkdir vendor
export GEM_HOME=`pwd`/vendor
gem install thor --no-ri --no-rdoc
git clone git://github.com/bodepd/librarian-puppet-simple vendor/librarian-puppet-simple
export PATH=`pwd`/vendor/librarian-puppet-simple/bin/:$PATH
Once this library is installed, you can run the following command from this project's
root directory:
librarian-puppet install --verbose
Add the basebox
vagrant box add blank blank.box
This command will clone all required modules into the modules directory.
## Spinning up virtual machines with vagrant
Now that you have set up the puppet content, the next step is to build
out your multi-node environment using vagrant.
First, deploy the apt-ng-cacher instance:
vagrant up cache
Next, bring up the build server:
vagrant up build
Now, bring up the blank boxes so that they can PXE boot against the master
vagrant up control
vagrant up compute
Now, you have created a fully functional openstack environment, now have a look at some services:
* service dashboard: http://192.168.242.100/
* horizon: http://192.168.242.10/ (username: admin, password: Cisco123)
Log into your controller at: ssh localadmin@192.168.242.10 (password ubuntu)
and run through the 'Deploy Your First VM' section of this document:
http://docwiki.cisco.com/wiki/OpenStack:Folsom-Multinode#Creating_a_build_server

184
Vagrantfile vendored Normal file
View File

@@ -0,0 +1,184 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Four networks:
# 0 - VM host NAT
# 1 - COE build/deploy
# 2 - COE openstack internal
# 3 - COE openstack external (public)
def parse_vagrant_config(
config_file=File.expand_path(File.join(File.dirname(__FILE__), 'config.yaml'))
)
require 'yaml'
config = {
'gui_mode' => false,
'operatingsystem' => 'ubuntu',
'verbose' => false,
'update_repos' => true
}
if File.exists?(config_file)
overrides = YAML.load_file(config_file)
config.merge!(overrides)
end
config
end
Vagrant::Config.run do |config|
require 'fileutils'
if !File.symlink?("templates")
File.symlink("./modules/manifests/templates", "./templates")
end
if !File.symlink?("manifests")
File.symlink("./modules/manifests/manifests", "./manifests")
end
if !File.file?("./manifests/site.pp") && File.file?("./manifests/site.pp.example")
FileUtils.mv("./manifests/site.pp.example", "./manifests/site.pp")
end
v_config = parse_vagrant_config
apt_cache_proxy = 'Acquire::http { Proxy \"http://%s:3142\"; };' % v_config['apt_cache']
config.vm.define :cache do |cache_config|
cache_config.vm.box = "precise64"
cache_config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
cache_config.vm.network :hostonly, "192.168.242.99"
cache_config.vm.network :hostonly, "10.2.3.99"
cache_config.vm.network :hostonly, "10.3.3.99"
cache_config.vm.customize ['modifyvm', :id, '--name', 'cache']
cache_config.vm.host_name = 'cache'
cache_config.vm.provision :shell do |shell|
shell.inline = "apt-get update; apt-get install apt-cacher-ng -y; cp /vagrant/01apt-cacher-ng-proxy /etc/apt/apt.conf.d; apt-get update;sysctl -w net.ipv4.ip_forward=1;"#iptables A FORWARD i eth0 o eth2 j ACCEPT;iptables A FORWARD i eth2 o eth0 j ACCEPT;iptables t nat A POSTROUTING o eth0 j MASQUERADE"
end
end
# Cobbler based "build" server
config.vm.define :build do |build_config|
build_config.vm.box = "precise64"
build_config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
build_config.vm.customize ["modifyvm", :id, "--name", 'build-server']
build_config.vm.host_name = 'build-server'
build_config.vm.network :hostonly, "192.168.242.100"
build_config.vm.network :hostonly, "10.2.3.100"
build_config.vm.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"]
build_config.vm.network :hostonly, "10.3.3.100"
# Use user-provided sources.list if available
build_config.vm.provision :shell do |shell|
shell.inline = 'if [ -f /vagrant/sources.list ]; then cp /vagrant/sources.list /etc/apt; fi;'
end
# configure apt and basic packages needed for install
build_config.vm.provision :shell do |shell|
shell.inline = "cp /vagrant/dhclient.conf /etc/dhcp;echo \"%s\" > /etc/apt/apt.conf.d/01apt-cacher-ng-proxy; apt-get update; dhclient -r eth0 && dhclient eth0; apt-get install -y git vim puppet curl; cp /vagrant/templates/* /etc/puppet/templates" % apt_cache_proxy
end
# pre-import the ubuntu image from an appropriate mirror
build_config.vm.provision :shell do |shell|
shell.inline = "if [ -f /vagrant/sources.list ]; then apt-get install -y cobbler; cobbler-ubuntu-import -m $(cat /vagrant/sources.list | grep deb | cut -d ' ' -f 2 | grep http | grep -v security | head -1) precise-x86_64; fi;"
end
# now run puppet to install the build server
build_config.vm.provision(:puppet, :pp_path => "/etc/puppet") do |puppet|
puppet.manifests_path = 'manifests'
puppet.manifest_file = "site.pp"
puppet.module_path = 'modules'
puppet.options = ['--verbose', '--trace', '--debug']
end
# Configure puppet
build_config.vm.provision :shell do |shell|
shell.inline = 'if [ ! -h /etc/puppet/modules ]; then rmdir /etc/puppet/modules;ln -s /etc/puppet/modules-0 /etc/puppet/modules; fi;puppet plugin download --server build-server.domain.name;service apache2 restart'
end
# enable ip forwarding and NAT so that the build server can act
# as an external gateway for the quantum router.
build_config.vm.provision :shell do |shell|
shell.inline = "ip addr add 172.16.2.1/24 dev eth2; sysctl -w net.ipv4.ip_forward=1; iptables -A FORWARD -o eth0 -i eth1 -s 172.16.2.0/24 -m conntrack --ctstate NEW -j ACCEPT; iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; iptables -t nat -F POSTROUTING; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE"
end
end
# Openstack control server
config.vm.define :control_pxe do |control_config|
control_config.vm.customize(['modifyvm', :id ,'--nicbootprio2','1'])
control_config.vm.box = 'blank'
control_config.vm.boot_mode = 'gui'
control_config.ssh.port = 2727
control_config.vm.network :hostonly, "192.168.242.10", :mac => "001122334455"
control_config.vm.network :hostonly, "10.2.3.10"
control_config.vm.network :hostonly, "10.3.3.10"
end
config.vm.define :control_basevm do |control_config|
node_name = "control-server-#{Time.now.strftime('%Y%m%d%m%s')}.domain.name"
control_config.vm.box = "precise64"
control_config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
control_config.vm.customize ["modifyvm", :id, "--name", 'control-server']
control_config.vm.customize ["modifyvm", :id, "--memory", 1024]
control_config.vm.host_name = node_name
# you cannot boot this at the same time as the control_pxe b/c they have the same ip address
control_config.vm.network :hostonly, "192.168.242.10"
control_config.vm.network :hostonly, "10.2.3.10"
control_config.vm.customize ["modifyvm", :id, "--nicpromisc3", "allow-all"]
control_config.vm.network :hostonly, "10.3.3.10"
# Use user-provided sources.list if available
control_config.vm.provision :shell do |shell|
shell.inline = 'if [ -f /vagrant/sources.list ]; then cp /vagrant/sources.list /etc/apt; fi;'
end
control_config.vm.provision :shell do |shell|
shell.inline = 'echo "192.168.242.100 build-server build-server.domain.name" >> /etc/hosts;echo \"%s\" > /etc/apt/apt.conf.d/01apt-cacher-ng-proxy; apt-get update;apt-get install ubuntu-cloud-keyring' % apt_cache_proxy
end
control_config.vm.provision(:puppet_server) do |puppet|
puppet.puppet_server = 'build-server.domain.name'
puppet.options = ['-t', '--pluginsync', '--trace', "--certname #{node_name}"]
end
# TODO install from puppet
end
# Openstack compute server
config.vm.define :compute_pxe do |compute_config|
compute_config.vm.customize(['modifyvm', :id ,'--nicbootprio2','1'])
compute_config.vm.box = 'blank'
compute_config.vm.boot_mode = 'gui'
compute_config.ssh.port = 2728
compute_config.vm.network :hostonly, "192.168.242.21", :mac => "001122334466"
compute_config.vm.network :hostonly, "10.2.3.21"
compute_config.vm.network :hostonly, "10.3.3.21"
end
config.vm.define :compute_basevm do |compute_config|
node_name = "compute-server02-#{Time.now.strftime('%Y%m%d%m%s')}.domain.name"
compute_config.vm.box = "precise64"
compute_config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
compute_config.vm.customize ["modifyvm", :id, "--name", 'compute-server02']
compute_config.vm.host_name = node_name
compute_config.vm.customize ["modifyvm", :id, "--memory", 2512]
compute_config.vm.network :hostonly, "192.168.242.21"
compute_config.vm.network :hostonly, "10.2.3.21"
compute_config.vm.network :hostonly, "10.3.3.21"
# Use user-provided sources.list if available
compute_config.vm.provision :shell do |shell|
shell.inline = 'if [ -f /vagrant/sources.list ]; then cp /vagrant/sources.list /etc/apt; fi;'
end
compute_config.vm.provision :shell do |shell|
shell.inline = 'echo "192.168.242.100 build-server build-server.domain.name" >> /etc/hosts;echo \"%s\" > /etc/apt/apt.conf.d/01apt-cacher-ng-proxy; apt-get update;apt-get install ubuntu-cloud-keyring' % apt_cache_proxy
end
compute_config.vm.provision(:puppet_server) do |puppet|
puppet.puppet_server = 'build-server.domain.name'
puppet.options = ['-t', '--pluginsync', '--trace', "--certname #{node_name}"]
end
# TODO install from puppet
end
end

BIN
blank.box Normal file

Binary file not shown.

2
config.yaml Normal file
View File

@@ -0,0 +1,2 @@
---
apt_cache: '192.168.1.16'

55
dhclient.conf Normal file
View File

@@ -0,0 +1,55 @@
# Configuration file for /sbin/dhclient, which is included in Debian's
# dhcp3-client package.
#
# This is a sample configuration file for dhclient. See dhclient.conf's
# man page for more information about the syntax of this file
# and a more comprehensive list of the parameters understood by
# dhclient.
#
# Normally, if the DHCP server provides reasonable information and does
# not leave anything out (like the domain name, for example), then
# few changes must be made to this file, if any.
#
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
send host-name "<hostname>";
#send dhcp-client-identifier 1:0:a0:24:ab:fb:9c;
#send dhcp-lease-time 3600;
supersede domain-name "domain.name";
#prepend domain-name-servers 127.0.0.1;
request subnet-mask, broadcast-address, time-offset, routers,
domain-name-servers, host-name,
netbios-name-servers, netbios-scope, interface-mtu,
rfc3442-classless-static-routes, ntp-servers,
dhcp6.domain-search, dhcp6.fqdn,
dhcp6.name-servers, dhcp6.sntp-servers;
#require subnet-mask, domain-name-servers;
#timeout 60;
#retry 60;
#reboot 10;
#select-timeout 5;
#initial-interval 2;
#script "/etc/dhcp3/dhclient-script";
#media "-link0 -link1 -link2", "link0 link1";
#reject 192.33.137.209;
#alias {
# interface "eth0";
# fixed-address 192.5.5.213;
# option subnet-mask 255.255.255.255;
#}
#lease {
# interface "eth0";
# fixed-address 192.33.137.200;
# medium "link0 link1";
# option host-name "andare.swiftmedia.com";
# option subnet-mask 255.255.255.0;
# option broadcast-address 192.33.137.255;
# option routers 192.33.137.250;
# option domain-name-servers 127.0.0.1;
# renew 2 2000/1/12 00:00:01;
# rebind 2 2000/1/12 00:00:01;
# expire 2 2000/1/12 00:00:01;
#}

130
install_os_puppet Executable file
View File

@@ -0,0 +1,130 @@
#!/bin/bash
# install_os_puppet by Cisco Systems, Inc. is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
#
# This script runs the basic steps for preparing to auto-deploy OpenStack as per the Cisco Edition process
# The manual steps are documented at http://docwiki.cisco.com/wiki/OpenStack:Folsom
#
# This script: updates apt, and makes sure that the system is up to date with the current Ubuntu baseline
# It then downloads the current set of Cisco validated puppet modules and a set of baseline manifests from the
# Cisco github repository
# If a proxy is necessary in order to download files from the internet, then either a proxy target can be passed
# to the script, or the environmet variables can be pre-set before running the script locally.
#
set -o errexit
usage() {
cat <<EOF
usage: $0 options
OPTIONS:
-h Show this message
-p http proxy i.e. -p http://username:password@host:port/
EOF
}
# wrapper all commands with sudo in case this is not run as root
# also map in a proxy in case it was passed as a command line argument
function run_cmd () {
if [ -z "$PROXY" ]; then
sudo $*
else
sudo env http_proxy=$PROXY https_proxy=$PROXY $*
fi
}
# Define some useful APT parameters to make sure you get the latest versions of code
APT_CONFIG="-o Acquire::http::No-Cache=True -o Acquire::BrokenProxy=true -o Acquire::Retries=3"
# check if the environment is set up for http and https proxies
if [ -n "$http_proxy" ]; then
if [ -z "$https_proxy" ]; then
echo "Please set https_proxy env variable."
exit 1
fi
PROXY=$http_proxy
fi
# parse CLI options
while getopts "h:p:" OPTION
do
case $OPTION in
h)
usage
exit 1
;;
p)
PROXY=$OPTARG
export http_proxy=$PROXY
export https_proxy=$PROXY
esac
done
# Make sure the apt repository list is up to date
echo -e "\n\nUpdate apt repository...\n\n"
if ! run_cmd apt-get $APT_CONFIG update; then
echo "Can't update apt repository"
exit 1
fi
# Install prerequisite packages
echo "Installing prerequisite apps: git, puppet, ipmitool, python-software-properties.."
if ! run_cmd apt-get $APT_CONFIG install -qym git puppet ipmitool python-software-properties; then
echo "Can't install prerequisites!..."
exit 1
fi
# Grab the Cisco puppet global manifests (site.pp, etc.), try to update a previously downloaded set first
echo "Cloning grizzly-manifests multi-node repository branch from github.com..."
if [ -d /root/cisco-grizzly-manifests ] ; then
echo -e "Looks like perhaps you ran this script before? We'll try to update your os-docs directory, just in case..."
if ! run_cmd git --git-dir=/root/cisco-grizzly-manifests/.git/ pull ; then
echo "That did not work. Perhaps rename your os-docs directory, and try again?"
exit 1
fi
fi
# Get a new set, as there was no previous download
if [ ! -d /root/cisco-grizzly-manifests ] ; then
if ! run_cmd git clone -b multi-node https://github.com/CiscoSystems/grizzly-manifests /root/cisco-grizzly-manifests ; then
echo "Can't run git clone!"
exit 1
fi
fi
echo "Copying manifests examples to manifest dir..."
if ! run_cmd cp /root/cisco-grizzly-manifests/manifests/* /etc/puppet/manifests/ ;then
echo "Can't copy sample manifests!!!"
exit 1
fi
# Update APT again, to capture any changes and updates driven by the newly loaded code
echo -e "\n\nUpdated apt repository...\n\n"
if ! run_cmd apt-get $APT_CONFIG update; then
echo "Can't update apt repository"
exit 1
fi
# Make sure the distro is up to date
echo -e "\n\nUpdate packages...\n\n"
if ! run_cmd apt-get $APT_CONFIG dist-upgrade -y; then
echo "Can't update packages"
exit 1
fi
# Change to the manifests directory, as the puppet-modules.sh script expects to find a file in the local
# directory that lists the modules to download
cd /etc/puppet/manifests
# Load the lateast modules.
echo -e "\n\nInstalling Cisco Validated puppet openstack modules...\n\n"
if ! run_cmd sh puppet-modules.sh ; then
echo "Can't install puppet modules..."
exit 1
fi
echo -e "\n\nSUCCESS!!!!\n\n Now, go edit your site.pp file in /etc/puppet/manifests, and then run 'puppet apply -v /etc/puppet/manifests/site.pp"
exit 0

1
manifests Symbolic link
View File

@@ -0,0 +1 @@
./modules/manifests/manifests

1
modules/apache Submodule

Submodule modules/apache added at cf880ad942

1
modules/apt Submodule

Submodule modules/apt added at 07da427488

1
modules/apt-cacher-ng Submodule

Submodule modules/apt-cacher-ng added at 4f97174880

1
modules/boolean Submodule

Submodule modules/boolean added at 3084373e8d

1
modules/cinder Submodule

Submodule modules/cinder added at 9686bb830b

1
modules/cobbler Submodule

Submodule modules/cobbler added at b128cf87f0

1
modules/coe Submodule

Submodule modules/coe added at e79e5bc96b

1
modules/collectd Submodule

Submodule modules/collectd added at e9746ad5b8

1
modules/concat Submodule

Submodule modules/concat added at 031bf26128

1
modules/corosync Submodule

Submodule modules/corosync added at 33b46ee789

1
modules/dnsmasq Submodule

Submodule modules/dnsmasq added at fa08eff8ca

1
modules/drbd Submodule

Submodule modules/drbd added at 902dc3ea43

1
modules/filemapper Submodule

Submodule modules/filemapper added at a7fce7c520

1
modules/glance Submodule

Submodule modules/glance added at 94f25c83da

1
modules/graphite Submodule

Submodule modules/graphite added at 8fae5e124f

1
modules/horizon Submodule

Submodule modules/horizon added at 12d0a244cc

1
modules/inifile Submodule

Submodule modules/inifile added at 6c6f9a4fa8

1
modules/keystone Submodule

Submodule modules/keystone added at 594f4e94e7

1
modules/manifests Submodule

Submodule modules/manifests added at 4e642c377d

1
modules/memcached Submodule

Submodule modules/memcached added at 03f99c2b69

1
modules/monit Submodule

Submodule modules/monit added at 58bba3de31

1
modules/mysql Submodule

Submodule modules/mysql added at 10c369e331

1
modules/naginator Submodule

Submodule modules/naginator added at 12163dc7eb

1
modules/network Submodule

Submodule modules/network added at e9d5528db5

1
modules/nova Submodule

Submodule modules/nova added at 8487b41c21

1
modules/ntp Submodule

Submodule modules/ntp added at b1b3132f84

1
modules/openstack Submodule

Submodule modules/openstack added at aed432f502

Submodule modules/openstack_admin added at 9471eb84ed

1
modules/pip Submodule

Submodule modules/pip added at a3a4f851e3

1
modules/puppet Submodule

Submodule modules/puppet added at 70905fd780

1
modules/quantum Submodule

Submodule modules/quantum added at c91a1ec310

1
modules/rabbitmq Submodule

Submodule modules/rabbitmq added at 4fcb71991f

1
modules/rsync Submodule

Submodule modules/rsync added at 5c866fc8ca

1
modules/stdlib Submodule

Submodule modules/stdlib added at 96e19d05f3

1
modules/swift Submodule

Submodule modules/swift added at 4c990fd894

1
modules/sysctl Submodule

Submodule modules/sysctl added at 7e20e7f050

1
modules/vswitch Submodule

Submodule modules/vswitch added at cf31f73cb4

1
modules/xinetd Submodule

Submodule modules/xinetd added at 452ddd9af8

61
sources.list Normal file
View File

@@ -0,0 +1,61 @@
# deb http://au.archive.ubuntu.com/ubuntu/ precise main restricted
# deb http://au.archive.ubuntu.com/ubuntu/ precise-updates main restricted
# deb http://security.ubuntu.com/ubuntu precise-security main restricted
# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://au.archive.ubuntu.com/ubuntu/ precise main restricted
deb-src http://au.archive.ubuntu.com/ubuntu/ precise main restricted
## Major bug fix updates produced after the final release of the
## distribution.
deb http://au.archive.ubuntu.com/ubuntu/ precise-updates main restricted
deb-src http://au.archive.ubuntu.com/ubuntu/ precise-updates main restricted
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team. Also, please note that software in universe WILL NOT receive any
## review or updates from the Ubuntu security team.
deb http://au.archive.ubuntu.com/ubuntu/ precise universe
deb-src http://au.archive.ubuntu.com/ubuntu/ precise universe
deb http://au.archive.ubuntu.com/ubuntu/ precise-updates universe
deb-src http://au.archive.ubuntu.com/ubuntu/ precise-updates universe
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## multiverse WILL NOT receive any review or updates from the Ubuntu
## security team.
deb http://au.archive.ubuntu.com/ubuntu/ precise multiverse
deb-src http://au.archive.ubuntu.com/ubuntu/ precise multiverse
deb http://au.archive.ubuntu.com/ubuntu/ precise-updates multiverse
deb-src http://au.archive.ubuntu.com/ubuntu/ precise-updates multiverse
## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
deb http://au.archive.ubuntu.com/ubuntu/ precise-backports main restricted universe multiverse
deb-src http://au.archive.ubuntu.com/ubuntu/ precise-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu precise-security main restricted
deb-src http://security.ubuntu.com/ubuntu precise-security main restricted
deb http://security.ubuntu.com/ubuntu precise-security universe
deb-src http://security.ubuntu.com/ubuntu precise-security universe
deb http://security.ubuntu.com/ubuntu precise-security multiverse
deb-src http://security.ubuntu.com/ubuntu precise-security multiverse
## Uncomment the following two lines to add software from Canonical's
## 'partner' repository.
## This software is not part of Ubuntu, but is offered by Canonical and the
## respective vendors as a service to Ubuntu users.
# deb http://archive.canonical.com/ubuntu precise partner
# deb-src http://archive.canonical.com/ubuntu precise partner
## Uncomment the following two lines to add software from Ubuntu's
## 'extras' repository.
## This software is not part of Ubuntu, but is offered by third-party
## developers who want to ship their latest software.
# deb http://extras.ubuntu.com/ubuntu precise main
# deb-src http://extras.ubuntu.com/ubuntu precise main

1
templates Symbolic link
View File

@@ -0,0 +1 @@
./modules/manifests/templates

19
vendor/bin/thor vendored Executable file
View File

@@ -0,0 +1,19 @@
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'thor' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
version = $1
ARGV.shift
end
gem 'thor', version
load Gem.bin_path('thor', 'thor', version)

BIN
vendor/cache/thor-0.18.1.gem vendored Normal file

Binary file not shown.

5
vendor/gems/thor-0.18.1/.document vendored Normal file
View File

@@ -0,0 +1,5 @@
lib/*.rb
lib/**/*.rb
-
CHANGELOG.rdoc
LICENSE.md

139
vendor/gems/thor-0.18.1/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,139 @@
## 0.18.1, release 2013-03-30
* Revert regressions found in 0.18.0
## 0.18.0, release 2013-03-26
* Remove rake2thor
* Only display colors if output medium supports colors
* Pass parent_options to subcommands
* Fix non-dash-prefixed aliases
* Make error messages more helpful
* Rename "task" to "command"
* Add the method to allow for custom package name
## 0.17.0, release 2013-01-24
* Add better support for tasks that accept arbitrary additional arguments (e.g. things like `bundle exec`)
* Add #stop_on_unknown_option!
* Only strip from stdin.gets if it wasn't ended with EOF
* Allow "send" as a task name
* Allow passing options as arguments after "--"
* Autoload Thor::Group
## 0.16.0, release 2012-08-14
* Add enum to string arguments
## 0.15.4, release 2012-06-29
* Fix regression when destination root contains reserved regexp characters
## 0.15.3, release 2012-06-18
* Support strict_args_position! for backwards compatibility
* Escape Dir glob characters in paths
## 0.15.2, released 2012-05-07
* Added print_in_columns
* Exposed terminal_width as a public API
## 0.15.1, release 2012-05-06
* Fix Ruby 1.8 truncation bug with unicode chars
* Fix shell delegate methods to pass their block
* Don't output trailing spaces when printing the last column in a table
## 0.15, released 2012-04-29
* Alias method_options to options
* Refactor say to allow multiple colors
* Exposed error as a public API
* Exposed file_collision as a public API
* Exposed print_wrapped as a public API
* Exposed set_color as a public API
* Fix number-formatting bugs in print_table
* Fix "indent" typo in print_table
* Fix Errno::EPIPE when piping tasks to `head`
* More friendly error messages
## 0.14, released 2010-07-25
* Added CreateLink class and #link_file method
* Made Thor::Actions#run use system as default method for system calls
* Allow use of private methods from superclass as tasks
* Added mute(&block) method which allows to run block without any output
* Removed config[:pretend]
* Enabled underscores for command line switches
* Added Thor::Base.basename which is used by both Thor.banner and Thor::Group.banner
* Deprecated invoke() without arguments
* Added :only and :except to check_unknown_options
## 0.13, released 2010-02-03
* Added :lazy_default which is only triggered if a switch is given
* Added Thor::Shell::HTML
* Added subcommands
* Decoupled Thor::Group and Thor, so it's easier to vendor
* Added check_unknown_options! in case you want error messages to be raised in valid switches
* run(command) should return the results of command
## 0.12, released 2010-01-02
* Methods generated by attr_* are automatically not marked as tasks
* inject_into_file does not add the same content twice, unless :force is set
* Removed rr in favor to rspec mock framework
* Improved output for thor -T
* [#7] Do not force white color on status
* [#8] Yield a block with the filename on directory
## 0.11, released 2009-07-01
* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
Thor classes.
* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
since it may cause wrong behavior in the invocation system.
* thor help now show information about any class/task. All those calls are
possible:
thor help describe
thor help describe:amazing
Or even with default namespaces:
thor help :spec
* Thor::Runner now invokes the default task if none is supplied:
thor describe # invokes the default task, usually help
* Thor::Runner now works with mappings:
thor describe -h
* Added some documentation and code refactoring.
## 0.9.8, released 2008-10-20
* Fixed some tiny issues that were introduced lately.
## 0.9.7, released 2008-10-13
* Setting global method options on the initialize method works as expected:
All other tasks will accept these global options in addition to their own.
* Added 'group' notion to Thor task sets (class Thor); by default all tasks
are in the 'standard' group. Running 'thor -T' will only show the standard
tasks - adding --all will show all tasks. You can also filter on a specific
group using the --group option: thor -T --group advanced
## 0.9.6, released 2008-09-13
* Generic improvements
## 0.9.5, released 2008-08-27
* Improve Windows compatibility
* Update (incorrect) README and task.thor sample file
* Options hash is now frozen (once returned)
* Allow magic predicates on options object. For instance: `options.force?`
* Add support for :numeric type
* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
* Allow specifying optional args with default values: method_options(:user => "mislav")
* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
* Exit with the status of the spec command to help CI stuff out some.
## 0.9.4, released 2008-08-13
* Try to add Windows compatibility.
* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
* Allow options at the beginning of the argument list as well as the end.
* Make options available with symbol keys in addition to string keys.
* Allow true to be passed to Thor#method_options to denote a boolean option.
* If loading a thor file fails, don't give up, just print a warning and keep going.
* Make sure that we re-raise errors if they happened further down the pipe than we care about.
* Only delete the old file on updating when the installation of the new one is a success
* Make it Ruby 1.8.5 compatible.
* Don't raise an error if a boolean switch is defined multiple times.
* Thor::Options now doesn't parse through things that look like options but aren't.
* Add URI detection to install task, and make sure we don't append ".thor" to URIs
* Add rake2thor to the gem binfiles.
* Make sure local Thorfiles override system-wide ones.

20
vendor/gems/thor-0.18.1/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

35
vendor/gems/thor-0.18.1/README.md vendored Normal file
View File

@@ -0,0 +1,35 @@
[![Gem Version](https://badge.fury.io/rb/thor.png)](https://rubygems.org/gems/thor)
[![Build Status](https://secure.travis-ci.org/wycats/thor.png?branch=master)](http://travis-ci.org/wycats/thor)
[![Dependency Status](https://gemnasium.com/wycats/thor.png?travis)](https://gemnasium.com/wycats/thor)
[![Code Climate](https://codeclimate.com/github/wycats/thor.png)](https://codeclimate.com/github/wycats/thor)
[![Coverage Status](https://coveralls.io/repos/wycats/thor/badge.png?branch=master)](https://coveralls.io/r/wycats/thor)
Thor
====
Description
-----------
Thor is a simple and efficient tool for building self-documenting command line
utilities. It removes the pain of parsing command line options, writing
"USAGE:" banners, and can also be used as an alternative to the [Rake][rake]
build tool. The syntax is Rake-like, so it should be familiar to most Rake
users.
[rake]: https://github.com/jimweirich/rake
Installation
------------
gem install thor
Usage and documentation
-----------------------
Please see the [wiki][] for basic usage and other documentation on using Thor. You can also checkout the [official homepage][homepage].
[wiki]: https://github.com/wycats/thor/wiki
[homepage]: http://whatisthor.com/
License
-------
Released under the MIT License. See the [LICENSE][] file for further details.
[license]: LICENSE.md

30
vendor/gems/thor-0.18.1/Thorfile vendored Normal file
View File

@@ -0,0 +1,30 @@
# encoding: utf-8
$:.unshift File.expand_path("../lib", __FILE__)
require 'bundler'
require 'thor/rake_compat'
class Default < Thor
include Thor::RakeCompat
Bundler::GemHelper.install_tasks
desc "build", "Build thor-#{Thor::VERSION}.gem into the pkg directory"
def build
Rake::Task["build"].execute
end
desc "install", "Build and install thor-#{Thor::VERSION}.gem into system gems"
def install
Rake::Task["install"].execute
end
desc "release", "Create tag v#{Thor::VERSION} and build and push thor-#{Thor::VERSION}.gem to Rubygems"
def release
Rake::Task["release"].execute
end
desc "spec", "Run RSpec code examples"
def spec
exec "rspec --color --format=documentation spec"
end
end

6
vendor/gems/thor-0.18.1/bin/thor vendored Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# -*- mode: ruby -*-
require 'thor/runner'
$thor_runner = true
Thor::Runner.start

473
vendor/gems/thor-0.18.1/lib/thor.rb vendored Normal file
View File

@@ -0,0 +1,473 @@
require 'set'
require 'thor/base'
class Thor
class << self
# Allows for custom "Command" package naming.
#
# === Parameters
# name<String>
# options<Hash>
#
def package_name(name, options={})
@package_name = name.nil? || name == '' ? nil : name
end
# Sets the default command when thor is executed without an explicit command to be called.
#
# ==== Parameters
# meth<Symbol>:: name of the default command
#
def default_command(meth=nil)
@default_command = case meth
when :none
'help'
when nil
@default_command || from_superclass(:default_command, 'help')
else
meth.to_s
end
end
alias default_task default_command
# Registers another Thor subclass as a command.
#
# ==== Parameters
# klass<Class>:: Thor subclass to register
# command<String>:: Subcommand name to use
# usage<String>:: Short usage for the subcommand
# description<String>:: Description for the subcommand
def register(klass, subcommand_name, usage, description, options={})
if klass <= Thor::Group
desc usage, description, options
define_method(subcommand_name) { |*args| invoke(klass, args) }
else
desc usage, description, options
subcommand subcommand_name, klass
end
end
# Defines the usage and the description of the next command.
#
# ==== Parameters
# usage<String>
# description<String>
# options<String>
#
def desc(usage, description, options={})
if options[:for]
command = find_and_refresh_command(options[:for])
command.usage = usage if usage
command.description = description if description
else
@usage, @desc, @hide = usage, description, options[:hide] || false
end
end
# Defines the long description of the next command.
#
# ==== Parameters
# long description<String>
#
def long_desc(long_description, options={})
if options[:for]
command = find_and_refresh_command(options[:for])
command.long_description = long_description if long_description
else
@long_desc = long_description
end
end
# Maps an input to a command. If you define:
#
# map "-T" => "list"
#
# Running:
#
# thor -T
#
# Will invoke the list command.
#
# ==== Parameters
# Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
#
def map(mappings=nil)
@map ||= from_superclass(:map, {})
if mappings
mappings.each do |key, value|
if key.respond_to?(:each)
key.each {|subkey| @map[subkey] = value}
else
@map[key] = value
end
end
end
@map
end
# Declares the options for the next command to be declared.
#
# ==== Parameters
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
# or :required (string). If you give a value, the type of the value is used.
#
def method_options(options=nil)
@method_options ||= {}
build_options(options, @method_options) if options
@method_options
end
alias options method_options
# Adds an option to the set of method options. If :for is given as option,
# it allows you to change the options from a previous defined command.
#
# def previous_command
# # magic
# end
#
# method_option :foo => :bar, :for => :previous_command
#
# def next_command
# # magic
# end
#
# ==== Parameters
# name<Symbol>:: The name of the argument.
# options<Hash>:: Described below.
#
# ==== Options
# :desc - Description for the argument.
# :required - If the argument is required or not.
# :default - Default value for this argument. It cannot be required and have default values.
# :aliases - Aliases for this option.
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :banner - String to show on usage notes.
# :hide - If you want to hide this option from the help.
#
def method_option(name, options={})
scope = if options[:for]
find_and_refresh_command(options[:for]).options
else
method_options
end
build_option(name, options, scope)
end
alias option method_option
# Prints help information for the given command.
#
# ==== Parameters
# shell<Thor::Shell>
# command_name<String>
#
def command_help(shell, command_name)
meth = normalize_command_name(command_name)
command = all_commands[meth]
handle_no_command_error(meth) unless command
shell.say "Usage:"
shell.say " #{banner(command)}"
shell.say
class_options_help(shell, nil => command.options.map { |_, o| o })
if command.long_description
shell.say "Description:"
shell.print_wrapped(command.long_description, :indent => 2)
else
shell.say command.description
end
end
alias task_help command_help
# Prints help information for this class.
#
# ==== Parameters
# shell<Thor::Shell>
#
def help(shell, subcommand = false)
list = printable_commands(true, subcommand)
Thor::Util.thor_classes_in(self).each do |klass|
list += klass.printable_commands(false)
end
list.sort!{ |a,b| a[0] <=> b[0] }
if @package_name
shell.say "#{@package_name} commands:"
else
shell.say "Commands:"
end
shell.print_table(list, :indent => 2, :truncate => true)
shell.say
class_options_help(shell)
end
# Returns commands ready to be printed.
def printable_commands(all = true, subcommand = false)
(all ? all_commands : commands).map do |_, command|
next if command.hidden?
item = []
item << banner(command, false, subcommand)
item << (command.description ? "# #{command.description.gsub(/\s+/m,' ')}" : "")
item
end.compact
end
alias printable_tasks printable_commands
def subcommands
@subcommands ||= from_superclass(:subcommands, [])
end
alias subtasks subcommands
def subcommand(subcommand, subcommand_class)
self.subcommands << subcommand.to_s
subcommand_class.subcommand_help subcommand
define_method(subcommand) do |*args|
args, opts = Thor::Arguments.split(args)
invoke subcommand_class, args, opts, :invoked_via_subcommand => true, :class_options => options
end
end
alias subtask subcommand
# Extend check unknown options to accept a hash of conditions.
#
# === Parameters
# options<Hash>: A hash containing :only and/or :except keys
def check_unknown_options!(options={})
@check_unknown_options ||= Hash.new
options.each do |key, value|
if value
@check_unknown_options[key] = Array(value)
else
@check_unknown_options.delete(key)
end
end
@check_unknown_options
end
# Overwrite check_unknown_options? to take subcommands and options into account.
def check_unknown_options?(config) #:nodoc:
options = check_unknown_options
return false unless options
command = config[:current_command]
return true unless command
name = command.name
if subcommands.include?(name)
false
elsif options[:except]
!options[:except].include?(name.to_sym)
elsif options[:only]
options[:only].include?(name.to_sym)
else
true
end
end
# Stop parsing of options as soon as an unknown option or a regular
# argument is encountered. All remaining arguments are passed to the command.
# This is useful if you have a command that can receive arbitrary additional
# options, and where those additional options should not be handled by
# Thor.
#
# ==== Example
#
# To better understand how this is useful, let's consider a command that calls
# an external command. A user may want to pass arbitrary options and
# arguments to that command. The command itself also accepts some options,
# which should be handled by Thor.
#
# class_option "verbose", :type => :boolean
# stop_on_unknown_option! :exec
# check_unknown_options! :except => :exec
#
# desc "exec", "Run a shell command"
# def exec(*args)
# puts "diagnostic output" if options[:verbose]
# Kernel.exec(*args)
# end
#
# Here +exec+ can be called with +--verbose+ to get diagnostic output,
# e.g.:
#
# $ thor exec --verbose echo foo
# diagnostic output
# foo
#
# But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
#
# $ thor exec echo --verbose foo
# --verbose foo
#
# ==== Parameters
# Symbol ...:: A list of commands that should be affected.
def stop_on_unknown_option!(*command_names)
@stop_on_unknown_option ||= Set.new
@stop_on_unknown_option.merge(command_names)
end
def stop_on_unknown_option?(command) #:nodoc:
!!@stop_on_unknown_option && @stop_on_unknown_option.include?(command.name.to_sym)
end
protected
# The method responsible for dispatching given the args.
def dispatch(meth, given_args, given_opts, config) #:nodoc:
# There is an edge case when dispatching from a subcommand.
# A problem occurs invoking the default command. This case occurs
# when arguments are passed and a default command is defined, and
# the first given_args does not match the default command.
# Thor use "help" by default so we skip that case.
# Note the call to retrieve_command_name. It's called with
# given_args.dup since that method calls args.shift. Then lookup
# the command normally. If the first item in given_args is not
# a command then use the default command. The given_args will be
# intact later since dup was used.
if config[:invoked_via_subcommand] && given_args.size >= 1 && default_command != "help" && given_args.first != default_command
meth ||= retrieve_command_name(given_args.dup)
command = all_commands[normalize_command_name(meth)]
command ||= all_commands[normalize_command_name(default_command)]
else
meth ||= retrieve_command_name(given_args)
command = all_commands[normalize_command_name(meth)]
end
if command
args, opts = Thor::Options.split(given_args)
if stop_on_unknown_option?(command) && !args.empty?
# given_args starts with a non-option, so we treat everything as
# ordinary arguments
args.concat opts
opts.clear
end
else
args, opts = given_args, nil
command = Thor::DynamicCommand.new(meth)
end
opts = given_opts || opts || []
config.merge!(:current_command => command, :command_options => command.options)
instance = new(args, opts, config)
yield instance if block_given?
args = instance.args
trailing = args[Range.new(arguments.size, -1)]
instance.invoke_command(command, trailing || [])
end
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner. It receives
# the command that is going to be invoked and a boolean which indicates if
# the namespace should be displayed as arguments.
#
def banner(command, namespace = nil, subcommand = false)
"#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
end
def baseclass #:nodoc:
Thor
end
def create_command(meth) #:nodoc:
if @usage && @desc
base_class = @hide ? Thor::HiddenCommand : Thor::Command
commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
@usage, @desc, @long_desc, @method_options, @hide = nil
true
elsif self.all_commands[meth] || meth == "method_missing"
true
else
puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " <<
"Call desc if you want this method to be available as command or declare it inside a " <<
"no_commands{} block. Invoked from #{caller[1].inspect}."
false
end
end
alias create_task create_command
def initialize_added #:nodoc:
class_options.merge!(method_options)
@method_options = nil
end
# Retrieve the command name from given args.
def retrieve_command_name(args) #:nodoc:
meth = args.first.to_s unless args.empty?
if meth && (map[meth] || meth !~ /^\-/)
args.shift
else
nil
end
end
alias retrieve_task_name retrieve_command_name
# receives a (possibly nil) command name and returns a name that is in
# the commands hash. In addition to normalizing aliases, this logic
# will determine if a shortened command is an unambiguous substring of
# a command or alias.
#
# +normalize_command_name+ also converts names like +animal-prison+
# into +animal_prison+.
def normalize_command_name(meth) #:nodoc:
return default_command.to_s.gsub('-', '_') unless meth
possibilities = find_command_possibilities(meth)
if possibilities.size > 1
raise ArgumentError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]"
elsif possibilities.size < 1
meth = meth || default_command
elsif map[meth]
meth = map[meth]
else
meth = possibilities.first
end
meth.to_s.gsub('-','_') # treat foo-bar as foo_bar
end
alias normalize_task_name normalize_command_name
# this is the logic that takes the command name passed in by the user
# and determines whether it is an unambiguous substrings of a command or
# alias name.
def find_command_possibilities(meth)
len = meth.to_s.length
possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
if possibilities.include?(meth)
[meth]
elsif unique_possibilities.size == 1
unique_possibilities
else
possibilities
end
end
alias find_task_possibilities find_command_possibilities
def subcommand_help(cmd)
desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
class_eval <<-RUBY
def help(command = nil, subcommand = true); super; end
RUBY
end
alias subtask_help subcommand_help
end
include Thor::Base
map HELP_MAPPINGS => :help
desc "help [COMMAND]", "Describe available commands or one specific command"
def help(command = nil, subcommand = false)
command ? self.class.command_help(shell, command) : self.class.help(shell, subcommand)
end
end

View File

@@ -0,0 +1,318 @@
require 'fileutils'
require 'uri'
require 'thor/core_ext/io_binary_read'
require 'thor/actions/create_file'
require 'thor/actions/create_link'
require 'thor/actions/directory'
require 'thor/actions/empty_directory'
require 'thor/actions/file_manipulation'
require 'thor/actions/inject_into_file'
class Thor
module Actions
attr_accessor :behavior
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
# Hold source paths for one Thor instance. source_paths_for_search is the
# method responsible to gather source_paths from this current class,
# inherited paths and the source root.
#
def source_paths
@_source_paths ||= []
end
# Stores and return the source root for this class
def source_root(path=nil)
@_source_root = path if path
@_source_root
end
# Returns the source paths in the following order:
#
# 1) This class source paths
# 2) Source root
# 3) Parents source paths
#
def source_paths_for_search
paths = []
paths += self.source_paths
paths << self.source_root if self.source_root
paths += from_superclass(:source_paths, [])
paths
end
# Add runtime options that help actions execution.
#
def add_runtime_options!
class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
:desc => "Overwrite files that already exist"
class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
:desc => "Run but do not make any changes"
class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
:desc => "Suppress status output"
class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
:desc => "Skip files that already exist"
end
end
# Extends initializer to add more configuration options.
#
# ==== Configuration
# behavior<Symbol>:: The actions default behavior. Can be :invoke or :revoke.
# It also accepts :force, :skip and :pretend to set the behavior
# and the respective option.
#
# destination_root<String>:: The root directory needed for some actions.
#
def initialize(args=[], options={}, config={})
self.behavior = case config[:behavior].to_s
when "force", "skip"
_cleanup_options_and_set(options, config[:behavior])
:invoke
when "revoke"
:revoke
else
:invoke
end
super
self.destination_root = config[:destination_root]
end
# Wraps an action object and call it accordingly to the thor class behavior.
#
def action(instance) #:nodoc:
if behavior == :revoke
instance.revoke!
else
instance.invoke!
end
end
# Returns the root for this thor class (also aliased as destination root).
#
def destination_root
@destination_stack.last
end
# Sets the root for this thor class. Relatives path are added to the
# directory where the script was invoked and expanded.
#
def destination_root=(root)
@destination_stack ||= []
@destination_stack[0] = File.expand_path(root || '')
end
# Returns the given path relative to the absolute root (ie, root where
# the script started).
#
def relative_to_original_destination_root(path, remove_dot=true)
path = path.dup
if path.gsub!(@destination_stack[0], '.')
remove_dot ? (path[2..-1] || '') : path
else
path
end
end
# Holds source paths in instance so they can be manipulated.
#
def source_paths
@source_paths ||= self.class.source_paths_for_search
end
# Receives a file or directory and search for it in the source paths.
#
def find_in_source_paths(file)
relative_root = relative_to_original_destination_root(destination_root, false)
source_paths.each do |source|
source_file = File.expand_path(file, File.join(source, relative_root))
return source_file if File.exists?(source_file)
end
message = "Could not find #{file.inspect} in any of your source paths. "
unless self.class.source_root
message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
end
if source_paths.empty?
message << "Currently you have no source paths."
else
message << "Your current source paths are: \n#{source_paths.join("\n")}"
end
raise Error, message
end
# Do something in the root or on a provided subfolder. If a relative path
# is given it's referenced from the current root. The full path is yielded
# to the block you provide. The path is set back to the previous path when
# the method exits.
#
# ==== Parameters
# dir<String>:: the directory to move to.
# config<Hash>:: give :verbose => true to log and use padding.
#
def inside(dir='', config={}, &block)
verbose = config.fetch(:verbose, false)
pretend = options[:pretend]
say_status :inside, dir, verbose
shell.padding += 1 if verbose
@destination_stack.push File.expand_path(dir, destination_root)
# If the directory doesnt exist and we're not pretending
if !File.exist?(destination_root) && !pretend
FileUtils.mkdir_p(destination_root)
end
if pretend
# In pretend mode, just yield down to the block
block.arity == 1 ? yield(destination_root) : yield
else
FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
end
@destination_stack.pop
shell.padding -= 1 if verbose
end
# Goes to the root and execute the given block.
#
def in_root
inside(@destination_stack.first) { yield }
end
# Loads an external file and execute it in the instance binding.
#
# ==== Parameters
# path<String>:: The path to the file to execute. Can be a web address or
# a relative path from the source root.
#
# ==== Examples
#
# apply "http://gist.github.com/103208"
#
# apply "recipes/jquery.rb"
#
def apply(path, config={})
verbose = config.fetch(:verbose, true)
is_uri = path =~ /^https?\:\/\//
path = find_in_source_paths(path) unless is_uri
say_status :apply, path, verbose
shell.padding += 1 if verbose
if is_uri
contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
else
contents = open(path) {|io| io.read }
end
instance_eval(contents, path)
shell.padding -= 1 if verbose
end
# Executes a command returning the contents of the command.
#
# ==== Parameters
# command<String>:: the command to be executed.
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
# to append an executable to command executation.
#
# ==== Example
#
# inside('vendor') do
# run('ln -s ~/edge rails')
# end
#
def run(command, config={})
return unless behavior == :invoke
destination = relative_to_original_destination_root(destination_root, false)
desc = "#{command} from #{destination.inspect}"
if config[:with]
desc = "#{File.basename(config[:with].to_s)} #{desc}"
command = "#{config[:with]} #{command}"
end
say_status :run, desc, config.fetch(:verbose, true)
unless options[:pretend]
config[:capture] ? `#{command}` : system("#{command}")
end
end
# Executes a ruby script (taking into account WIN32 platform quirks).
#
# ==== Parameters
# command<String>:: the command to be executed.
# config<Hash>:: give :verbose => false to not log the status.
#
def run_ruby_script(command, config={})
return unless behavior == :invoke
run command, config.merge(:with => Thor::Util.ruby_command)
end
# Run a thor command. A hash of options can be given and it's converted to
# switches.
#
# ==== Parameters
# command<String>:: the command to be invoked
# args<Array>:: arguments to the command
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
# Other options are given as parameter to Thor.
#
#
# ==== Examples
#
# thor :install, "http://gist.github.com/103208"
# #=> thor install http://gist.github.com/103208
#
# thor :list, :all => true, :substring => 'rails'
# #=> thor list --all --substring=rails
#
def thor(command, *args)
config = args.last.is_a?(Hash) ? args.pop : {}
verbose = config.key?(:verbose) ? config.delete(:verbose) : true
pretend = config.key?(:pretend) ? config.delete(:pretend) : false
capture = config.key?(:capture) ? config.delete(:capture) : false
args.unshift(command)
args.push Thor::Options.to_switches(config)
command = args.join(' ').strip
run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
end
protected
# Allow current root to be shared between invocations.
#
def _shared_configuration #:nodoc:
super.merge!(:destination_root => self.destination_root)
end
def _cleanup_options_and_set(options, key) #:nodoc:
case options
when Array
%w(--force -f --skip -s).each { |i| options.delete(i) }
options << "--#{key}"
when Hash
[:force, :skip, "force", "skip"].each { |i| options.delete(i) }
options.merge!(key => true)
end
end
end
end

View File

@@ -0,0 +1,105 @@
require 'thor/actions/empty_directory'
class Thor
module Actions
# Create a new file relative to the destination root with the given data,
# which is the return value of a block or a data string.
#
# ==== Parameters
# destination<String>:: the relative path to the destination root.
# data<String|NilClass>:: the data to append to the file.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# create_file "lib/fun_party.rb" do
# hostname = ask("What is the virtual hostname I should use?")
# "vhost.name = #{hostname}"
# end
#
# create_file "config/apache.conf", "your apache config"
#
def create_file(destination, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
data = args.first
action CreateFile.new(self, destination, block || data.to_s, config)
end
alias :add_file :create_file
# CreateFile is a subset of Template, which instead of rendering a file with
# ERB, it gets the content from the user.
#
class CreateFile < EmptyDirectory #:nodoc:
attr_reader :data
def initialize(base, destination, data, config={})
@data = data
super(base, destination, config)
end
# Checks if the content of the file at the destination is identical to the rendered result.
#
# ==== Returns
# Boolean:: true if it is identical, false otherwise.
#
def identical?
exists? && File.binread(destination) == render
end
# Holds the content to be added to the file.
#
def render
@render ||= if data.is_a?(Proc)
data.call
else
data
end
end
def invoke!
invoke_with_conflict_check do
FileUtils.mkdir_p(File.dirname(destination))
File.open(destination, 'wb') { |f| f.write render }
end
given_destination
end
protected
# Now on conflict we check if the file is identical or not.
#
def on_conflict_behavior(&block)
if identical?
say_status :identical, :blue
else
options = base.options.merge(config)
force_or_skip_or_conflict(options[:force], options[:skip], &block)
end
end
# If force is true, run the action, otherwise check if it's not being
# skipped. If both are false, show the file_collision menu, if the menu
# returns true, force it, otherwise skip.
#
def force_or_skip_or_conflict(force, skip, &block)
if force
say_status :force, :yellow
block.call unless pretend?
elsif skip
say_status :skip, :yellow
else
say_status :conflict, :red
force_or_skip_or_conflict(force_on_collision?, true, &block)
end
end
# Shows the file collision menu to the user and gets the result.
#
def force_on_collision?
base.shell.file_collision(destination){ render }
end
end
end
end

View File

@@ -0,0 +1,60 @@
require 'thor/actions/create_file'
class Thor
module Actions
# Create a new file relative to the destination root from the given source.
#
# ==== Parameters
# destination<String>:: the relative path to the destination root.
# source<String|NilClass>:: the relative path to the source root.
# config<Hash>:: give :verbose => false to not log the status.
# :: give :symbolic => false for hard link.
#
# ==== Examples
#
# create_link "config/apache.conf", "/etc/apache.conf"
#
def create_link(destination, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
source = args.first
action CreateLink.new(self, destination, source, config)
end
alias :add_link :create_link
# CreateLink is a subset of CreateFile, which instead of taking a block of
# data, just takes a source string from the user.
#
class CreateLink < CreateFile #:nodoc:
attr_reader :data
# Checks if the content of the file at the destination is identical to the rendered result.
#
# ==== Returns
# Boolean:: true if it is identical, false otherwise.
#
def identical?
exists? && File.identical?(render, destination)
end
def invoke!
invoke_with_conflict_check do
FileUtils.mkdir_p(File.dirname(destination))
# Create a symlink by default
config[:symbolic] = true if config[:symbolic].nil?
File.unlink(destination) if exists?
if config[:symbolic]
File.symlink(render, destination)
else
File.link(render, destination)
end
end
given_destination
end
def exists?
super || File.symlink?(destination)
end
end
end
end

View File

@@ -0,0 +1,119 @@
require 'thor/actions/empty_directory'
class Thor
module Actions
# Copies recursively the files from source directory to root directory.
# If any of the files finishes with .tt, it's considered to be a template
# and is placed in the destination without the extension .tt. If any
# empty directory is found, it's copied and all .empty_directory files are
# ignored. If any file name is wrapped within % signs, the text within
# the % signs will be executed as a method and replaced with the returned
# value. Let's suppose a doc directory with the following files:
#
# doc/
# components/.empty_directory
# README
# rdoc.rb.tt
# %app_name%.rb
#
# When invoked as:
#
# directory "doc"
#
# It will create a doc directory in the destination with the following
# files (assuming that the `app_name` method returns the value "blog"):
#
# doc/
# components/
# README
# rdoc.rb
# blog.rb
#
# <b>Encoded path note:</b> Since Thor internals use Object#respond_to? to check if it can
# expand %something%, this `something` should be a public method in the class calling
# #directory. If a method is private, Thor stack raises PrivateMethodEncodedError.
#
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
# If :recursive => false, does not look for paths recursively.
# If :mode => :preserve, preserve the file mode from the source.
# If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
#
# ==== Examples
#
# directory "doc"
# directory "doc", "docs", :recursive => false
#
def directory(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
action Directory.new(self, source, destination || source, config, &block)
end
class Directory < EmptyDirectory #:nodoc:
attr_reader :source
def initialize(base, source, destination=nil, config={}, &block)
@source = File.expand_path(base.find_in_source_paths(source.to_s))
@block = block
super(base, destination, { :recursive => true }.merge(config))
end
def invoke!
base.empty_directory given_destination, config
execute!
end
def revoke!
execute!
end
protected
def execute!
lookup = Util.escape_globs(source)
lookup = config[:recursive] ? File.join(lookup, '**') : lookup
lookup = file_level_lookup(lookup)
files(lookup).sort.each do |file_source|
next if File.directory?(file_source)
next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/')
case file_source
when /\.empty_directory$/
dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
next if dirname == given_destination
base.empty_directory(dirname, config)
when /\.tt$/
destination = base.template(file_source, file_destination[0..-4], config, &@block)
else
destination = base.copy_file(file_source, file_destination, config, &@block)
end
end
end
if RUBY_VERSION < '2.0'
def file_level_lookup(previous_lookup)
File.join(previous_lookup, '{*,.[a-z]*}')
end
def files(lookup)
Dir[lookup]
end
else
def file_level_lookup(previous_lookup)
File.join(previous_lookup, '*')
end
def files(lookup)
Dir.glob(lookup, File::FNM_DOTMATCH)
end
end
end
end
end

View File

@@ -0,0 +1,137 @@
class Thor
module Actions
# Creates an empty directory.
#
# ==== Parameters
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# empty_directory "doc"
#
def empty_directory(destination, config={})
action EmptyDirectory.new(self, destination, config)
end
# Class which holds create directory logic. This is the base class for
# other actions like create_file and directory.
#
# This implementation is based in Templater actions, created by Jonas Nicklas
# and Michael S. Klishin under MIT LICENSE.
#
class EmptyDirectory #:nodoc:
attr_reader :base, :destination, :given_destination, :relative_destination, :config
# Initializes given the source and destination.
#
# ==== Parameters
# base<Thor::Base>:: A Thor::Base instance
# source<String>:: Relative path to the source of this file
# destination<String>:: Relative path to the destination of this file
# config<Hash>:: give :verbose => false to not log the status.
#
def initialize(base, destination, config={})
@base, @config = base, { :verbose => true }.merge(config)
self.destination = destination
end
# Checks if the destination file already exists.
#
# ==== Returns
# Boolean:: true if the file exists, false otherwise.
#
def exists?
::File.exists?(destination)
end
def invoke!
invoke_with_conflict_check do
::FileUtils.mkdir_p(destination)
end
end
def revoke!
say_status :remove, :red
::FileUtils.rm_rf(destination) if !pretend? && exists?
given_destination
end
protected
# Shortcut for pretend.
#
def pretend?
base.options[:pretend]
end
# Sets the absolute destination value from a relative destination value.
# It also stores the given and relative destination. Let's suppose our
# script is being executed on "dest", it sets the destination root to
# "dest". The destination, given_destination and relative_destination
# are related in the following way:
#
# inside "bar" do
# empty_directory "baz"
# end
#
# destination #=> dest/bar/baz
# relative_destination #=> bar/baz
# given_destination #=> baz
#
def destination=(destination)
if destination
@given_destination = convert_encoded_instructions(destination.to_s)
@destination = ::File.expand_path(@given_destination, base.destination_root)
@relative_destination = base.relative_to_original_destination_root(@destination)
end
end
# Filenames in the encoded form are converted. If you have a file:
#
# %file_name%.rb
#
# It calls #file_name from the base and replaces %-string with the
# return value (should be String) of #file_name:
#
# user.rb
#
# The method referenced can be either public or private.
#
def convert_encoded_instructions(filename)
filename.gsub(/%(.*?)%/) do |initial_string|
method = $1.strip
base.respond_to?(method, true) ? base.send(method) : initial_string
end
end
# Receives a hash of options and just execute the block if some
# conditions are met.
#
def invoke_with_conflict_check(&block)
if exists?
on_conflict_behavior(&block)
else
say_status :create, :green
block.call unless pretend?
end
destination
end
# What to do when the destination file already exists.
#
def on_conflict_behavior(&block)
say_status :exist, :blue
end
# Shortcut to say_status shell method.
#
def say_status(status, color)
base.shell.say_status status, relative_destination, color if config[:verbose]
end
end
end
end

View File

@@ -0,0 +1,314 @@
require 'erb'
require 'open-uri'
class Thor
module Actions
# Copies the file from the relative source to the relative destination. If
# the destination is not given it's assumed to be equal to the source.
#
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status, and
# :mode => :preserve, to preserve the file mode from the source.
#
# ==== Examples
#
# copy_file "README", "doc/README"
#
# copy_file "doc/README"
#
def copy_file(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
source = File.expand_path(find_in_source_paths(source.to_s))
create_file destination, nil, config do
content = File.binread(source)
content = block.call(content) if block
content
end
if config[:mode] == :preserve
mode = File.stat(source).mode
chmod(destination, mode, config)
end
end
# Links the file from the relative source to the relative destination. If
# the destination is not given it's assumed to be equal to the source.
#
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# link_file "README", "doc/README"
#
# link_file "doc/README"
#
def link_file(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
source = File.expand_path(find_in_source_paths(source.to_s))
create_link destination, source, config
end
# Gets the content at the given address and places it at the given relative
# destination. If a block is given instead of destination, the content of
# the url is yielded and used as location.
#
# ==== Parameters
# source<String>:: the address of the given content.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# get "http://gist.github.com/103208", "doc/README"
#
# get "http://gist.github.com/103208" do |content|
# content.split("\n").first
# end
#
def get(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^https?\:\/\//
render = open(source) {|input| input.binmode.read }
destination ||= if block_given?
block.arity == 1 ? block.call(render) : block.call
else
File.basename(source)
end
create_file destination, render, config
end
# Gets an ERB template at the relative source, executes it and makes a copy
# at the relative destination. If the destination is not given it's assumed
# to be equal to the source removing .tt from the filename.
#
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# template "README", "doc/README"
#
# template "doc/README"
#
def template(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source.sub(/\.tt$/, '')
source = File.expand_path(find_in_source_paths(source.to_s))
context = instance_eval('binding')
create_file destination, nil, config do
content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
content = block.call(content) if block
content
end
end
# Changes the mode of the given file or directory.
#
# ==== Parameters
# mode<Integer>:: the file mode
# path<String>:: the name of the file to change mode
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# chmod "script/server", 0755
#
def chmod(path, mode, config={})
return unless behavior == :invoke
path = File.expand_path(path, destination_root)
say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
FileUtils.chmod_R(mode, path) unless options[:pretend]
end
# Prepend text to a file. Since it depends on insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# data<String>:: the data to prepend to the file, can be also given as a block.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
#
# prepend_to_file 'config/environments/test.rb' do
# 'config.gem "rspec"'
# end
#
def prepend_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /\A/)
insert_into_file(path, *(args << config), &block)
end
alias_method :prepend_file, :prepend_to_file
# Append text to a file. Since it depends on insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# data<String>:: the data to append to the file, can be also given as a block.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
#
# append_to_file 'config/environments/test.rb' do
# 'config.gem "rspec"'
# end
#
def append_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:before => /\z/)
insert_into_file(path, *(args << config), &block)
end
alias_method :append_file, :append_to_file
# Injects text right after the class definition. Since it depends on
# insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# klass<String|Class>:: the class to be manipulated
# data<String>:: the data to append to the class, can be also given as a block.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
#
# inject_into_class "app/controllers/application_controller.rb", ApplicationController do
# " filter_parameter :password\n"
# end
#
def inject_into_class(path, klass, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
insert_into_file(path, *(args << config), &block)
end
# Run a regular expression replacement on a file.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# flag<Regexp|String>:: the regexp or string to be replaced
# replacement<String>:: the replacement, can be also given as a block
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
#
# gsub_file 'README', /rake/, :green do |match|
# match << " no more. Use thor!"
# end
#
def gsub_file(path, flag, *args, &block)
return unless behavior == :invoke
config = args.last.is_a?(Hash) ? args.pop : {}
path = File.expand_path(path, destination_root)
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
unless options[:pretend]
content = File.binread(path)
content.gsub!(flag, *args, &block)
File.open(path, 'wb') { |file| file.write(content) }
end
end
# Uncomment all lines matching a given regex. It will leave the space
# which existed before the comment hash in tact but will remove any spacing
# between the comment hash and the beginning of the line.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# flag<Regexp|String>:: the regexp or string used to decide which lines to uncomment
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# uncomment_lines 'config/initializers/session_store.rb', /active_record/
#
def uncomment_lines(path, flag, *args)
flag = flag.respond_to?(:source) ? flag.source : flag
gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
end
# Comment all lines matching a given regex. It will leave the space
# which existed before the beginning of the line in tact and will insert
# a single space after the comment hash.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# flag<Regexp|String>:: the regexp or string used to decide which lines to comment
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# comment_lines 'config/initializers/session_store.rb', /cookie_store/
#
def comment_lines(path, flag, *args)
flag = flag.respond_to?(:source) ? flag.source : flag
gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
end
# Removes a file at the given location.
#
# ==== Parameters
# path<String>:: path of the file to be changed
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Example
#
# remove_file 'README'
# remove_file 'app/controllers/application_controller.rb'
#
def remove_file(path, config={})
return unless behavior == :invoke
path = File.expand_path(path, destination_root)
say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
end
alias :remove_dir :remove_file
private
attr_accessor :output_buffer
def concat(string)
@output_buffer.concat(string)
end
def capture(*args, &block)
with_output_buffer { block.call(*args) }
end
def with_output_buffer(buf = '') #:nodoc:
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
end
end

View File

@@ -0,0 +1,109 @@
require 'thor/actions/empty_directory'
class Thor
module Actions
# Injects the given content into a file. Different from gsub_file, this
# method is reversible.
#
# ==== Parameters
# destination<String>:: Relative path to the destination root
# data<String>:: Data to add to the file. Can be given as a block.
# config<Hash>:: give :verbose => false to not log the status and the flag
# for injection (:after or :before) or :force => true for
# insert two or more times the same content.
#
# ==== Examples
#
# insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
#
# insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
# gems = ask "Which gems would you like to add?"
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
# end
#
def insert_into_file(destination, *args, &block)
if block_given?
data, config = block, args.shift
else
data, config = args.shift, args.shift
end
action InjectIntoFile.new(self, destination, data, config)
end
alias_method :inject_into_file, :insert_into_file
class InjectIntoFile < EmptyDirectory #:nodoc:
attr_reader :replacement, :flag, :behavior
def initialize(base, destination, data, config)
super(base, destination, { :verbose => true }.merge(config))
@behavior, @flag = if @config.key?(:after)
[:after, @config.delete(:after)]
else
[:before, @config.delete(:before)]
end
@replacement = data.is_a?(Proc) ? data.call : data
@flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
end
def invoke!
say_status :invoke
content = if @behavior == :after
'\0' + replacement
else
replacement + '\0'
end
replace!(/#{flag}/, content, config[:force])
end
def revoke!
say_status :revoke
regexp = if @behavior == :after
content = '\1\2'
/(#{flag})(.*)(#{Regexp.escape(replacement)})/m
else
content = '\2\3'
/(#{Regexp.escape(replacement)})(.*)(#{flag})/m
end
replace!(regexp, content, true)
end
protected
def say_status(behavior)
status = if behavior == :invoke
if flag == /\A/
:prepend
elsif flag == /\z/
:append
else
:insert
end
else
:subtract
end
super(status, config[:verbose])
end
# Adds the content to the file.
#
def replace!(regexp, string, force)
unless base.options[:pretend]
content = File.binread(destination)
if force || !content.include?(replacement)
content.gsub!(regexp, string)
File.open(destination, 'wb') { |file| file.write(content) }
end
end
end
end
end
end

652
vendor/gems/thor-0.18.1/lib/thor/base.rb vendored Normal file
View File

@@ -0,0 +1,652 @@
require 'thor/command'
require 'thor/core_ext/hash_with_indifferent_access'
require 'thor/core_ext/ordered_hash'
require 'thor/error'
require 'thor/invocation'
require 'thor/parser'
require 'thor/shell'
require 'thor/util'
class Thor
autoload :Actions, 'thor/actions'
autoload :RakeCompat, 'thor/rake_compat'
autoload :Group, 'thor/group'
# Shortcuts for help.
HELP_MAPPINGS = %w(-h -? --help -D)
# Thor methods that should not be overwritten by the user.
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
action add_file create_file in_root inside run run_ruby_script)
module Base
attr_accessor :options, :parent_options, :args
# It receives arguments in an Array and two hashes, one for options and
# other for configuration.
#
# Notice that it does not check if all required arguments were supplied.
# It should be done by the parser.
#
# ==== Parameters
# args<Array[Object]>:: An array of objects. The objects are applied to their
# respective accessors declared with <tt>argument</tt>.
#
# options<Hash>:: An options hash that will be available as self.options.
# The hash given is converted to a hash with indifferent
# access, magic predicates (options.skip?) and then frozen.
#
# config<Hash>:: Configuration for this Thor class.
#
def initialize(args=[], options={}, config={})
parse_options = self.class.class_options
# The start method splits inbound arguments at the first argument
# that looks like an option (starts with - or --). It then calls
# new, passing in the two halves of the arguments Array as the
# first two parameters.
if options.is_a?(Array)
command_options = config.delete(:command_options) # hook for start
parse_options = parse_options.merge(command_options) if command_options
array_options, hash_options = options, {}
else
# Handle the case where the class was explicitly instantiated
# with pre-parsed options.
array_options, hash_options = [], options
end
# Let Thor::Options parse the options first, so it can remove
# declared options from the array. This will leave us with
# a list of arguments that weren't declared.
stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown)
self.options = opts.parse(array_options)
self.options = config[:class_options].merge(self.options) if config[:class_options]
# If unknown options are disallowed, make sure that none of the
# remaining arguments looks like an option.
opts.check_unknown! if self.class.check_unknown_options?(config)
# Add the remaining arguments from the options parser to the
# arguments passed in to initialize. Then remove any positional
# arguments declared using #argument (this is primarily used
# by Thor::Group). Tis will leave us with the remaining
# positional arguments.
to_parse = args
to_parse += opts.remaining unless self.class.strict_args_position?(config)
thor_args = Thor::Arguments.new(self.class.arguments)
thor_args.parse(to_parse).each { |k,v| __send__("#{k}=", v) }
@args = thor_args.remaining
end
class << self
def included(base) #:nodoc:
base.send :extend, ClassMethods
base.send :include, Invocation
base.send :include, Shell
end
# Returns the classes that inherits from Thor or Thor::Group.
#
# ==== Returns
# Array[Class]
#
def subclasses
@subclasses ||= []
end
# Returns the files where the subclasses are kept.
#
# ==== Returns
# Hash[path<String> => Class]
#
def subclass_files
@subclass_files ||= Hash.new{ |h,k| h[k] = [] }
end
# Whenever a class inherits from Thor or Thor::Group, we should track the
# class and the file on Thor::Base. This is the method responsable for it.
#
def register_klass_file(klass) #:nodoc:
file = caller[1].match(/(.*):\d+/)[1]
Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
file_subclasses << klass unless file_subclasses.include?(klass)
end
end
module ClassMethods
def attr_reader(*) #:nodoc:
no_commands { super }
end
def attr_writer(*) #:nodoc:
no_commands { super }
end
def attr_accessor(*) #:nodoc:
no_commands { super }
end
# If you want to raise an error for unknown options, call check_unknown_options!
# This is disabled by default to allow dynamic invocations.
def check_unknown_options!
@check_unknown_options = true
end
def check_unknown_options #:nodoc:
@check_unknown_options ||= from_superclass(:check_unknown_options, false)
end
def check_unknown_options?(config) #:nodoc:
!!check_unknown_options
end
# If true, option parsing is suspended as soon as an unknown option or a
# regular argument is encountered. All remaining arguments are passed to
# the command as regular arguments.
def stop_on_unknown_option?(command_name) #:nodoc:
false
end
# If you want only strict string args (useful when cascading thor classes),
# call strict_args_position! This is disabled by default to allow dynamic
# invocations.
def strict_args_position!
@strict_args_position = true
end
def strict_args_position #:nodoc:
@strict_args_position ||= from_superclass(:strict_args_position, false)
end
def strict_args_position?(config) #:nodoc:
!!strict_args_position
end
# Adds an argument to the class and creates an attr_accessor for it.
#
# Arguments are different from options in several aspects. The first one
# is how they are parsed from the command line, arguments are retrieved
# from position:
#
# thor command NAME
#
# Instead of:
#
# thor command --name=NAME
#
# Besides, arguments are used inside your code as an accessor (self.argument),
# while options are all kept in a hash (self.options).
#
# Finally, arguments cannot have type :default or :boolean but can be
# optional (supplying :optional => :true or :required => false), although
# you cannot have a required argument after a non-required argument. If you
# try it, an error is raised.
#
# ==== Parameters
# name<Symbol>:: The name of the argument.
# options<Hash>:: Described below.
#
# ==== Options
# :desc - Description for the argument.
# :required - If the argument is required or not.
# :optional - If the argument is optional or not.
# :type - The type of the argument, can be :string, :hash, :array, :numeric.
# :default - Default value for this argument. It cannot be required and have default values.
# :banner - String to show on usage notes.
#
# ==== Errors
# ArgumentError:: Raised if you supply a required argument after a non required one.
#
def argument(name, options={})
is_thor_reserved_word?(name, :argument)
no_commands { attr_accessor name }
required = if options.key?(:optional)
!options[:optional]
elsif options.key?(:required)
options[:required]
else
options[:default].nil?
end
remove_argument name
arguments.each do |argument|
next if argument.required?
raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " <<
"the non-required argument #{argument.human_name.inspect}."
end if required
options[:required] = required
arguments << Thor::Argument.new(name, options)
end
# Returns this class arguments, looking up in the ancestors chain.
#
# ==== Returns
# Array[Thor::Argument]
#
def arguments
@arguments ||= from_superclass(:arguments, [])
end
# Adds a bunch of options to the set of class options.
#
# class_options :foo => false, :bar => :required, :baz => :string
#
# If you prefer more detailed declaration, check class_option.
#
# ==== Parameters
# Hash[Symbol => Object]
#
def class_options(options=nil)
@class_options ||= from_superclass(:class_options, {})
build_options(options, @class_options) if options
@class_options
end
# Adds an option to the set of class options
#
# ==== Parameters
# name<Symbol>:: The name of the argument.
# options<Hash>:: Described below.
#
# ==== Options
# :desc:: -- Description for the argument.
# :required:: -- If the argument is required or not.
# :default:: -- Default value for this argument.
# :group:: -- The group for this options. Use by class options to output options in different levels.
# :aliases:: -- Aliases for this option. <b>Note:</b> Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead.
# :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :banner:: -- String to show on usage notes.
# :hide:: -- If you want to hide this option from the help.
#
def class_option(name, options={})
build_option(name, options, class_options)
end
# Removes a previous defined argument. If :undefine is given, undefine
# accessors as well.
#
# ==== Parameters
# names<Array>:: Arguments to be removed
#
# ==== Examples
#
# remove_argument :foo
# remove_argument :foo, :bar, :baz, :undefine => true
#
def remove_argument(*names)
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
arguments.delete_if { |a| a.name == name.to_s }
undef_method name, "#{name}=" if options[:undefine]
end
end
# Removes a previous defined class option.
#
# ==== Parameters
# names<Array>:: Class options to be removed
#
# ==== Examples
#
# remove_class_option :foo
# remove_class_option :foo, :bar, :baz
#
def remove_class_option(*names)
names.each do |name|
class_options.delete(name)
end
end
# Defines the group. This is used when thor list is invoked so you can specify
# that only commands from a pre-defined group will be shown. Defaults to standard.
#
# ==== Parameters
# name<String|Symbol>
#
def group(name=nil)
@group = case name
when nil
@group || from_superclass(:group, 'standard')
else
name.to_s
end
end
# Returns the commands for this Thor class.
#
# ==== Returns
# OrderedHash:: An ordered hash with commands names as keys and Thor::Command
# objects as values.
#
def commands
@commands ||= Thor::CoreExt::OrderedHash.new
end
alias tasks commands
# Returns the commands for this Thor class and all subclasses.
#
# ==== Returns
# OrderedHash:: An ordered hash with commands names as keys and Thor::Command
# objects as values.
#
def all_commands
@all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
@all_commands.merge(commands)
end
alias all_tasks all_commands
# Removes a given command from this Thor class. This is usually done if you
# are inheriting from another class and don't want it to be available
# anymore.
#
# By default it only remove the mapping to the command. But you can supply
# :undefine => true to undefine the method from the class as well.
#
# ==== Parameters
# name<Symbol|String>:: The name of the command to be removed
# options<Hash>:: You can give :undefine => true if you want commands the method
# to be undefined from the class as well.
#
def remove_command(*names)
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
commands.delete(name.to_s)
all_commands.delete(name.to_s)
undef_method name if options[:undefine]
end
end
alias remove_task remove_command
# All methods defined inside the given block are not added as commands.
#
# So you can do:
#
# class MyScript < Thor
# no_commands do
# def this_is_not_a_command
# end
# end
# end
#
# You can also add the method and remove it from the command list:
#
# class MyScript < Thor
# def this_is_not_a_command
# end
# remove_command :this_is_not_a_command
# end
#
def no_commands
@no_commands = true
yield
ensure
@no_commands = false
end
alias no_tasks no_commands
# Sets the namespace for the Thor or Thor::Group class. By default the
# namespace is retrieved from the class name. If your Thor class is named
# Scripts::MyScript, the help method, for example, will be called as:
#
# thor scripts:my_script -h
#
# If you change the namespace:
#
# namespace :my_scripts
#
# You change how your commands are invoked:
#
# thor my_scripts -h
#
# Finally, if you change your namespace to default:
#
# namespace :default
#
# Your commands can be invoked with a shortcut. Instead of:
#
# thor :my_command
#
def namespace(name=nil)
@namespace = case name
when nil
@namespace || Thor::Util.namespace_from_thor_class(self)
else
@namespace = name.to_s
end
end
# Parses the command and options from the given args, instantiate the class
# and invoke the command. This method is used when the arguments must be parsed
# from an array. If you are inside Ruby and want to use a Thor class, you
# can simply initialize it:
#
# script = MyScript.new(args, options, config)
# script.invoke(:command, first_arg, second_arg, third_arg)
#
def start(given_args=ARGV, config={})
config[:shell] ||= Thor::Base.shell.new
dispatch(nil, given_args.dup, nil, config)
rescue Thor::Error => e
ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
exit(1) if exit_on_failure?
rescue Errno::EPIPE
# This happens if a thor command is piped to something like `head`,
# which closes the pipe when it's done reading. This will also
# mean that if the pipe is closed, further unnecessary
# computation will not occur.
exit(0)
end
# Allows to use private methods from parent in child classes as commands.
#
# ==== Parameters
# names<Array>:: Method names to be used as commands
#
# ==== Examples
#
# public_command :foo
# public_command :foo, :bar, :baz
#
def public_command(*names)
names.each do |name|
class_eval "def #{name}(*); super end"
end
end
alias public_task public_command
def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
if has_namespace
raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace."
else
raise UndefinedCommandError, "Could not find command #{command.inspect}."
end
end
alias handle_no_task_error handle_no_command_error
def handle_argument_error(command, error, args, arity) #:nodoc:
msg = "ERROR: #{basename} #{command.name} was called with "
msg << 'no arguments' if args.empty?
msg << 'arguments ' << args.inspect if !args.empty?
msg << "\nUsage: #{self.banner(command).inspect}."
raise InvocationError, msg
end
protected
# Prints the class options per group. If an option does not belong to
# any group, it's printed as Class option.
#
def class_options_help(shell, groups={}) #:nodoc:
# Group options by group
class_options.each do |_, value|
groups[value.group] ||= []
groups[value.group] << value
end
# Deal with default group
global_options = groups.delete(nil) || []
print_options(shell, global_options)
# Print all others
groups.each do |group_name, options|
print_options(shell, options, group_name)
end
end
# Receives a set of options and print them.
def print_options(shell, options, group_name=nil)
return if options.empty?
list = []
padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
options.each do |option|
unless option.hide
item = [ option.usage(padding) ]
item.push(option.description ? "# #{option.description}" : "")
list << item
list << [ "", "# Default: #{option.default}" ] if option.show_default?
list << [ "", "# Possible values: #{option.enum.join(', ')}" ] if option.enum
end
end
shell.say(group_name ? "#{group_name} options:" : "Options:")
shell.print_table(list, :indent => 2)
shell.say ""
end
# Raises an error if the word given is a Thor reserved word.
def is_thor_reserved_word?(word, type) #:nodoc:
return false unless THOR_RESERVED_WORDS.include?(word.to_s)
raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
end
# Build an option and adds it to the given scope.
#
# ==== Parameters
# name<Symbol>:: The name of the argument.
# options<Hash>:: Described in both class_option and method_option.
# scope<Hash>:: Options hash that is being built up
def build_option(name, options, scope) #:nodoc:
scope[name] = Thor::Option.new(name, options)
end
# Receives a hash of options, parse them and add to the scope. This is a
# fast way to set a bunch of options:
#
# build_options :foo => true, :bar => :required, :baz => :string
#
# ==== Parameters
# Hash[Symbol => Object]
def build_options(options, scope) #:nodoc:
options.each do |key, value|
scope[key] = Thor::Option.parse(key, value)
end
end
# Finds a command with the given name. If the command belongs to the current
# class, just return it, otherwise dup it and add the fresh copy to the
# current command hash.
def find_and_refresh_command(name) #:nodoc:
command = if command = commands[name.to_s]
command
elsif command = all_commands[name.to_s]
commands[name.to_s] = command.clone
else
raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
end
end
alias find_and_refresh_task find_and_refresh_command
# Everytime someone inherits from a Thor class, register the klass
# and file into baseclass.
def inherited(klass)
Thor::Base.register_klass_file(klass)
klass.instance_variable_set(:@no_commands, false)
end
# Fire this callback whenever a method is added. Added methods are
# tracked as commands by invoking the create_command method.
def method_added(meth)
meth = meth.to_s
if meth == "initialize"
initialize_added
return
end
# Return if it's not a public instance method
return unless public_method_defined?(meth.to_sym)
return if @no_commands || !create_command(meth)
is_thor_reserved_word?(meth, :command)
Thor::Base.register_klass_file(self)
end
# Retrieves a value from superclass. If it reaches the baseclass,
# returns default.
def from_superclass(method, default=nil)
if self == baseclass || !superclass.respond_to?(method, true)
default
else
value = superclass.send(method)
if value
if value.is_a?(TrueClass) || value.is_a?(Symbol)
value
else
value.dup
end
end
end
end
# A flag that makes the process exit with status 1 if any error happens.
def exit_on_failure?
false
end
#
# The basename of the program invoking the thor class.
#
def basename
File.basename($0).split(' ').first
end
# SIGNATURE: Sets the baseclass. This is where the superclass lookup
# finishes.
def baseclass #:nodoc:
end
# SIGNATURE: Creates a new command if valid_command? is true. This method is
# called when a new method is added to the class.
def create_command(meth) #:nodoc:
end
alias create_task create_command
# SIGNATURE: Defines behavior when the initialize method is added to the
# class.
def initialize_added #:nodoc:
end
# SIGNATURE: The hook invoked by start.
def dispatch(command, given_args, given_opts, config) #:nodoc:
raise NotImplementedError
end
end
end
end

View File

@@ -0,0 +1,136 @@
class Thor
class Command < Struct.new(:name, :description, :long_description, :usage, :options)
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
def initialize(name, description, long_description, usage, options=nil)
super(name.to_s, description, long_description, usage, options || {})
end
def initialize_copy(other) #:nodoc:
super(other)
self.options = other.options.dup if other.options
end
def hidden?
false
end
# By default, a command invokes a method in the thor class. You can change this
# implementation to create custom commands.
def run(instance, args=[])
arity = nil
if private_method?(instance)
instance.class.handle_no_command_error(name)
elsif public_method?(instance)
arity = instance.method(name).arity
instance.__send__(name, *args)
elsif local_method?(instance, :method_missing)
instance.__send__(:method_missing, name.to_sym, *args)
else
instance.class.handle_no_command_error(name)
end
rescue ArgumentError => e
handle_argument_error?(instance, e, caller) ?
instance.class.handle_argument_error(self, e, args, arity) : (raise e)
rescue NoMethodError => e
handle_no_method_error?(instance, e, caller) ?
instance.class.handle_no_command_error(name) : (raise e)
end
# Returns the formatted usage by injecting given required arguments
# and required options into the given usage.
def formatted_usage(klass, namespace = true, subcommand = false)
if namespace
namespace = klass.namespace
formatted = "#{namespace.gsub(/^(default)/,'')}:"
end
formatted = "#{klass.namespace.split(':').last} " if subcommand
formatted ||= ""
# Add usage with required arguments
formatted << if klass && !klass.arguments.empty?
usage.to_s.gsub(/^#{name}/) do |match|
match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ')
end
else
usage.to_s
end
# Add required options
formatted << " #{required_options}"
# Strip and go!
formatted.strip
end
protected
def not_debugging?(instance)
!(instance.class.respond_to?(:debugging) && instance.class.debugging)
end
def required_options
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
end
# Given a target, checks if this class name is a public method.
def public_method?(instance) #:nodoc:
!(instance.public_methods & [name.to_s, name.to_sym]).empty?
end
def private_method?(instance)
!(instance.private_methods & [name.to_s, name.to_sym]).empty?
end
def local_method?(instance, name)
methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
!(methods & [name.to_s, name.to_sym]).empty?
end
def sans_backtrace(backtrace, caller) #:nodoc:
saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) }
saned -= caller
end
def handle_argument_error?(instance, error, caller)
not_debugging?(instance) && error.message =~ /wrong number of arguments/ && begin
saned = sans_backtrace(error.backtrace, caller)
# Ruby 1.9 always include the called method in the backtrace
saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
end
end
def handle_no_method_error?(instance, error, caller)
not_debugging?(instance) &&
error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
end
end
Task = Command
# A command that is hidden in help messages but still invocable.
class HiddenCommand < Command
def hidden?
true
end
end
HiddenTask = HiddenCommand
# A dynamic command that handles method missing scenarios.
class DynamicCommand < Command
def initialize(name, options=nil)
super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
end
def run(instance, args=[])
if (instance.methods & [name.to_s, name.to_sym]).empty?
super
else
instance.class.handle_no_command_error(name)
end
end
end
DynamicTask = DynamicCommand
end

View File

@@ -0,0 +1,80 @@
class Thor
module CoreExt #:nodoc:
# A hash with indifferent access and magic predicates.
#
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
#
# hash[:foo] #=> 'bar'
# hash['foo'] #=> 'bar'
# hash.foo? #=> true
#
class HashWithIndifferentAccess < ::Hash #:nodoc:
def initialize(hash={})
super()
hash.each do |key, value|
self[convert_key(key)] = value
end
end
def [](key)
super(convert_key(key))
end
def []=(key, value)
super(convert_key(key), value)
end
def delete(key)
super(convert_key(key))
end
def values_at(*indices)
indices.collect { |key| self[convert_key(key)] }
end
def merge(other)
dup.merge!(other)
end
def merge!(other)
other.each do |key, value|
self[convert_key(key)] = value
end
self
end
# Convert to a Hash with String keys.
def to_hash
Hash.new(default).merge!(self)
end
protected
def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end
# Magic predicates. For instance:
#
# options.force? # => !!options['force']
# options.shebang # => "/usr/lib/local/ruby"
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
#
def method_missing(method, *args, &block)
method = method.to_s
if method =~ /^(\w+)\?$/
if args.empty?
!!self[$1]
else
self[$1] == args.first
end
else
self[method]
end
end
end
end
end

View File

@@ -0,0 +1,12 @@
class IO #:nodoc:
class << self
def binread(file, *args)
raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
File.open(file, 'rb') do |f|
f.read(*args)
end
end unless method_defined? :binread
end
end

View File

@@ -0,0 +1,100 @@
class Thor
module CoreExt #:nodoc:
if RUBY_VERSION >= '1.9'
class OrderedHash < ::Hash
end
else
# This class is based on the Ruby 1.9 ordered hashes.
#
# It keeps the semantics and most of the efficiency of normal hashes
# while also keeping track of the order in which elements were set.
#
class OrderedHash #:nodoc:
include Enumerable
Node = Struct.new(:key, :value, :next, :prev)
def initialize
@hash = {}
end
def [](key)
@hash[key] && @hash[key].value
end
def []=(key, value)
if node = @hash[key]
node.value = value
else
node = Node.new(key, value)
if @first.nil?
@first = @last = node
else
node.prev = @last
@last.next = node
@last = node
end
end
@hash[key] = node
value
end
def delete(key)
if node = @hash[key]
prev_node = node.prev
next_node = node.next
next_node.prev = prev_node if next_node
prev_node.next = next_node if prev_node
@first = next_node if @first == node
@last = prev_node if @last == node
value = node.value
end
@hash.delete(key)
value
end
def keys
self.map { |k, v| k }
end
def values
self.map { |k, v| v }
end
def each
return unless @first
yield [@first.key, @first.value]
node = @first
yield [node.key, node.value] while node = node.next
self
end
def merge(other)
hash = self.class.new
self.each do |key, value|
hash[key] = value
end
other.each do |key, value|
hash[key] = value
end
hash
end
def empty?
@hash.empty?
end
end
end
end
end

View File

@@ -0,0 +1,28 @@
class Thor
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
# errors have their backtrace suppressed and are nicely shown to the user.
#
# Errors that are caused by the developer, like declaring a method which
# overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
# ensure that developer errors are shown with full backtrace.
class Error < StandardError
end
# Raised when a command was not found.
class UndefinedCommandError < Error
end
UndefinedTaskError = UndefinedCommandError
# Raised when a command was found, but not invoked properly.
class InvocationError < Error
end
class UnknownArgumentError < Error
end
class RequiredArgumentMissingError < InvocationError
end
class MalformattedArgumentError < InvocationError
end
end

View File

@@ -0,0 +1,282 @@
require 'thor/base'
# Thor has a special class called Thor::Group. The main difference to Thor class
# is that it invokes all commands at once. It also include some methods that allows
# invocations to be done at the class method, which are not available to Thor
# commands.
class Thor::Group
class << self
# The description for this Thor::Group. If none is provided, but a source root
# exists, tries to find the USAGE one folder above it, otherwise searches
# in the superclass.
#
# ==== Parameters
# description<String>:: The description for this Thor::Group.
#
def desc(description=nil)
@desc = case description
when nil
@desc || from_superclass(:desc, nil)
else
description
end
end
# Prints help information.
#
# ==== Options
# short:: When true, shows only usage.
#
def help(shell)
shell.say "Usage:"
shell.say " #{banner}\n"
shell.say
class_options_help(shell)
shell.say self.desc if self.desc
end
# Stores invocations for this class merging with superclass values.
#
def invocations #:nodoc:
@invocations ||= from_superclass(:invocations, {})
end
# Stores invocation blocks used on invoke_from_option.
#
def invocation_blocks #:nodoc:
@invocation_blocks ||= from_superclass(:invocation_blocks, {})
end
# Invoke the given namespace or class given. It adds an instance
# method that will invoke the klass and command. You can give a block to
# configure how it will be invoked.
#
# The namespace/class given will have its options showed on the help
# usage. Check invoke_from_option for more information.
#
def invoke(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, true)
names.each do |name|
invocations[name] = false
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_#{name.to_s.gsub(/\W/, '_')}
klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
if klass
say_status :invoke, #{name.inspect}, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, command, &block
else
say_status :error, %(#{name.inspect} [not found]), :red
end
end
METHOD
end
end
# Invoke a thor class based on the value supplied by the user to the
# given option named "name". A class option must be created before this
# method is invoked for each name given.
#
# ==== Examples
#
# class GemGenerator < Thor::Group
# class_option :test_framework, :type => :string
# invoke_from_option :test_framework
# end
#
# ==== Boolean options
#
# In some cases, you want to invoke a thor class if some option is true or
# false. This is automatically handled by invoke_from_option. Then the
# option name is used to invoke the generator.
#
# ==== Preparing for invocation
#
# In some cases you want to customize how a specified hook is going to be
# invoked. You can do that by overwriting the class method
# prepare_for_invocation. The class method must necessarily return a klass
# and an optional command.
#
# ==== Custom invocations
#
# You can also supply a block to customize how the option is going to be
# invoked. The block receives two parameters, an instance of the current
# class and the klass to be invoked.
#
def invoke_from_option(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, :white)
names.each do |name|
unless class_options.key?(name)
raise ArgumentError, "You have to define the option #{name.inspect} " <<
"before setting invoke_from_option."
end
invocations[name] = true
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
return unless options[#{name.inspect}]
value = options[#{name.inspect}]
value = #{name.inspect} if TrueClass === value
klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
if klass
say_status :invoke, value, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, command, &block
else
say_status :error, %(\#{value} [not found]), :red
end
end
METHOD
end
end
# Remove a previously added invocation.
#
# ==== Examples
#
# remove_invocation :test_framework
#
def remove_invocation(*names)
names.each do |name|
remove_command(name)
remove_class_option(name)
invocations.delete(name)
invocation_blocks.delete(name)
end
end
# Overwrite class options help to allow invoked generators options to be
# shown recursively when invoking a generator.
#
def class_options_help(shell, groups={}) #:nodoc:
get_options_from_invocations(groups, class_options) do |klass|
klass.send(:get_options_from_invocations, groups, class_options)
end
super(shell, groups)
end
# Get invocations array and merge options from invocations. Those
# options are added to group_options hash. Options that already exists
# in base_options are not added twice.
#
def get_options_from_invocations(group_options, base_options) #:nodoc:
invocations.each do |name, from_option|
value = if from_option
option = class_options[name]
option.type == :boolean ? name : option.default
else
name
end
next unless value
klass, _ = prepare_for_invocation(name, value)
next unless klass && klass.respond_to?(:class_options)
value = value.to_s
human_name = value.respond_to?(:classify) ? value.classify : value
group_options[human_name] ||= []
group_options[human_name] += klass.class_options.values.select do |class_option|
base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
!group_options.values.flatten.any? { |i| i.name == class_option.name }
end
yield klass if block_given?
end
end
# Returns commands ready to be printed.
def printable_commands(*)
item = []
item << banner
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
[item]
end
alias printable_tasks printable_commands
def handle_argument_error(command, error, args, arity) #:nodoc:
msg = "#{basename} #{command.name} takes #{arity} argument"
msg << "s" if arity > 1
msg << ", but it should not."
raise error, msg
end
protected
# The method responsible for dispatching given the args.
def dispatch(command, given_args, given_opts, config) #:nodoc:
if Thor::HELP_MAPPINGS.include?(given_args.first)
help(config[:shell])
return
end
args, opts = Thor::Options.split(given_args)
opts = given_opts || opts
instance = new(args, opts, config)
yield instance if block_given?
if command
instance.invoke_command(all_commands[command])
else
instance.invoke_all
end
end
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner.
def banner
"#{basename} #{self_command.formatted_usage(self, false)}"
end
# Represents the whole class as a command.
def self_command #:nodoc:
Thor::DynamicCommand.new(self.namespace, class_options)
end
alias self_task self_command
def baseclass #:nodoc:
Thor::Group
end
def create_command(meth) #:nodoc:
commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
true
end
alias create_task create_command
end
include Thor::Base
protected
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
def _invoke_for_class_method(klass, command=nil, *args, &block) #:nodoc:
with_padding do
if block
case block.arity
when 3
block.call(self, klass, command)
when 2
block.call(self, klass)
when 1
instance_exec(klass, &block)
end
else
invoke klass, command, *args
end
end
end
end

View File

@@ -0,0 +1,172 @@
class Thor
module Invocation
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
# This method is responsible for receiving a name and find the proper
# class and command for it. The key is an optional parameter which is
# available only in class methods invocations (i.e. in Thor::Group).
def prepare_for_invocation(key, name) #:nodoc:
case name
when Symbol, String
Thor::Util.find_class_and_command_by_namespace(name.to_s, !key)
else
name
end
end
end
# Make initializer aware of invocations and the initialization args.
def initialize(args=[], options={}, config={}, &block) #:nodoc:
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
@_initializer = [ args, options, config ]
super
end
# Receives a name and invokes it. The name can be a string (either "command" or
# "namespace:command"), a Thor::Command, a Class or a Thor instance. If the
# command cannot be guessed by name, it can also be supplied as second argument.
#
# You can also supply the arguments, options and configuration values for
# the command to be invoked, if none is given, the same values used to
# initialize the invoker are used to initialize the invoked.
#
# When no name is given, it will invoke the default command of the current class.
#
# ==== Examples
#
# class A < Thor
# def foo
# invoke :bar
# invoke "b:hello", ["José"]
# end
#
# def bar
# invoke "b:hello", ["José"]
# end
# end
#
# class B < Thor
# def hello(name)
# puts "hello #{name}"
# end
# end
#
# You can notice that the method "foo" above invokes two commands: "bar",
# which belongs to the same class and "hello" which belongs to the class B.
#
# By using an invocation system you ensure that a command is invoked only once.
# In the example above, invoking "foo" will invoke "b:hello" just once, even
# if it's invoked later by "bar" method.
#
# When class A invokes class B, all arguments used on A initialization are
# supplied to B. This allows lazy parse of options. Let's suppose you have
# some rspec commands:
#
# class Rspec < Thor::Group
# class_option :mock_framework, :type => :string, :default => :rr
#
# def invoke_mock_framework
# invoke "rspec:#{options[:mock_framework]}"
# end
# end
#
# As you noticed, it invokes the given mock framework, which might have its
# own options:
#
# class Rspec::RR < Thor::Group
# class_option :style, :type => :string, :default => :mock
# end
#
# Since it's not rspec concern to parse mock framework options, when RR
# is invoked all options are parsed again, so RR can extract only the options
# that it's going to use.
#
# If you want Rspec::RR to be initialized with its own set of options, you
# have to do that explicitly:
#
# invoke "rspec:rr", [], :style => :foo
#
# Besides giving an instance, you can also give a class to invoke:
#
# invoke Rspec::RR, [], :style => :foo
#
def invoke(name=nil, *args)
if name.nil?
warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
return invoke_all
end
args.unshift(nil) if Array === args.first || NilClass === args.first
command, args, opts, config = args
klass, command = _retrieve_class_and_command(name, command)
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
args, opts, config = _parse_initialization_options(args, opts, config)
klass.send(:dispatch, command, args, opts, config) do |instance|
instance.parent_options = options
end
end
# Invoke the given command if the given args.
def invoke_command(command, *args) #:nodoc:
current = @_invocations[self.class]
unless current.include?(command.name)
current << command.name
command.run(self, *args)
end
end
alias invoke_task invoke_command
# Invoke all commands for the current instance.
def invoke_all #:nodoc:
self.class.all_commands.map { |_, command| invoke_command(command) }
end
# Invokes using shell padding.
def invoke_with_padding(*args)
with_padding { invoke(*args) }
end
protected
# Configuration values that are shared between invocations.
def _shared_configuration #:nodoc:
{ :invocations => @_invocations }
end
# This method simply retrieves the class and command to be invoked.
# If the name is nil or the given name is a command in the current class,
# use the given name and return self as class. Otherwise, call
# prepare_for_invocation in the current class.
def _retrieve_class_and_command(name, sent_command=nil) #:nodoc:
case
when name.nil?
[self.class, nil]
when self.class.all_commands[name.to_s]
[self.class, name.to_s]
else
klass, command = self.class.prepare_for_invocation(nil, name)
[klass, command || sent_command]
end
end
alias _retrieve_class_and_task _retrieve_class_and_command
# Initialize klass using values stored in the @_initializer.
def _parse_initialization_options(args, opts, config) #:nodoc:
stored_args, stored_opts, stored_config = @_initializer
args ||= stored_args.dup
opts ||= stored_opts.dup
config ||= {}
config = stored_config.merge(_shared_configuration).merge!(config)
[ args, opts, config ]
end
end
end

View File

@@ -0,0 +1,4 @@
require 'thor/parser/argument'
require 'thor/parser/arguments'
require 'thor/parser/option'
require 'thor/parser/options'

View File

@@ -0,0 +1,74 @@
class Thor
class Argument #:nodoc:
VALID_TYPES = [ :numeric, :hash, :array, :string ]
attr_reader :name, :description, :enum, :required, :type, :default, :banner
alias :human_name :name
def initialize(name, options={})
class_name = self.class.name.split("::").last
type = options[:type]
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
@name = name.to_s
@description = options[:desc]
@required = options.key?(:required) ? options[:required] : true
@type = (type || :string).to_sym
@default = options[:default]
@banner = options[:banner] || default_banner
@enum = options[:enum]
validate! # Trigger specific validations
end
def usage
required? ? banner : "[#{banner}]"
end
def required?
required
end
def show_default?
case default
when Array, String, Hash
!default.empty?
else
default
end
end
protected
def validate!
if required? && !default.nil?
raise ArgumentError, "An argument cannot be required and have default value."
elsif @enum && !@enum.is_a?(Array)
raise ArgumentError, "An argument cannot have an enum other than an array."
end
end
def valid_type?(type)
self.class::VALID_TYPES.include?(type.to_sym)
end
def default_banner
case type
when :boolean
nil
when :string, :default
human_name.upcase
when :numeric
"N"
when :hash
"key:value"
when :array
"one two three"
end
end
end
end

View File

@@ -0,0 +1,171 @@
class Thor
class Arguments #:nodoc:
NUMERIC = /(\d*\.\d+|\d+)/
# Receives an array of args and returns two arrays, one with arguments
# and one with switches.
#
def self.split(args)
arguments = []
args.each do |item|
break if item =~ /^-/
arguments << item
end
return arguments, args[Range.new(arguments.size, -1)]
end
def self.parse(*args)
to_parse = args.pop
new(*args).parse(to_parse)
end
# Takes an array of Thor::Argument objects.
#
def initialize(arguments=[])
@assigns, @non_assigned_required = {}, []
@switches = arguments
arguments.each do |argument|
if argument.default != nil
@assigns[argument.human_name] = argument.default
elsif argument.required?
@non_assigned_required << argument
end
end
end
def parse(args)
@pile = args.dup
@switches.each do |argument|
break unless peek
@non_assigned_required.delete(argument)
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
end
check_requirement!
@assigns
end
def remaining
@pile
end
private
def no_or_skip?(arg)
arg =~ /^--(no|skip)-([-\w]+)$/
$2
end
def last?
@pile.empty?
end
def peek
@pile.first
end
def shift
@pile.shift
end
def unshift(arg)
unless arg.kind_of?(Array)
@pile.unshift(arg)
else
@pile = arg + @pile
end
end
def current_is_value?
peek && peek.to_s !~ /^-/
end
# Runs through the argument array getting strings that contains ":" and
# mark it as a hash:
#
# [ "name:string", "age:integer" ]
#
# Becomes:
#
# { "name" => "string", "age" => "integer" }
#
def parse_hash(name)
return shift if peek.is_a?(Hash)
hash = {}
while current_is_value? && peek.include?(?:)
key, value = shift.split(':',2)
hash[key] = value
end
hash
end
# Runs through the argument array getting all strings until no string is
# found or a switch is found.
#
# ["a", "b", "c"]
#
# And returns it as an array:
#
# ["a", "b", "c"]
#
def parse_array(name)
return shift if peek.is_a?(Array)
array = []
while current_is_value?
array << shift
end
array
end
# Check if the peek is numeric format and return a Float or Integer.
# Otherwise raises an error.
#
def parse_numeric(name)
return shift if peek.is_a?(Numeric)
unless peek =~ NUMERIC && $& == peek
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
end
$&.index('.') ? shift.to_f : shift.to_i
end
# Parse string:
# for --string-arg, just return the current value in the pile
# for --no-string-arg, nil
#
def parse_string(name)
if no_or_skip?(name)
nil
else
value = shift
if @switches.is_a?(Hash) && switch = @switches[name]
if switch.enum && !switch.enum.include?(value)
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
end
end
value
end
end
# Raises an error if @non_assigned_required array is not empty.
#
def check_requirement!
unless @non_assigned_required.empty?
names = @non_assigned_required.map do |o|
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
end.join("', '")
class_name = self.class.name.split('::').last.downcase
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
end
end
end
end

View File

@@ -0,0 +1,121 @@
class Thor
class Option < Argument #:nodoc:
attr_reader :aliases, :group, :lazy_default, :hide
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
def initialize(name, options={})
options[:required] = false unless options.key?(:required)
super
@lazy_default = options[:lazy_default]
@group = options[:group].to_s.capitalize if options[:group]
@aliases = Array(options[:aliases])
@hide = options[:hide]
end
# This parse quick options given as method_options. It makes several
# assumptions, but you can be more specific using the option method.
#
# parse :foo => "bar"
# #=> Option foo with default value bar
#
# parse [:foo, :baz] => "bar"
# #=> Option foo with default value bar and alias :baz
#
# parse :foo => :required
# #=> Required option foo without default value
#
# parse :foo => 2
# #=> Option foo with default value 2 and type numeric
#
# parse :foo => :numeric
# #=> Option foo without default value and type numeric
#
# parse :foo => true
# #=> Option foo with default value true and type boolean
#
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
# is given a default type is assumed. This default type accepts arguments as
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
#
def self.parse(key, value)
if key.is_a?(Array)
name, *aliases = key
else
name, aliases = key, []
end
name = name.to_s
default = value
type = case value
when Symbol
default = nil
if VALID_TYPES.include?(value)
value
elsif required = (value == :required)
:string
end
when TrueClass, FalseClass
:boolean
when Numeric
:numeric
when Hash, Array, String
value.class.name.downcase.to_sym
end
self.new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
end
def switch_name
@switch_name ||= dasherized? ? name : dasherize(name)
end
def human_name
@human_name ||= dasherized? ? undasherize(name) : name
end
def usage(padding=0)
sample = if banner && !banner.to_s.empty?
"#{switch_name}=#{banner}"
else
switch_name
end
sample = "[#{sample}]" unless required?
if aliases.empty?
(" " * padding) << sample
else
"#{aliases.join(', ')}, #{sample}"
end
end
VALID_TYPES.each do |type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{type}?
self.type == #{type.inspect}
end
RUBY
end
protected
def validate!
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
end
def dasherized?
name.index('-') == 0
end
def undasherize(str)
str.sub(/^-{1,2}/, '')
end
def dasherize(str)
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
end
end
end

View File

@@ -0,0 +1,218 @@
class Thor
class Options < Arguments #:nodoc:
LONG_RE = /^(--\w+(?:-\w+)*)$/
SHORT_RE = /^(-[a-z])$/i
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
OPTS_END = '--'.freeze
# Receives a hash and makes it switches.
def self.to_switches(options)
options.map do |key, value|
case value
when true
"--#{key}"
when Array
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
when Hash
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
when nil, false
""
else
"--#{key} #{value.inspect}"
end
end.join(" ")
end
# Takes a hash of Thor::Option and a hash with defaults.
#
# If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
# an unknown option or a regular argument.
def initialize(hash_options={}, defaults={}, stop_on_unknown=false)
@stop_on_unknown = stop_on_unknown
options = hash_options.values
super(options)
# Add defaults
defaults.each do |key, value|
@assigns[key.to_s] = value
@non_assigned_required.delete(hash_options[key])
end
@shorts, @switches, @extra = {}, {}, []
options.each do |option|
@switches[option.switch_name] = option
option.aliases.each do |short|
name = short.to_s.sub(/^(?!\-)/, '-')
@shorts[name] ||= option.switch_name
end
end
end
def remaining
@extra
end
def peek
return super unless @parsing_options
result = super
if result == OPTS_END
shift
@parsing_options = false
super
else
result
end
end
def parse(args)
@pile = args.dup
@parsing_options = true
while peek
if parsing_options?
match, is_switch = current_is_switch?
shifted = shift
if is_switch
case shifted
when SHORT_SQ_RE
unshift($1.split('').map { |f| "-#{f}" })
next
when EQ_RE, SHORT_NUM
unshift($2)
switch = $1
when LONG_RE, SHORT_RE
switch = $1
end
switch = normalize_switch(switch)
option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option)
elsif @stop_on_unknown
@parsing_options = false
@extra << shifted
@extra << shift while peek
break
elsif match
@extra << shifted
@extra << shift while peek && peek !~ /^-/
else
@extra << shifted
end
else
@extra << shift
end
end
check_requirement!
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
assigns.freeze
assigns
end
def check_unknown!
# an unknown option starts with - or -- and has no more --'s afterward.
unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
end
protected
# Check if the current value in peek is a registered switch.
#
# Two booleans are returned. The first is true if the current value
# starts with a hyphen; the second is true if it is a registered switch.
def current_is_switch?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
[true, switch?($1)]
when SHORT_SQ_RE
[true, $1.split('').any? { |f| switch?("-#{f}") }]
else
[false, false]
end
end
def current_is_switch_formatted?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
true
else
false
end
end
def current_is_value?
peek && (!parsing_options? || super)
end
def switch?(arg)
switch_option(normalize_switch(arg))
end
def switch_option(arg)
if match = no_or_skip?(arg)
@switches[arg] || @switches["--#{match}"]
else
@switches[arg]
end
end
# Check if the given argument is actually a shortcut.
#
def normalize_switch(arg)
(@shorts[arg] || arg).tr('_', '-')
end
def parsing_options?
peek
@parsing_options
end
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
#
def parse_boolean(switch)
if current_is_value?
if ["true", "TRUE", "t", "T", true].include?(peek)
shift
true
elsif ["false", "FALSE", "f", "F", false].include?(peek)
shift
false
else
true
end
else
@switches.key?(switch) || !no_or_skip?(switch)
end
end
# Parse the value at the peek analyzing if it requires an input or not.
#
def parse_peek(switch, option)
if parsing_options? && (current_is_switch_formatted? || last?)
if option.boolean?
# No problem for boolean types
elsif no_or_skip?(switch)
return nil # User set value to nil
elsif option.string? && !option.required?
# Return the default if there is one, else the human name
return option.lazy_default || option.default || option.human_name
elsif option.lazy_default
return option.lazy_default
else
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
end
end
@non_assigned_required.delete(option)
send(:"parse_#{option.type}", switch)
end
end
end

View File

@@ -0,0 +1,72 @@
require 'rake'
require 'rake/dsl_definition'
class Thor
# Adds a compatibility layer to your Thor classes which allows you to use
# rake package tasks. For example, to use rspec rake tasks, one can do:
#
# require 'thor/rake_compat'
# require 'rspec/core/rake_task'
#
# class Default < Thor
# include Thor::RakeCompat
#
# RSpec::Core::RakeTask.new(:spec) do |t|
# t.spec_opts = ['--options', "./.rspec"]
# t.spec_files = FileList['spec/**/*_spec.rb']
# end
# end
#
module RakeCompat
include Rake::DSL if defined?(Rake::DSL)
def self.rake_classes
@rake_classes ||= []
end
def self.included(base)
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
Rake.application.instance_variable_set(:@rakefile, rakefile)
self.rake_classes << base
end
end
end
# override task on (main), for compatibility with Rake 0.9
self.instance_eval do
alias rake_namespace namespace
def task(*)
task = super
if klass = Thor::RakeCompat.rake_classes.last
non_namespaced_name = task.name.split(':').last
description = non_namespaced_name
description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
description.strip!
klass.desc description, Rake.application.last_description || non_namespaced_name
Rake.application.last_description = nil
klass.send :define_method, non_namespaced_name do |*args|
Rake::Task[task.name.to_sym].invoke(*args)
end
end
task
end
def namespace(name)
if klass = Thor::RakeCompat.rake_classes.last
const_name = Thor::Util.camel_case(name.to_s).to_sym
klass.const_set(const_name, Class.new(Thor))
new_klass = klass.const_get(const_name)
Thor::RakeCompat.rake_classes << new_klass
end
super
Thor::RakeCompat.rake_classes.pop
end
end

View File

@@ -0,0 +1,322 @@
require 'thor'
require 'thor/group'
require 'thor/core_ext/io_binary_read'
require 'fileutils'
require 'open-uri'
require 'yaml'
require 'digest/md5'
require 'pathname'
class Thor::Runner < Thor #:nodoc:
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
# Override Thor#help so it can give information about any class and any method.
#
def help(meth = nil)
if meth && !self.respond_to?(meth)
initialize_thorfiles(meth)
klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
self.class.handle_no_command_error(command, false) if klass.nil?
klass.start(["-h", command].compact, :shell => self.shell)
else
super
end
end
# If a command is not found on Thor::Runner, method missing is invoked and
# Thor::Runner is then responsible for finding the command in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
self.class.handle_no_command_error(command, false) if klass.nil?
args.unshift(command) if command
klass.start(args, :shell => self.shell)
end
desc "install NAME", "Install an optionally named Thor file into your system commands"
method_options :as => :string, :relative => :boolean, :force => :boolean
def install(name)
initialize_thorfiles
# If a directory name is provided as the argument, look for a 'main.thor'
# command in said directory.
begin
if File.directory?(File.expand_path(name))
base, package = File.join(name, "main.thor"), :directory
contents = open(base) {|input| input.read }
else
base, package = name, :file
contents = open(name) {|input| input.read }
end
rescue OpenURI::HTTPError
raise Error, "Error opening URI '#{name}'"
rescue Errno::ENOENT
raise Error, "Error opening file '#{name}'"
end
say "Your Thorfile contains:"
say contents
unless options["force"]
return false if no?("Do you wish to continue [y/N]?")
end
as = options["as"] || begin
first_line = contents.split("\n")[0]
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
end
unless as
basename = File.basename(name)
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
as = basename if as.empty?
end
location = if options[:relative] || name =~ /^https?:\/\//
name
else
File.expand_path(name)
end
thor_yaml[as] = {
:filename => Digest::MD5.hexdigest(name + as),
:location => location,
:namespaces => Thor::Util.namespaces_in_content(contents, base)
}
save_yaml(thor_yaml)
say "Storing thor file in your system repository"
destination = File.join(thor_root, thor_yaml[as][:filename])
if package == :file
File.open(destination, "w") { |f| f.puts contents }
else
FileUtils.cp_r(name, destination)
end
thor_yaml[as][:filename] # Indicate success
end
desc "version", "Show Thor version"
def version
require 'thor/version'
say "Thor #{Thor::VERSION}"
end
desc "uninstall NAME", "Uninstall a named Thor module"
def uninstall(name)
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
say "Uninstalling #{name}."
FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
thor_yaml.delete(name)
save_yaml(thor_yaml)
puts "Done."
end
desc "update NAME", "Update a Thor file from its original location"
def update(name)
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
old_filename = thor_yaml[name][:filename]
self.options = self.options.merge("as" => name)
if File.directory? File.expand_path(name)
FileUtils.rm_rf(File.join(thor_root, old_filename))
thor_yaml.delete(old_filename)
save_yaml(thor_yaml)
filename = install(name)
else
filename = install(thor_yaml[name][:location])
end
unless filename == old_filename
File.delete(File.join(thor_root, old_filename))
end
end
desc "installed", "List the installed Thor modules and commands"
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
def list(search="")
initialize_thorfiles
search = ".*#{search}" if options["substring"]
search = /^#{search}.*/i
group = options[:group] || "standard"
klasses = Thor::Base.subclasses.select do |k|
(options[:all] || k.group == group) && k.namespace =~ search
end
display_klasses(false, false, klasses)
end
private
def self.banner(command, all = false, subcommand = false)
"thor " + command.formatted_usage(self, all, subcommand)
end
def thor_root
Thor::Util.thor_root
end
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
yaml || {}
end
end
# Save the yaml file. If none exists in thor root, creates one.
#
def save_yaml(yaml)
yaml_file = File.join(thor_root, "thor.yml")
unless File.exists?(yaml_file)
FileUtils.mkdir_p(thor_root)
yaml_file = File.join(thor_root, "thor.yml")
FileUtils.touch(yaml_file)
end
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
end
def self.exit_on_failure?
true
end
# Load the Thorfiles. If relevant_to is supplied, looks for specific files
# in the thor_root instead of loading them all.
#
# By default, it also traverses the current path until find Thor files, as
# described in thorfiles. This look up can be skipped by suppliying
# skip_lookup true.
#
def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles(relevant_to, skip_lookup).each do |f|
Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
end
end
# Finds Thorfiles by traversing from your current directory down to the root
# directory of your system. If at any time we find a Thor file, we stop.
#
# We also ensure that system-wide Thorfiles are loaded first, so local
# Thorfiles can override them.
#
# ==== Example
#
# If we start at /Users/wycats/dev/thor ...
#
# 1. /Users/wycats/dev/thor
# 2. /Users/wycats/dev
# 3. /Users/wycats <-- we find a Thorfile here, so we stop
#
# Suppose we start at c:\Documents and Settings\james\dev\thor ...
#
# 1. c:\Documents and Settings\james\dev\thor
# 2. c:\Documents and Settings\james\dev
# 3. c:\Documents and Settings\james
# 4. c:\Documents and Settings
# 5. c:\ <-- no Thorfiles found!
#
def thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles = []
unless skip_lookup
Pathname.pwd.ascend do |path|
thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
break unless thorfiles.empty?
end
end
files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
files += thorfiles
files -= ["#{thor_root}/thor.yml"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Load Thorfiles relevant to the given method. If you provide "foo:bar" it
# will load all thor files in the thor.yaml that has "foo" e "foo:bar"
# namespaces registered.
#
def thorfiles_relevant_to(meth)
lookup = [ meth, meth.split(":")[0...-1].join(":") ]
files = thor_yaml.select do |k, v|
v[:namespaces] && !(v[:namespaces] & lookup).empty?
end
files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
end
# Display information about the given klasses. If with_module is given,
# it shows a table with information extracted from the yaml file.
#
def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
raise Error, "No Thor commands available" if klasses.empty?
show_modules if with_modules && !thor_yaml.empty?
list = Hash.new { |h,k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
# Get classes which inherit from Thor
(klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) }
# Get classes which inherit from Thor::Base
groups.map! { |k| k.printable_commands(false).first }
list["root"] = groups
# Order namespaces with default coming first
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
end
def display_commands(namespace, list) #:nodoc:
list.sort!{ |a,b| a[0] <=> b[0] }
say shell.set_color(namespace, :blue, true)
say "-" * namespace.size
print_table(list, :truncate => true)
say
end
alias display_tasks display_commands
def show_modules #:nodoc:
info = []
labels = ["Modules", "Namespaces"]
info << labels
info << [ "-" * labels[0].size, "-" * labels[1].size ]
thor_yaml.each do |name, hash|
info << [ name, hash[:namespaces].join(", ") ]
end
print_table info
say ""
end
end

View File

@@ -0,0 +1,88 @@
require 'rbconfig'
class Thor
module Base
# Returns the shell used in all Thor classes. If you are in a Unix platform
# it will use a colored log, otherwise it will use a basic one without color.
#
def self.shell
@shell ||= if ENV['THOR_SHELL'] && ENV['THOR_SHELL'].size > 0
Thor::Shell.const_get(ENV['THOR_SHELL'])
elsif ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) && !(ENV['ANSICON']))
Thor::Shell::Basic
else
Thor::Shell::Color
end
end
# Sets the shell used in all Thor classes.
#
def self.shell=(klass)
@shell = klass
end
end
module Shell
SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
autoload :Basic, 'thor/shell/basic'
autoload :Color, 'thor/shell/color'
autoload :HTML, 'thor/shell/html'
# Add shell to initialize config values.
#
# ==== Configuration
# shell<Object>:: An instance of the shell to be used.
#
# ==== Examples
#
# class MyScript < Thor
# argument :first, :type => :numeric
# end
#
# MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
#
def initialize(args=[], options={}, config={})
super
self.shell = config[:shell]
self.shell.base ||= self if self.shell.respond_to?(:base)
end
# Holds the shell for the given Thor instance. If no shell is given,
# it gets a default shell from Thor::Base.shell.
def shell
@shell ||= Thor::Base.shell.new
end
# Sets the shell for this thor class.
def shell=(shell)
@shell = shell
end
# Common methods that are delegated to the shell.
SHELL_DELEGATED_METHODS.each do |method|
module_eval <<-METHOD, __FILE__, __LINE__
def #{method}(*args,&block)
shell.#{method}(*args,&block)
end
METHOD
end
# Yields the given block with padding.
def with_padding
shell.padding += 1
yield
ensure
shell.padding -= 1
end
protected
# Allow shell to be shared between invocations.
#
def _shared_configuration #:nodoc:
super.merge!(:shell => self.shell)
end
end
end

View File

@@ -0,0 +1,393 @@
require 'tempfile'
class Thor
module Shell
class Basic
attr_accessor :base
attr_reader :padding
# Initialize base, mute and padding to nil.
#
def initialize #:nodoc:
@base, @mute, @padding = nil, false, 0
end
# Mute everything that's inside given block
#
def mute
@mute = true
yield
ensure
@mute = false
end
# Check if base is muted
#
def mute?
@mute
end
# Sets the output padding, not allowing less than zero values.
#
def padding=(value)
@padding = [0, value].max
end
# Asks something to the user and receives a response.
#
# If asked to limit the correct responses, you can pass in an
# array of acceptable answers. If one of those is not supplied,
# they will be shown a message stating that one of those answers
# must be given and re-asked the question.
#
# ==== Example
# ask("What is your name?")
#
# ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
#
def ask(statement, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
end
# Say (print) something to the user. If the sentence ends with a whitespace
# or tab character, a new line is not appended (print + flush). Otherwise
# are passed straight to puts (behavior got from Highline).
#
# ==== Example
# say("I know you knew that.")
#
def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)\Z/))
message = message.to_s
message = set_color(message, *color) if color && can_display_colors?
spaces = " " * padding
if force_new_line
stdout.puts(spaces + message)
else
stdout.print(spaces + message)
end
stdout.flush
end
# Say a status with the given color and appends the message. Since this
# method is used frequently by actions, it allows nil or false to be given
# in log_status, avoiding the message from being shown. If a Symbol is
# given in log_status, it's used as the color.
#
def say_status(status, message, log_status=true)
return if quiet? || log_status == false
spaces = " " * (padding + 1)
color = log_status.is_a?(Symbol) ? log_status : :green
status = status.to_s.rjust(12)
status = set_color status, color, true if color
stdout.puts "#{status}#{spaces}#{message}"
stdout.flush
end
# Make a question the to user and returns true if the user replies "y" or
# "yes".
#
def yes?(statement, color=nil)
!!(ask(statement, color) =~ is?(:yes))
end
# Make a question the to user and returns true if the user replies "n" or
# "no".
#
def no?(statement, color=nil)
!yes?(statement, color)
end
# Prints values in columns
#
# ==== Parameters
# Array[String, String, ...]
#
def print_in_columns(array)
return if array.empty?
colwidth = (array.map{|el| el.to_s.size}.max || 0) + 2
array.each_with_index do |value, index|
# Don't output trailing spaces when printing the last column
if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
stdout.puts value
else
stdout.printf("%-#{colwidth}s", value)
end
end
end
# Prints a table.
#
# ==== Parameters
# Array[Array[String, String, ...]]
#
# ==== Options
# indent<Integer>:: Indent the first column by indent value.
# colwidth<Integer>:: Force the first column to colwidth spaces wide.
#
def print_table(array, options={})
return if array.empty?
formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
options[:truncate] = terminal_width if options[:truncate] == true
formats << "%-#{colwidth + 2}s" if colwidth
start = colwidth ? 1 : 0
colcount = array.max{|a,b| a.size <=> b.size }.size
maximas = []
start.upto(colcount - 1) do |index|
maxima = array.map {|row| row[index] ? row[index].to_s.size : 0 }.max
maximas << maxima
if index == colcount - 1
# Don't output 2 trailing spaces when printing the last column
formats << "%-s"
else
formats << "%-#{maxima + 2}s"
end
end
formats[0] = formats[0].insert(0, " " * indent)
formats << "%s"
array.each do |row|
sentence = ""
row.each_with_index do |column, index|
maxima = maximas[index]
if column.is_a?(Numeric)
if index == row.size - 1
# Don't output 2 trailing spaces when printing the last column
f = "%#{maxima}s"
else
f = "%#{maxima}s "
end
else
f = formats[index]
end
sentence << f % column.to_s
end
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
stdout.puts sentence
end
end
# Prints a long string, word-wrapping the text to the current width of the
# terminal display. Ideal for printing heredocs.
#
# ==== Parameters
# String
#
# ==== Options
# indent<Integer>:: Indent each line of the printed paragraph by indent value.
#
def print_wrapped(message, options={})
indent = options[:indent] || 0
width = terminal_width - indent
paras = message.split("\n\n")
paras.map! do |unwrapped|
unwrapped.strip.gsub(/\n/, " ").squeeze(" ").
gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
end
paras.each do |para|
para.split("\n").each do |line|
stdout.puts line.insert(0, " " * indent)
end
stdout.puts unless para == paras.last
end
end
# Deals with file collision and returns true if the file should be
# overwritten and false otherwise. If a block is given, it uses the block
# response as the content for the diff.
#
# ==== Parameters
# destination<String>:: the destination file to solve conflicts
# block<Proc>:: an optional block that returns the value to be used in diff
#
def file_collision(destination)
return true if @always_force
options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
while true
answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
case answer
when is?(:yes), is?(:force), ""
return true
when is?(:no), is?(:skip)
return false
when is?(:always)
return @always_force = true
when is?(:quit)
say 'Aborting...'
raise SystemExit
when is?(:diff)
show_diff(destination, yield) if block_given?
say 'Retrying...'
else
say file_collision_help
end
end
end
# This code was copied from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
def terminal_width
if ENV['THOR_COLUMNS']
result = ENV['THOR_COLUMNS'].to_i
else
result = unix? ? dynamic_width : 80
end
(result < 10) ? 80 : result
rescue
80
end
# Called if something goes wrong during the execution. This is used by Thor
# internally and should not be used inside your scripts. If something went
# wrong, you can always raise an exception. If you raise a Thor::Error, it
# will be rescued and wrapped in the method below.
#
def error(statement)
stderr.puts statement
end
# Apply color to the given string with optional bold. Disabled in the
# Thor::Shell::Basic class.
#
def set_color(string, *args) #:nodoc:
string
end
protected
def can_display_colors?
false
end
def lookup_color(color)
return color unless color.is_a?(Symbol)
self.class.const_get(color.to_s.upcase)
end
def stdout
$stdout
end
def stdin
$stdin
end
def stderr
$stderr
end
def is?(value) #:nodoc:
value = value.to_s
if value.size == 1
/\A#{value}\z/i
else
/\A(#{value}|#{value[0,1]})\z/i
end
end
def file_collision_help #:nodoc:
<<HELP
Y - yes, overwrite
n - no, do not overwrite
a - all, overwrite this and all others
q - quit, abort
d - diff, show the differences between the old and the new
h - help, show this help
HELP
end
def show_diff(destination, content) #:nodoc:
diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
temp.write content
temp.rewind
system %(#{diff_cmd} "#{destination}" "#{temp.path}")
end
end
def quiet? #:nodoc:
mute? || (base && base.options[:quiet])
end
# Calculate the dynamic width of the terminal
def dynamic_width
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
end
def dynamic_width_stty
%x{stty size 2>/dev/null}.split[1].to_i
end
def dynamic_width_tput
%x{tput cols 2>/dev/null}.to_i
end
def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
end
def truncate(string, width)
as_unicode do
chars = string.chars.to_a
if chars.length <= width
chars.join
else
( chars[0, width-3].join ) + "..."
end
end
end
if "".respond_to?(:encode)
def as_unicode
yield
end
else
def as_unicode
old, $KCODE = $KCODE, "U"
yield
ensure
$KCODE = old
end
end
def ask_simply(statement, color=nil)
say("#{statement} ", color)
stdin.gets.tap{|text| text.strip! if text}
end
def ask_filtered(statement, answer_set, *args)
correct_answer = nil
until correct_answer
answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
correct_answer = answer_set.include?(answer) ? answer : nil
answers = answer_set.map(&:inspect).join(", ")
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
end
correct_answer
end
end
end
end

View File

@@ -0,0 +1,148 @@
require 'thor/shell/basic'
class Thor
module Shell
# Inherit from Thor::Shell::Basic and add set_color behavior. Check
# Thor::Shell::Basic to see all available methods.
#
class Color < Basic
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
# The start of an ANSI bold sequence.
BOLD = "\e[1m"
# Set the terminal's foreground ANSI color to black.
BLACK = "\e[30m"
# Set the terminal's foreground ANSI color to red.
RED = "\e[31m"
# Set the terminal's foreground ANSI color to green.
GREEN = "\e[32m"
# Set the terminal's foreground ANSI color to yellow.
YELLOW = "\e[33m"
# Set the terminal's foreground ANSI color to blue.
BLUE = "\e[34m"
# Set the terminal's foreground ANSI color to magenta.
MAGENTA = "\e[35m"
# Set the terminal's foreground ANSI color to cyan.
CYAN = "\e[36m"
# Set the terminal's foreground ANSI color to white.
WHITE = "\e[37m"
# Set the terminal's background ANSI color to black.
ON_BLACK = "\e[40m"
# Set the terminal's background ANSI color to red.
ON_RED = "\e[41m"
# Set the terminal's background ANSI color to green.
ON_GREEN = "\e[42m"
# Set the terminal's background ANSI color to yellow.
ON_YELLOW = "\e[43m"
# Set the terminal's background ANSI color to blue.
ON_BLUE = "\e[44m"
# Set the terminal's background ANSI color to magenta.
ON_MAGENTA = "\e[45m"
# Set the terminal's background ANSI color to cyan.
ON_CYAN = "\e[46m"
# Set the terminal's background ANSI color to white.
ON_WHITE = "\e[47m"
# Set color by using a string or one of the defined constants. If a third
# option is set to true, it also adds bold to the string. This is based
# on Highline implementation and it automatically appends CLEAR to the end
# of the returned String.
#
# Pass foreground, background and bold options to this method as
# symbols.
#
# Example:
#
# set_color "Hi!", :red, :on_white, :bold
#
# The available colors are:
#
# :bold
# :black
# :red
# :green
# :yellow
# :blue
# :magenta
# :cyan
# :white
# :on_black
# :on_red
# :on_green
# :on_yellow
# :on_blue
# :on_magenta
# :on_cyan
# :on_white
def set_color(string, *colors)
if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
ansi_colors = colors.map { |color| lookup_color(color) }
"#{ansi_colors.join}#{string}#{CLEAR}"
else
# The old API was `set_color(color, bold=boolean)`. We
# continue to support the old API because you should never
# break old APIs unnecessarily :P
foreground, bold = colors
foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol)
bold = bold ? BOLD : ""
"#{bold}#{foreground}#{string}#{CLEAR}"
end
end
protected
def can_display_colors?
stdout.tty?
end
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
output_diff_line(diff)
end
else
super
end
end
def output_diff_line(diff) #:nodoc:
case diff.action
when '-'
say "- #{diff.old_element.chomp}", :red, true
when '+'
say "+ #{diff.new_element.chomp}", :green, true
when '!'
say "- #{diff.old_element.chomp}", :red, true
say "+ #{diff.new_element.chomp}", :green, true
else
say " #{diff.old_element.chomp}", nil, true
end
end
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
@diff_lcs_loaded = begin
require 'diff/lcs'
true
rescue LoadError
false
end
end
end
end
end

View File

@@ -0,0 +1,127 @@
require 'thor/shell/basic'
class Thor
module Shell
# Inherit from Thor::Shell::Basic and add set_color behavior. Check
# Thor::Shell::Basic to see all available methods.
#
class HTML < Basic
# The start of an HTML bold sequence.
BOLD = "font-weight: bold"
# Set the terminal's foreground HTML color to black.
BLACK = 'color: black'
# Set the terminal's foreground HTML color to red.
RED = 'color: red'
# Set the terminal's foreground HTML color to green.
GREEN = 'color: green'
# Set the terminal's foreground HTML color to yellow.
YELLOW = 'color: yellow'
# Set the terminal's foreground HTML color to blue.
BLUE = 'color: blue'
# Set the terminal's foreground HTML color to magenta.
MAGENTA = 'color: magenta'
# Set the terminal's foreground HTML color to cyan.
CYAN = 'color: cyan'
# Set the terminal's foreground HTML color to white.
WHITE = 'color: white'
# Set the terminal's background HTML color to black.
ON_BLACK = 'background-color: black'
# Set the terminal's background HTML color to red.
ON_RED = 'background-color: red'
# Set the terminal's background HTML color to green.
ON_GREEN = 'background-color: green'
# Set the terminal's background HTML color to yellow.
ON_YELLOW = 'background-color: yellow'
# Set the terminal's background HTML color to blue.
ON_BLUE = 'background-color: blue'
# Set the terminal's background HTML color to magenta.
ON_MAGENTA = 'background-color: magenta'
# Set the terminal's background HTML color to cyan.
ON_CYAN = 'background-color: cyan'
# Set the terminal's background HTML color to white.
ON_WHITE = 'background-color: white'
# Set color by using a string or one of the defined constants. If a third
# option is set to true, it also adds bold to the string. This is based
# on Highline implementation and it automatically appends CLEAR to the end
# of the returned String.
#
def set_color(string, *colors)
if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
html_colors = colors.map { |color| lookup_color(color) }
"<span style=\"#{html_colors.join("; ")};\">#{string}</span>"
else
color, bold = colors
html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
styles = [html_color]
styles << BOLD if bold
"<span style=\"#{styles.join("; ")};\">#{string}</span>"
end
end
# Ask something to the user and receives a response.
#
# ==== Example
# ask("What is your name?")
#
# TODO: Implement #ask for Thor::Shell::HTML
def ask(statement, color=nil)
raise NotImplementedError, "Implement #ask for Thor::Shell::HTML"
end
protected
def can_display_colors?
true
end
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
output_diff_line(diff)
end
else
super
end
end
def output_diff_line(diff) #:nodoc:
case diff.action
when '-'
say "- #{diff.old_element.chomp}", :red, true
when '+'
say "+ #{diff.new_element.chomp}", :green, true
when '!'
say "- #{diff.old_element.chomp}", :red, true
say "+ #{diff.new_element.chomp}", :green, true
else
say " #{diff.old_element.chomp}", nil, true
end
end
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
@diff_lcs_loaded = begin
require 'diff/lcs'
true
rescue LoadError
false
end
end
end
end
end

270
vendor/gems/thor-0.18.1/lib/thor/util.rb vendored Normal file
View File

@@ -0,0 +1,270 @@
require 'rbconfig'
class Thor
module Sandbox #:nodoc:
end
# This module holds several utilities:
#
# 1) Methods to convert thor namespaces to constants and vice-versa.
#
# Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
#
# 2) Loading thor files and sandboxing:
#
# Thor::Util.load_thorfile("~/.thor/foo")
#
module Util
class << self
# Receives a namespace and search for it in the Thor::Base subclasses.
#
# ==== Parameters
# namespace<String>:: The namespace to search for.
#
def find_by_namespace(namespace)
namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
end
# Receives a constant and converts it to a Thor namespace. Since Thor
# commands can be added to a sandbox, this method is also responsable for
# removing the sandbox namespace.
#
# This method should not be used in general because it's used to deal with
# older versions of Thor. On current versions, if you need to get the
# namespace from a class, just call namespace on it.
#
# ==== Parameters
# constant<Object>:: The constant to be converted to the thor path.
#
# ==== Returns
# String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
#
def namespace_from_thor_class(constant)
constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
constant = snake_case(constant).squeeze(":")
constant
end
# Given the contents, evaluate it inside the sandbox and returns the
# namespaces defined in the sandbox.
#
# ==== Parameters
# contents<String>
#
# ==== Returns
# Array[Object]
#
def namespaces_in_content(contents, file=__FILE__)
old_constants = Thor::Base.subclasses.dup
Thor::Base.subclasses.clear
load_thorfile(file, contents)
new_constants = Thor::Base.subclasses.dup
Thor::Base.subclasses.replace(old_constants)
new_constants.map!{ |c| c.namespace }
new_constants.compact!
new_constants
end
# Returns the thor classes declared inside the given class.
#
def thor_classes_in(klass)
stringfied_constants = klass.constants.map { |c| c.to_s }
Thor::Base.subclasses.select do |subclass|
next unless subclass.name
stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
end
end
# Receives a string and convert it to snake case. SnakeCase returns snake_case.
#
# ==== Parameters
# String
#
# ==== Returns
# String
#
def snake_case(str)
return str.downcase if str =~ /^[A-Z_]+$/
str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
return $+.downcase
end
# Receives a string and convert it to camel case. camel_case returns CamelCase.
#
# ==== Parameters
# String
#
# ==== Returns
# String
#
def camel_case(str)
return str if str !~ /_/ && str =~ /[A-Z]+.*/
str.split('_').map { |i| i.capitalize }.join
end
# Receives a namespace and tries to retrieve a Thor or Thor::Group class
# from it. It first searches for a class using the all the given namespace,
# if it's not found, removes the highest entry and searches for the class
# again. If found, returns the highest entry as the class name.
#
# ==== Examples
#
# class Foo::Bar < Thor
# def baz
# end
# end
#
# class Baz::Foo < Thor::Group
# end
#
# Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command
# Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
# Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
#
# ==== Parameters
# namespace<String>
#
def find_class_and_command_by_namespace(namespace, fallback = true)
if namespace.include?(?:) # look for a namespaced command
pieces = namespace.split(":")
command = pieces.pop
klass = Thor::Util.find_by_namespace(pieces.join(":"))
end
unless klass # look for a Thor::Group with the right name
klass, command = Thor::Util.find_by_namespace(namespace), nil
end
if !klass && fallback # try a command in the default namespace
command = namespace
klass = Thor::Util.find_by_namespace('')
end
return klass, command
end
alias find_class_and_task_by_namespace find_class_and_command_by_namespace
# Receives a path and load the thor file in the path. The file is evaluated
# inside the sandbox to avoid namespacing conflicts.
#
def load_thorfile(path, content=nil, debug=false)
content ||= File.binread(path)
begin
Thor::Sandbox.class_eval(content, path)
rescue Exception => e
$stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
if debug
$stderr.puts(*e.backtrace)
else
$stderr.puts(e.backtrace.first)
end
end
end
def user_home
@@user_home ||= if ENV["HOME"]
ENV["HOME"]
elsif ENV["USERPROFILE"]
ENV["USERPROFILE"]
elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
elsif ENV["APPDATA"]
ENV["APPDATA"]
else
begin
File.expand_path("~")
rescue
if File::ALT_SEPARATOR
"C:/"
else
"/"
end
end
end
end
# Returns the root where thor files are located, depending on the OS.
#
def thor_root
File.join(user_home, ".thor").gsub(/\\/, '/')
end
# Returns the files in the thor root. On Windows thor_root will be something
# like this:
#
# C:\Documents and Settings\james\.thor
#
# If we don't #gsub the \ character, Dir.glob will fail.
#
def thor_root_glob
files = Dir["#{escape_globs(thor_root)}/*"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Where to look for Thor files.
#
def globs_for(path)
path = escape_globs(path)
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
end
# Return the path to the ruby interpreter taking into account multiple
# installations and windows extensions.
#
def ruby_command
@ruby_command ||= begin
ruby_name = RbConfig::CONFIG['ruby_install_name']
ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name)
ruby << RbConfig::CONFIG['EXEEXT']
# avoid using different name than ruby (on platforms supporting links)
if ruby_name != 'ruby' && File.respond_to?(:readlink)
begin
alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
alternate_ruby << RbConfig::CONFIG['EXEEXT']
# ruby is a symlink
if File.symlink? alternate_ruby
linked_ruby = File.readlink alternate_ruby
# symlink points to 'ruby_install_name'
ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
end
rescue NotImplementedError
# just ignore on windows
end
end
# escape string in case path to ruby executable contain spaces.
ruby.sub!(/.*\s.*/m, '"\&"')
ruby
end
end
# Returns a string that has had any glob characters escaped.
# The glob characters are `* ? { } [ ]`.
#
# ==== Examples
#
# Thor::Util.escape_globs('[apps]') # => '\[apps\]'
#
# ==== Parameters
# String
#
# ==== Returns
# String
#
def escape_globs(path)
path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
end
end
end
end

View File

@@ -0,0 +1,3 @@
class Thor
VERSION = "0.18.1"
end

View File

@@ -0,0 +1,170 @@
require 'helper'
require 'thor/actions'
describe Thor::Actions::CreateFile do
before do
::FileUtils.rm_rf(destination_root)
end
def create_file(destination=nil, config={}, options={})
@base = MyCounter.new([1, 2], options, { :destination_root => destination_root })
@base.stub!(:file_name).and_return('rdoc')
@action = Thor::Actions::CreateFile.new(@base, destination, "CONFIGURATION",
{ :verbose => !@silence }.merge(config))
end
def invoke!
capture(:stdout) { @action.invoke! }
end
def revoke!
capture(:stdout) { @action.revoke! }
end
def silence!
@silence = true
end
describe "#invoke!" do
it "creates a file" do
create_file("doc/config.rb")
invoke!
expect(File.exists?(File.join(destination_root, "doc/config.rb"))).to be_true
end
it "does not create a file if pretending" do
create_file("doc/config.rb", {}, :pretend => true)
invoke!
expect(File.exists?(File.join(destination_root, "doc/config.rb"))).to be_false
end
it "shows created status to the user" do
create_file("doc/config.rb")
expect(invoke!).to eq(" create doc/config.rb\n")
end
it "does not show any information if log status is false" do
silence!
create_file("doc/config.rb")
expect(invoke!).to be_empty
end
it "returns the given destination" do
capture(:stdout) do
expect(create_file("doc/config.rb").invoke!).to eq("doc/config.rb")
end
end
it "converts encoded instructions" do
create_file("doc/%file_name%.rb.tt")
invoke!
expect(File.exists?(File.join(destination_root, "doc/rdoc.rb.tt"))).to be_true
end
describe "when file exists" do
before do
create_file("doc/config.rb")
invoke!
end
describe "and is identical" do
it "shows identical status" do
create_file("doc/config.rb")
invoke!
expect(invoke!).to eq(" identical doc/config.rb\n")
end
end
describe "and is not identical" do
before do
File.open(File.join(destination_root, 'doc/config.rb'), 'w'){ |f| f.write("FOO = 3") }
end
it "shows forced status to the user if force is given" do
expect(create_file("doc/config.rb", {}, :force => true)).not_to be_identical
expect(invoke!).to eq(" force doc/config.rb\n")
end
it "shows skipped status to the user if skip is given" do
expect(create_file("doc/config.rb", {}, :skip => true)).not_to be_identical
expect(invoke!).to eq(" skip doc/config.rb\n")
end
it "shows forced status to the user if force is configured" do
expect(create_file("doc/config.rb", :force => true)).not_to be_identical
expect(invoke!).to eq(" force doc/config.rb\n")
end
it "shows skipped status to the user if skip is configured" do
expect(create_file("doc/config.rb", :skip => true)).not_to be_identical
expect(invoke!).to eq(" skip doc/config.rb\n")
end
it "shows conflict status to ther user" do
expect(create_file("doc/config.rb")).not_to be_identical
$stdin.should_receive(:gets).and_return('s')
file = File.join(destination_root, 'doc/config.rb')
content = invoke!
expect(content).to match(/conflict doc\/config\.rb/)
expect(content).to match(/Overwrite #{file}\? \(enter "h" for help\) \[Ynaqdh\]/)
expect(content).to match(/skip doc\/config\.rb/)
end
it "creates the file if the file collision menu returns true" do
create_file("doc/config.rb")
$stdin.should_receive(:gets).and_return('y')
expect(invoke!).to match(/force doc\/config\.rb/)
end
it "skips the file if the file collision menu returns false" do
create_file("doc/config.rb")
$stdin.should_receive(:gets).and_return('n')
expect(invoke!).to match(/skip doc\/config\.rb/)
end
it "executes the block given to show file content" do
create_file("doc/config.rb")
$stdin.should_receive(:gets).and_return('d')
$stdin.should_receive(:gets).and_return('n')
@base.shell.should_receive(:system).with(/diff -u/)
invoke!
end
end
end
end
describe "#revoke!" do
it "removes the destination file" do
create_file("doc/config.rb")
invoke!
revoke!
expect(File.exists?(@action.destination)).to be_false
end
it "does not raise an error if the file does not exist" do
create_file("doc/config.rb")
revoke!
expect(File.exists?(@action.destination)).to be_false
end
end
describe "#exists?" do
it "returns true if the destination file exists" do
create_file("doc/config.rb")
expect(@action.exists?).to be_false
invoke!
expect(@action.exists?).to be_true
end
end
describe "#identical?" do
it "returns true if the destination file and is identical" do
create_file("doc/config.rb")
expect(@action.identical?).to be_false
invoke!
expect(@action.identical?).to be_true
end
end
end

View File

@@ -0,0 +1,95 @@
require 'helper'
require 'thor/actions'
require 'tempfile'
describe Thor::Actions::CreateLink do
before do
@hardlink_to = File.join(Dir.tmpdir, 'linkdest.rb')
::FileUtils.rm_rf(destination_root)
::FileUtils.rm_rf(@hardlink_to)
end
def create_link(destination=nil, config={}, options={})
@base = MyCounter.new([1,2], options, { :destination_root => destination_root })
@base.stub!(:file_name).and_return('rdoc')
@tempfile = Tempfile.new("config.rb")
@action = Thor::Actions::CreateLink.new(@base, destination, @tempfile.path,
{ :verbose => !@silence }.merge(config))
end
def invoke!
capture(:stdout) { @action.invoke! }
end
def revoke!
capture(:stdout) { @action.revoke! }
end
def silence!
@silence = true
end
describe "#invoke!" do
it "creates a symbolic link for :symbolic => true" do
create_link("doc/config.rb", :symbolic => true)
invoke!
destination_path = File.join(destination_root, "doc/config.rb")
expect(File.exists?(destination_path)).to be_true
expect(File.symlink?(destination_path)).to be_true
end
it "creates a hard link for :symbolic => false" do
create_link(@hardlink_to, :symbolic => false)
invoke!
destination_path = @hardlink_to
expect(File.exists?(destination_path)).to be_true
expect(File.symlink?(destination_path)).to be_false
end
it "creates a symbolic link by default" do
create_link("doc/config.rb")
invoke!
destination_path = File.join(destination_root, "doc/config.rb")
expect(File.exists?(destination_path)).to be_true
expect(File.symlink?(destination_path)).to be_true
end
it "does not create a link if pretending" do
create_link("doc/config.rb", {}, :pretend => true)
invoke!
expect(File.exists?(File.join(destination_root, "doc/config.rb"))).to be_false
end
it "shows created status to the user" do
create_link("doc/config.rb")
expect(invoke!).to eq(" create doc/config.rb\n")
end
it "does not show any information if log status is false" do
silence!
create_link("doc/config.rb")
expect(invoke!).to be_empty
end
end
describe "#identical?" do
it "returns true if the destination link exists and is identical" do
create_link("doc/config.rb")
expect(@action.identical?).to be_false
invoke!
expect(@action.identical?).to be_true
end
end
describe "#revoke!" do
it "removes the symbolic link of non-existent destination" do
create_link("doc/config.rb")
invoke!
File.delete(@tempfile.path)
revoke!
expect(File.symlink?(@action.destination)).to be_false
end
end
end

View File

@@ -0,0 +1,169 @@
require 'helper'
require 'thor/actions'
describe Thor::Actions::Directory do
before do
::FileUtils.rm_rf(destination_root)
invoker.stub!(:file_name).and_return("rdoc")
end
def invoker
@invoker ||= WhinyGenerator.new([1,2], {}, { :destination_root => destination_root })
end
def revoker
@revoker ||= WhinyGenerator.new([1,2], {}, { :destination_root => destination_root, :behavior => :revoke })
end
def invoke!(*args, &block)
capture(:stdout){ invoker.directory(*args, &block) }
end
def revoke!(*args, &block)
capture(:stdout){ revoker.directory(*args, &block) }
end
def exists_and_identical?(source_path, destination_path)
%w(config.rb README).each do |file|
source = File.join(source_root, source_path, file)
destination = File.join(destination_root, destination_path, file)
expect(File.exists?(destination)).to be_true
expect(FileUtils.identical?(source, destination)).to be_true
end
end
describe "#invoke!" do
it "raises an error if the source does not exist" do
expect {
invoke! "unknown"
}.to raise_error(Thor::Error, /Could not find "unknown" in any of your source paths/)
end
it "does not create a directory in pretend mode" do
invoke! "doc", "ghost", :pretend => true
expect(File.exists?("ghost")).to be_false
end
it "copies the whole directory recursively to the default destination" do
invoke! "doc"
exists_and_identical?("doc", "doc")
end
it "copies the whole directory recursively to the specified destination" do
invoke! "doc", "docs"
exists_and_identical?("doc", "docs")
end
it "copies only the first level files if recursive" do
invoke! ".", "commands", :recursive => false
file = File.join(destination_root, "commands", "group.thor")
expect(File.exists?(file)).to be_true
file = File.join(destination_root, "commands", "doc")
expect(File.exists?(file)).to be_false
file = File.join(destination_root, "commands", "doc", "README")
expect(File.exists?(file)).to be_false
end
it "ignores files within excluding/ directories when exclude_pattern is provided" do
invoke! "doc", "docs", :exclude_pattern => /excluding\//
file = File.join(destination_root, "docs", "excluding", "rdoc.rb")
expect(File.exists?(file)).to be_false
end
it "copies and evalutes files within excluding/ directory when no exclude_pattern is present" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "excluding", "rdoc.rb")
expect(File.exists?(file)).to be_true
expect(File.read(file)).to eq("BAR = BAR\n")
end
it "copies files from the source relative to the current path" do
invoker.inside "doc" do
invoke! "."
end
exists_and_identical?("doc", "doc")
end
it "copies and evaluates templates" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "rdoc.rb")
expect(File.exists?(file)).to be_true
expect(File.read(file)).to eq("FOO = FOO\n")
end
it "copies directories and preserved file mode" do
invoke! "preserve", "preserved", :mode => :preserve
original = File.join(source_root, "preserve", "script.sh")
copy = File.join(destination_root, "preserved", "script.sh")
expect(File.stat(original).mode).to eq(File.stat(copy).mode)
end
it "copies directories" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "components")
expect(File.exists?(file)).to be_true
expect(File.directory?(file)).to be_true
end
it "does not copy .empty_directory files" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "components", ".empty_directory")
expect(File.exists?(file)).to be_false
end
it "copies directories even if they are empty" do
invoke! "doc/components", "docs/components"
file = File.join(destination_root, "docs", "components")
expect(File.exists?(file)).to be_true
end
it "does not copy empty directories twice" do
content = invoke!("doc/components", "docs/components")
expect(content).not_to match(/exist/)
end
it "logs status" do
content = invoke!("doc")
expect(content).to match(/create doc\/README/)
expect(content).to match(/create doc\/config\.rb/)
expect(content).to match(/create doc\/rdoc\.rb/)
expect(content).to match(/create doc\/components/)
end
it "yields a block" do
checked = false
invoke!("doc") do |content|
checked ||= !!(content =~ /FOO/)
end
expect(checked).to be_true
end
it "works with glob characters in the path" do
content = invoke!("app{1}")
expect(content).to match(/create app\{1\}\/README/)
end
end
describe "#revoke!" do
it "removes the destination file" do
invoke! "doc"
revoke! "doc"
expect(File.exists?(File.join(destination_root, "doc", "README"))).to be_false
expect(File.exists?(File.join(destination_root, "doc", "config.rb"))).to be_false
expect(File.exists?(File.join(destination_root, "doc", "components"))).to be_false
end
it "works with glob characters in the path" do
invoke! "app{1}"
expect(File.exists?(File.join(destination_root, "app{1}", "README"))).to be_true
revoke! "app{1}"
expect(File.exists?(File.join(destination_root, "app{1}", "README"))).to be_false
end
end
end

View File

@@ -0,0 +1,129 @@
require 'helper'
require 'thor/actions'
describe Thor::Actions::EmptyDirectory do
before do
::FileUtils.rm_rf(destination_root)
end
def empty_directory(destination, options={})
@action = Thor::Actions::EmptyDirectory.new(base, destination)
end
def invoke!
capture(:stdout) { @action.invoke! }
end
def revoke!
capture(:stdout) { @action.revoke! }
end
def base
@base ||= MyCounter.new([1,2], {}, { :destination_root => destination_root })
end
describe "#destination" do
it "returns the full destination with the destination_root" do
expect(empty_directory('doc').destination).to eq(File.join(destination_root, 'doc'))
end
it "takes relative root into account" do
base.inside('doc') do
expect(empty_directory('contents').destination).to eq(File.join(destination_root, 'doc', 'contents'))
end
end
end
describe "#relative_destination" do
it "returns the relative destination to the original destination root" do
base.inside('doc') do
expect(empty_directory('contents').relative_destination).to eq('doc/contents')
end
end
end
describe "#given_destination" do
it "returns the destination supplied by the user" do
base.inside('doc') do
expect(empty_directory('contents').given_destination).to eq('contents')
end
end
end
describe "#invoke!" do
it "copies the file to the specified destination" do
empty_directory("doc")
invoke!
expect(File.exists?(File.join(destination_root, "doc"))).to be_true
end
it "shows created status to the user" do
empty_directory("doc")
expect(invoke!).to eq(" create doc\n")
end
it "does not create a directory if pretending" do
base.inside("foo", :pretend => true) do
empty_directory("ghost")
end
expect(File.exists?(File.join(base.destination_root, "ghost"))).to be_false
end
describe "when directory exists" do
it "shows exist status" do
empty_directory("doc")
invoke!
expect(invoke!).to eq(" exist doc\n")
end
end
end
describe "#revoke!" do
it "removes the destination file" do
empty_directory("doc")
invoke!
revoke!
expect(File.exists?(@action.destination)).to be_false
end
end
describe "#exists?" do
it "returns true if the destination file exists" do
empty_directory("doc")
expect(@action.exists?).to be_false
invoke!
expect(@action.exists?).to be_true
end
end
context "protected methods" do
describe "#convert_encoded_instructions" do
before do
empty_directory("test_dir")
@action.base.stub!(:file_name).and_return("expected")
end
it "accepts and executes a 'legal' %\w+% encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%file_name%.txt")).to eq("expected.txt")
end
it "accepts and executes a private %\w+% encoded instruction" do
@action.base.extend Module.new {
private
def private_file_name
"expected"
end
}
expect(@action.send(:convert_encoded_instructions, "%private_file_name%.txt")).to eq("expected.txt")
end
it "ignores an 'illegal' %\w+% encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%some_name%.txt")).to eq("%some_name%.txt")
end
it "ignores incorrectly encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%some.name%.txt")).to eq("%some.name%.txt")
end
end
end
end

View File

@@ -0,0 +1,382 @@
require 'helper'
class Application; end
describe Thor::Actions do
def runner(options={})
@runner ||= MyCounter.new([1], options, { :destination_root => destination_root })
end
def action(*args, &block)
capture(:stdout) { runner.send(*args, &block) }
end
def exists_and_identical?(source, destination)
destination = File.join(destination_root, destination)
expect(File.exists?(destination)).to be_true
source = File.join(source_root, source)
expect(FileUtils).to be_identical(source, destination)
end
def file
File.join(destination_root, "foo")
end
before do
::FileUtils.rm_rf(destination_root)
end
describe "#chmod" do
it "executes the command given" do
FileUtils.should_receive(:chmod_R).with(0755, file)
action :chmod, "foo", 0755
end
it "does not execute the command if pretending given" do
FileUtils.should_not_receive(:chmod_R)
runner(:pretend => true)
action :chmod, "foo", 0755
end
it "logs status" do
FileUtils.should_receive(:chmod_R).with(0755, file)
expect(action(:chmod, "foo", 0755)).to eq(" chmod foo\n")
end
it "does not log status if required" do
FileUtils.should_receive(:chmod_R).with(0755, file)
expect(action(:chmod, "foo", 0755, :verbose => false)).to be_empty
end
end
describe "#copy_file" do
it "copies file from source to default destination" do
action :copy_file, "command.thor"
exists_and_identical?("command.thor", "command.thor")
end
it "copies file from source to the specified destination" do
action :copy_file, "command.thor", "foo.thor"
exists_and_identical?("command.thor", "foo.thor")
end
it "copies file from the source relative to the current path" do
runner.inside("doc") do
action :copy_file, "README"
end
exists_and_identical?("doc/README", "doc/README")
end
it "copies file from source to default destination and preserves file mode" do
action :copy_file, "preserve/script.sh", :mode => :preserve
original = File.join(source_root, "preserve/script.sh")
copy = File.join(destination_root, "preserve/script.sh")
expect(File.stat(original).mode).to eq(File.stat(copy).mode)
end
it "logs status" do
expect(action(:copy_file, "command.thor")).to eq(" create command.thor\n")
end
it "accepts a block to change output" do
action :copy_file, "command.thor" do |content|
"OMG" + content
end
expect(File.read(File.join(destination_root, "command.thor"))).to match(/^OMG/)
end
end
describe "#link_file" do
it "links file from source to default destination" do
action :link_file, "command.thor"
exists_and_identical?("command.thor", "command.thor")
end
it "links file from source to the specified destination" do
action :link_file, "command.thor", "foo.thor"
exists_and_identical?("command.thor", "foo.thor")
end
it "links file from the source relative to the current path" do
runner.inside("doc") do
action :link_file, "README"
end
exists_and_identical?("doc/README", "doc/README")
end
it "logs status" do
expect(action(:link_file, "command.thor")).to eq(" create command.thor\n")
end
end
describe "#get" do
it "copies file from source to the specified destination" do
action :get, "doc/README", "docs/README"
exists_and_identical?("doc/README", "docs/README")
end
it "uses just the source basename as destination if none is specified" do
action :get, "doc/README"
exists_and_identical?("doc/README", "README")
end
it "allows the destination to be set as a block result" do
action(:get, "doc/README"){ |c| "docs/README" }
exists_and_identical?("doc/README", "docs/README")
end
it "yields file content to a block" do
action :get, "doc/README" do |content|
expect(content).to eq("__start__\nREADME\n__end__\n")
end
end
it "logs status" do
expect(action(:get, "doc/README", "docs/README")).to eq(" create docs/README\n")
end
it "accepts http remote sources" do
body = "__start__\nHTTPFILE\n__end__\n"
FakeWeb.register_uri(:get, 'http://example.com/file.txt', :body => body)
action :get, "http://example.com/file.txt" do |content|
expect(content).to eq(body)
end
FakeWeb.clean_registry
end
it "accepts https remote sources" do
body = "__start__\nHTTPSFILE\n__end__\n"
FakeWeb.register_uri(:get, 'https://example.com/file.txt', :body => body)
action :get, "https://example.com/file.txt" do |content|
expect(content).to eq(body)
end
FakeWeb.clean_registry
end
end
describe "#template" do
it "allows using block helpers in the template" do
action :template, "doc/block_helper.rb"
file = File.join(destination_root, "doc/block_helper.rb")
expect(File.read(file)).to eq("Hello world!")
end
it "evaluates the template given as source" do
runner.instance_variable_set("@klass", "Config")
action :template, "doc/config.rb"
file = File.join(destination_root, "doc/config.rb")
expect(File.read(file)).to eq("class Config; end\n")
end
it "copies the template to the specified destination" do
action :template, "doc/config.rb", "doc/configuration.rb"
file = File.join(destination_root, "doc/configuration.rb")
expect(File.exists?(file)).to be_true
end
it "converts enconded instructions" do
runner.should_receive(:file_name).and_return("rdoc")
action :template, "doc/%file_name%.rb.tt"
file = File.join(destination_root, "doc/rdoc.rb")
expect(File.exists?(file)).to be_true
end
it "logs status" do
expect(capture(:stdout) { runner.template("doc/config.rb") }).to eq(" create doc/config.rb\n")
end
it "accepts a block to change output" do
action :template, "doc/config.rb" do |content|
"OMG" + content
end
expect(File.read(File.join(destination_root, "doc/config.rb"))).to match(/^OMG/)
end
it "guesses the destination name when given only a source" do
action :template, "doc/config.yaml.tt"
file = File.join(destination_root, "doc/config.yaml")
expect(File.exists?(file)).to be_true
end
end
describe "when changing existent files" do
before do
::FileUtils.cp_r(source_root, destination_root)
end
def file
File.join(destination_root, "doc", "README")
end
describe "#remove_file" do
it "removes the file given" do
action :remove_file, "doc/README"
expect(File.exists?(file)).to be_false
end
it "removes directories too" do
action :remove_dir, "doc"
expect(File.exists?(File.join(destination_root, "doc"))).to be_false
end
it "does not remove if pretending" do
runner(:pretend => true)
action :remove_file, "doc/README"
expect(File.exists?(file)).to be_true
end
it "logs status" do
expect(action(:remove_file, "doc/README")).to eq(" remove doc/README\n")
end
it "does not log status if required" do
expect(action(:remove_file, "doc/README", :verbose => false)).to be_empty
end
end
describe "#gsub_file" do
it "replaces the content in the file" do
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "does not replace if pretending" do
runner(:pretend => true)
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n")
end
it "accepts a block" do
action(:gsub_file, "doc/README", "__start__"){ |match| match.gsub('__', '').upcase }
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "logs status" do
expect(action(:gsub_file, "doc/README", "__start__", "START")).to eq(" gsub doc/README\n")
end
it "does not log status if required" do
expect(action(:gsub_file, file, "__", :verbose => false){ |match| match * 2 }).to be_empty
end
end
describe "#append_to_file" do
it "appends content to the file" do
action :append_to_file, "doc/README", "END\n"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n")
end
it "accepts a block" do
action(:append_to_file, "doc/README"){ "END\n" }
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n")
end
it "logs status" do
expect(action(:append_to_file, "doc/README", "END")).to eq(" append doc/README\n")
end
end
describe "#prepend_to_file" do
it "prepends content to the file" do
action :prepend_to_file, "doc/README", "START\n"
expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n")
end
it "accepts a block" do
action(:prepend_to_file, "doc/README"){ "START\n" }
expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n")
end
it "logs status" do
expect(action(:prepend_to_file, "doc/README", "START")).to eq(" prepend doc/README\n")
end
end
describe "#inject_into_class" do
def file
File.join(destination_root, "application.rb")
end
it "appends content to a class" do
action :inject_into_class, "application.rb", Application, " filter_parameters :password\n"
expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n")
end
it "accepts a block" do
action(:inject_into_class, "application.rb", Application){ " filter_parameters :password\n" }
expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n")
end
it "logs status" do
expect(action(:inject_into_class, "application.rb", Application, " filter_parameters :password\n")).to eq(" insert application.rb\n")
end
it "does not append if class name does not match" do
action :inject_into_class, "application.rb", "App", " filter_parameters :password\n"
expect(File.binread(file)).to eq("class Application < Base\nend\n")
end
end
end
describe "when adjusting comments" do
before do
::FileUtils.cp_r(source_root, destination_root)
end
def file
File.join(destination_root, "doc", "COMMENTER")
end
unmodified_comments_file = /__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/
describe "#uncomment_lines" do
it "uncomments all matching lines in the file" do
action :uncomment_lines, "doc/COMMENTER", "green"
expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\n#yellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
action :uncomment_lines, "doc/COMMENTER", "red"
expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\nyellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
end
it "correctly uncomments lines with hashes in them" do
action :uncomment_lines, "doc/COMMENTER", "ind#igo"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n ind#igo\n__end__/)
end
it "does not modify already uncommented lines in the file" do
action :uncomment_lines, "doc/COMMENTER", "orange"
action :uncomment_lines, "doc/COMMENTER", "purple"
expect(File.binread(file)).to match(unmodified_comments_file)
end
it "does not uncomment the wrong line when uncommenting lines preceded by blank commented line" do
action :uncomment_lines, "doc/COMMENTER", "yellow"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\nyellowblue\nyellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
end
end
describe "#comment_lines" do
it "comments lines which are not commented" do
action :comment_lines, "doc/COMMENTER", "orange"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n purple\n ind#igo\n # ind#igo\n__end__/)
action :comment_lines, "doc/COMMENTER", "purple"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n # purple\n ind#igo\n # ind#igo\n__end__/)
end
it "correctly comments lines with hashes in them" do
action :comment_lines, "doc/COMMENTER", "ind#igo"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n # ind#igo\n # ind#igo\n__end__/)
end
it "does not modify already commented lines" do
action :comment_lines, "doc/COMMENTER", "green"
expect(File.binread(file)).to match(unmodified_comments_file)
end
end
end
end

View File

@@ -0,0 +1,135 @@
require 'helper'
require 'thor/actions'
describe Thor::Actions::InjectIntoFile do
before do
::FileUtils.rm_rf(destination_root)
::FileUtils.cp_r(source_root, destination_root)
end
def invoker(options={})
@invoker ||= MyCounter.new([1,2], options, { :destination_root => destination_root })
end
def revoker
@revoker ||= MyCounter.new([1,2], {}, { :destination_root => destination_root, :behavior => :revoke })
end
def invoke!(*args, &block)
capture(:stdout) { invoker.insert_into_file(*args, &block) }
end
def revoke!(*args, &block)
capture(:stdout) { revoker.insert_into_file(*args, &block) }
end
def file
File.join(destination_root, "doc/README")
end
describe "#invoke!" do
it "changes the file adding content after the flag" do
invoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nmore content\nREADME\n__end__\n")
end
it "changes the file adding content before the flag" do
invoke! "doc/README", "more content\n", :before => "__end__"
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "accepts data as a block" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "logs status" do
expect(invoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" insert doc/README\n")
end
it "does not change the file if pretending" do
invoker :pretend => true
invoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "does not change the file if already include content" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "does change the file if already include content and :force == true" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
invoke! "doc/README", :before => "__end__", :force => true do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\nmore content\n__end__\n")
end
end
describe "#revoke!" do
it "substracts the destination file after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "substracts the destination file before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "substracts even with double after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
invoke! "doc/README", "\nanother stuff", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nanother stuff\nREADME\n__end__\n")
end
it "substracts even with double before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
invoke! "doc/README", "another stuff\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"
expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n")
end
it "substracts when prepending" do
invoke! "doc/README", "more content\n", :after => /\A/
invoke! "doc/README", "another stuff\n", :after => /\A/
revoke! "doc/README", "more content\n", :after => /\A/
expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n")
end
it "substracts when appending" do
invoke! "doc/README", "more content\n", :before => /\z/
invoke! "doc/README", "another stuff\n", :before => /\z/
revoke! "doc/README", "more content\n", :before => /\z/
expect(File.read(file)).to eq("__start__\nREADME\n__end__\nanother stuff\n")
end
it "shows progress information to the user" do
invoke!("doc/README", "\nmore content", :after => "__start__")
expect(revoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" subtract doc/README\n")
end
end
end

View File

@@ -0,0 +1,331 @@
require 'helper'
describe Thor::Actions do
def runner(options={})
@runner ||= MyCounter.new([1], options, { :destination_root => destination_root })
end
def action(*args, &block)
capture(:stdout) { runner.send(*args, &block) }
end
def file
File.join(destination_root, "foo")
end
describe "on include" do
it "adds runtime options to the base class" do
expect(MyCounter.class_options.keys).to include(:pretend)
expect(MyCounter.class_options.keys).to include(:force)
expect(MyCounter.class_options.keys).to include(:quiet)
expect(MyCounter.class_options.keys).to include(:skip)
end
end
describe "#initialize" do
it "has default behavior invoke" do
expect(runner.behavior).to eq(:invoke)
end
it "can have behavior revoke" do
expect(MyCounter.new([1], {}, :behavior => :revoke).behavior).to eq(:revoke)
end
it "when behavior is set to force, overwrite options" do
runner = MyCounter.new([1], { :force => false, :skip => true }, :behavior => :force)
expect(runner.behavior).to eq(:invoke)
expect(runner.options.force).to be_true
expect(runner.options.skip).not_to be_true
end
it "when behavior is set to skip, overwrite options" do
runner = MyCounter.new([1], ["--force"], :behavior => :skip)
expect(runner.behavior).to eq(:invoke)
expect(runner.options.force).not_to be_true
expect(runner.options.skip).to be_true
end
end
describe "accessors" do
describe "#destination_root=" do
it "gets the current directory and expands the path to set the root" do
base = MyCounter.new([1])
base.destination_root = "here"
expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), "..", "here")))
end
it "does not use the current directory if one is given" do
root = File.expand_path("/")
base = MyCounter.new([1])
base.destination_root = root
expect(base.destination_root).to eq(root)
end
it "uses the current directory if none is given" do
base = MyCounter.new([1])
expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), "..")))
end
end
describe "#relative_to_original_destination_root" do
it "returns the path relative to the absolute root" do
expect(runner.relative_to_original_destination_root(file)).to eq("foo")
end
it "does not remove dot if required" do
expect(runner.relative_to_original_destination_root(file, false)).to eq("./foo")
end
it "always use the absolute root" do
runner.inside("foo") do
expect(runner.relative_to_original_destination_root(file)).to eq("foo")
end
end
it "creates proper relative paths for absolute file location" do
expect(runner.relative_to_original_destination_root('/test/file')).to eq("/test/file")
end
it "does not fail with files constaining regexp characters" do
runner = MyCounter.new([1], {}, { :destination_root => File.join(destination_root, "fo[o-b]ar") })
expect(runner.relative_to_original_destination_root("bar")).to eq("bar")
end
describe "#source_paths_for_search" do
it "add source_root to source_paths_for_search" do
expect(MyCounter.source_paths_for_search).to include(File.expand_path("fixtures", File.dirname(__FILE__)))
end
it "keeps only current source root in source paths" do
expect(ClearCounter.source_paths_for_search).to include(File.expand_path("fixtures/bundle", File.dirname(__FILE__)))
expect(ClearCounter.source_paths_for_search).not_to include(File.expand_path("fixtures", File.dirname(__FILE__)))
end
it "customized source paths should be before source roots" do
expect(ClearCounter.source_paths_for_search[0]).to eq(File.expand_path("fixtures/doc", File.dirname(__FILE__)))
expect(ClearCounter.source_paths_for_search[1]).to eq(File.expand_path("fixtures/bundle", File.dirname(__FILE__)))
end
it "keeps inherited source paths at the end" do
expect(ClearCounter.source_paths_for_search.last).to eq(File.expand_path("fixtures/broken", File.dirname(__FILE__)))
end
end
end
describe "#find_in_source_paths" do
it "raises an error if source path is empty" do
expect {
A.new.find_in_source_paths("foo")
}.to raise_error(Thor::Error, /Currently you have no source paths/)
end
it "finds a template inside the source path" do
expect(runner.find_in_source_paths("doc")).to eq(File.expand_path("doc", source_root))
expect{ runner.find_in_source_paths("README") }.to raise_error
new_path = File.join(source_root, "doc")
runner.instance_variable_set(:@source_paths, nil)
runner.source_paths.unshift(new_path)
expect(runner.find_in_source_paths("README")).to eq(File.expand_path("README", new_path))
end
end
end
describe "#inside" do
it "executes the block inside the given folder" do
runner.inside("foo") do
expect(Dir.pwd).to eq(file)
end
end
it "changes the base root" do
runner.inside("foo") do
expect(runner.destination_root).to eq(file)
end
end
it "creates the directory if it does not exist" do
runner.inside("foo") do
expect(File.exists?(file)).to be_true
end
end
describe "when pretending" do
it "no directories should be created" do
runner.inside("bar", :pretend => true) {}
expect(File.exists?("bar")).to be_false
end
end
describe "when verbose" do
it "logs status" do
expect(capture(:stdout) {
runner.inside("foo", :verbose => true) {}
}).to match(/inside foo/)
end
it "uses padding in next status" do
expect(capture(:stdout) {
runner.inside("foo", :verbose => true) do
runner.say_status :cool, :padding
end
}).to match(/cool padding/)
end
it "removes padding after block" do
expect(capture(:stdout) {
runner.inside("foo", :verbose => true) {}
runner.say_status :no, :padding
}).to match(/no padding/)
end
end
end
describe "#in_root" do
it "executes the block in the root folder" do
runner.inside("foo") do
runner.in_root { expect(Dir.pwd).to eq(destination_root) }
end
end
it "changes the base root" do
runner.inside("foo") do
runner.in_root { expect(runner.destination_root).to eq(destination_root) }
end
end
it "returns to the previous state" do
runner.inside("foo") do
runner.in_root { }
expect(runner.destination_root).to eq(file)
end
end
end
describe "#apply" do
before do
@template = <<-TEMPLATE
@foo = "FOO"
say_status :cool, :padding
TEMPLATE
@template.stub(:read).and_return(@template)
@file = '/'
runner.stub(:open).and_return(@template)
end
it "accepts a URL as the path" do
@file = "http://gist.github.com/103208.txt"
runner.should_receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template)
action(:apply, @file)
end
it "accepts a secure URL as the path" do
@file = "https://gist.github.com/103208.txt"
runner.should_receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template)
action(:apply, @file)
end
it "accepts a local file path with spaces" do
@file = File.expand_path("fixtures/path with spaces", File.dirname(__FILE__))
runner.should_receive(:open).with(@file).and_return(@template)
action(:apply, @file)
end
it "opens a file and executes its content in the instance binding" do
action :apply, @file
expect(runner.instance_variable_get("@foo")).to eq("FOO")
end
it "applies padding to the content inside the file" do
expect(action(:apply, @file)).to match(/cool padding/)
end
it "logs its status" do
expect(action(:apply, @file)).to match(/ apply #{@file}\n/)
end
it "does not log status" do
content = action(:apply, @file, :verbose => false)
expect(content).to match(/cool padding/)
expect(content).not_to match(/apply http/)
end
end
describe "#run" do
before do
runner.should_receive(:system).with("ls")
end
it "executes the command given" do
action :run, "ls"
end
it "logs status" do
expect(action(:run, "ls")).to eq(" run ls from \".\"\n")
end
it "does not log status if required" do
expect(action(:run, "ls", :verbose => false)).to be_empty
end
it "accepts a color as status" do
runner.shell.should_receive(:say_status).with(:run, 'ls from "."', :yellow)
action :run, "ls", :verbose => :yellow
end
end
describe "#run_ruby_script" do
before do
Thor::Util.stub!(:ruby_command).and_return("/opt/jruby")
runner.should_receive(:system).with("/opt/jruby script.rb")
end
it "executes the ruby script" do
action :run_ruby_script, "script.rb"
end
it "logs status" do
expect(action(:run_ruby_script, "script.rb")).to eq(" run jruby script.rb from \".\"\n")
end
it "does not log status if required" do
expect(action(:run_ruby_script, "script.rb", :verbose => false)).to be_empty
end
end
describe "#thor" do
it "executes the thor command" do
runner.should_receive(:system).with("thor list")
action :thor, :list, :verbose => true
end
it "converts extra arguments to command arguments" do
runner.should_receive(:system).with("thor list foo bar")
action :thor, :list, "foo", "bar"
end
it "converts options hash to switches" do
runner.should_receive(:system).with("thor list foo bar --foo")
action :thor, :list, "foo", "bar", :foo => true
runner.should_receive(:system).with("thor list --foo 1 2 3")
action :thor, :list, :foo => [1,2,3]
end
it "logs status" do
runner.should_receive(:system).with("thor list")
expect(action(:thor, :list)).to eq(" run thor list from \".\"\n")
end
it "does not log status if required" do
runner.should_receive(:system).with("thor list --foo 1 2 3")
expect(action(:thor, :list, :foo => [1,2,3], :verbose => false)).to be_empty
end
it "captures the output when :capture is given" do
runner.should_receive(:`).with("thor foo bar")
action(:thor, "foo", "bar", :capture => true)
end
end
end

View File

@@ -0,0 +1,291 @@
require 'helper'
require 'thor/base'
class Amazing
desc "hello", "say hello"
def hello
puts "Hello"
end
end
describe Thor::Base do
describe "#initialize" do
it "sets arguments array" do
base = MyCounter.new [1, 2]
expect(base.first).to eq(1)
expect(base.second).to eq(2)
end
it "sets arguments default values" do
base = MyCounter.new [1]
expect(base.second).to eq(2)
end
it "sets options default values" do
base = MyCounter.new [1, 2]
expect(base.options[:third]).to eq(3)
end
it "allows options to be given as symbols or strings" do
base = MyCounter.new [1, 2], :third => 4
expect(base.options[:third]).to eq(4)
base = MyCounter.new [1, 2], "third" => 4
expect(base.options[:third]).to eq(4)
end
it "creates options with indifferent access" do
base = MyCounter.new [1, 2], :third => 3
expect(base.options['third']).to eq(3)
end
it "creates options with magic predicates" do
base = MyCounter.new [1, 2], :third => 3
expect(base.options.third).to eq(3)
end
end
describe "#no_commands" do
it "avoids methods being added as commands" do
expect(MyScript.commands.keys).to include("animal")
expect(MyScript.commands.keys).not_to include("this_is_not_a_command")
end
end
describe "#argument" do
it "sets a value as required and creates an accessor for it" do
expect(MyCounter.start(["1", "2", "--third", "3"])[0]).to eq(1)
expect(Scripts::MyScript.start(["zoo", "my_special_param", "--param=normal_param"])).to eq("my_special_param")
end
it "does not set a value in the options hash" do
expect(BrokenCounter.start(["1", "2", "--third", "3"])[0]).to be_nil
end
end
describe "#arguments" do
it "returns the arguments for the class" do
expect(MyCounter.arguments).to have(2).items
end
end
describe ":aliases" do
it "supports string aliases without a dash prefix" do
expect(MyCounter.start(["1", "2", "-z", "3"])[4]).to eq(3)
end
it "supports symbol aliases" do
expect(MyCounter.start(["1", "2", "-y", "3"])[5]).to eq(3)
expect(MyCounter.start(["1", "2", "-r", "3"])[5]).to eq(3)
end
end
describe "#class_option" do
it "sets options class wise" do
expect(MyCounter.start(["1", "2", "--third", "3"])[2]).to eq(3)
end
it "does not create an accessor for it" do
expect(BrokenCounter.start(["1", "2", "--third", "3"])[3]).to be_false
end
end
describe "#class_options" do
it "sets default options overwriting superclass definitions" do
options = Scripts::MyScript.class_options
expect(options[:force]).not_to be_required
end
end
describe "#remove_argument" do
it "removes previous defined arguments from class" do
expect(ClearCounter.arguments).to be_empty
end
it "undefine accessors if required" do
expect(ClearCounter.new).not_to respond_to(:first)
expect(ClearCounter.new).not_to respond_to(:second)
end
end
describe "#remove_class_option" do
it "removes previous defined class option" do
expect(ClearCounter.class_options[:third]).to be_nil
end
end
describe "#class_options_help" do
before do
@content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) }
end
it "shows options description" do
expect(@content).to match(/# The third argument/)
end
it "shows usage with banner content" do
expect(@content).to match(/\[\-\-third=THREE\]/)
end
it "shows default values below description" do
expect(@content).to match(/# Default: 3/)
end
it "shows options in different groups" do
expect(@content).to match(/Options\:/)
expect(@content).to match(/Runtime options\:/)
expect(@content).to match(/\-p, \[\-\-pretend\]/)
end
it "use padding in options that does not have aliases" do
expect(@content).to match(/^ -t, \[--third/)
expect(@content).to match(/^ \[--fourth/)
end
it "allows extra options to be given" do
hash = { "Foo" => B.class_options.values }
content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, hash) }
expect(content).to match(/Foo options\:/)
expect(content).to match(/--last-name=LAST_NAME/)
end
it "displays choices for enums" do
content = capture(:stdout) { Enum.help(Thor::Base.shell.new) }
expect(content).to match(/Possible values\: apple, banana/)
end
end
describe "#namespace" do
it "returns the default class namespace" do
expect(Scripts::MyScript.namespace).to eq("scripts:my_script")
end
it "sets a namespace to the class" do
expect(Scripts::MyDefaults.namespace).to eq("default")
end
end
describe "#group" do
it "sets a group" do
expect(MyScript.group).to eq("script")
end
it "inherits the group from parent" do
expect(MyChildScript.group).to eq("script")
end
it "defaults to standard if no group is given" do
expect(Amazing.group).to eq("standard")
end
end
describe "#subclasses" do
it "tracks its subclasses in an Array" do
expect(Thor::Base.subclasses).to include(MyScript)
expect(Thor::Base.subclasses).to include(MyChildScript)
expect(Thor::Base.subclasses).to include(Scripts::MyScript)
end
end
describe "#subclass_files" do
it "returns tracked subclasses, grouped by the files they come from" do
thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor")
expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to eq([
MyScript, MyScript::AnotherScript, MyChildScript, Barn,
PackageNameScript, Scripts::MyScript, Scripts::MyDefaults,
Scripts::ChildDefault, Scripts::Arities
])
end
it "tracks a single subclass across multiple files" do
thorfile = File.join(File.dirname(__FILE__), "fixtures", "command.thor")
expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to include(Amazing)
expect(Thor::Base.subclass_files[File.expand_path(__FILE__)]).to include(Amazing)
end
end
describe "#commands" do
it "returns a list with all commands defined in this class" do
expect(MyChildScript.new).to respond_to("animal")
expect(MyChildScript.commands.keys).to include("animal")
end
it "raises an error if a command with reserved word is defined" do
expect {
klass = Class.new(Thor::Group)
klass.class_eval "def shell; end"
}.to raise_error(RuntimeError, /"shell" is a Thor reserved word and cannot be defined as command/)
end
end
describe "#all_commands" do
it "returns a list with all commands defined in this class plus superclasses" do
expect(MyChildScript.new).to respond_to("foo")
expect(MyChildScript.all_commands.keys).to include("foo")
end
end
describe "#remove_command" do
it "removes the command from its commands hash" do
expect(MyChildScript.commands.keys).not_to include("bar")
expect(MyChildScript.commands.keys).not_to include("boom")
end
it "undefines the method if desired" do
expect(MyChildScript.new).not_to respond_to("boom")
end
end
describe "#from_superclass" do
it "does not send a method to the superclass if the superclass does not respond to it" do
expect(MyCounter.get_from_super).to eq(13)
end
end
describe "#start" do
it "raises an error instead of rescueing if THOR_DEBUG=1 is given" do
begin
ENV["THOR_DEBUG"] = 1
expect {
MyScript.start ["what", "--debug"]
}.to raise_error(Thor::UndefinedcommandError, 'Could not find command "what" in "my_script" namespace.')
rescue
ENV["THOR_DEBUG"] = nil
end
end
it "does not steal args" do
args = ["foo", "bar", "--force", "true"]
MyScript.start(args)
expect(args).to eq(["foo", "bar", "--force", "true"])
end
it "checks unknown options" do
expect(capture(:stderr) {
MyScript.start(["foo", "bar", "--force", "true", "--unknown", "baz"])
}.strip).to eq("Unknown switches '--unknown'")
end
it "checks unknown options except specified" do
expect(capture(:stderr) {
expect(MyScript.start(["with_optional", "NAME", "--omg", "--invalid"])).to eq(["NAME", {}, ["--omg", "--invalid"]])
}.strip).to be_empty
end
end
describe "attr_*" do
it "does not add attr_reader as a command" do
expect(capture(:stderr){ MyScript.start(["another_attribute"]) }).to match(/Could not find/)
end
it "does not add attr_writer as a command" do
expect(capture(:stderr){ MyScript.start(["another_attribute=", "foo"]) }).to match(/Could not find/)
end
it "does not add attr_accessor as a command" do
expect(capture(:stderr){ MyScript.start(["some_attribute"]) }).to match(/Could not find/)
expect(capture(:stderr){ MyScript.start(["some_attribute=", "foo"]) }).to match(/Could not find/)
end
end
end

View File

@@ -0,0 +1,80 @@
require 'helper'
describe Thor::Command do
def command(options={})
options.each do |key, value|
options[key] = Thor::Option.parse(key, value)
end
@command ||= Thor::Command.new(:can_has, "I can has cheezburger", "I can has cheezburger\nLots and lots of it", "can_has", options)
end
describe "#formatted_usage" do
it "includes namespace within usage" do
object = Struct.new(:namespace, :arguments).new("foo", [])
expect(command(:bar => :required).formatted_usage(object)).to eq("foo:can_has --bar=BAR")
end
it "includes subcommand name within subcommand usage" do
object = Struct.new(:namespace, :arguments).new("main:foo", [])
expect(command(:bar => :required).formatted_usage(object, false, true)).to eq("foo can_has --bar=BAR")
end
it "removes default from namespace" do
object = Struct.new(:namespace, :arguments).new("default:foo", [])
expect(command(:bar => :required).formatted_usage(object)).to eq(":foo:can_has --bar=BAR")
end
it "injects arguments into usage" do
options = {:required => true, :type => :string}
object = Struct.new(:namespace, :arguments).new("foo", [Thor::Argument.new(:bar, options)])
expect(command(:foo => :required).formatted_usage(object)).to eq("foo:can_has BAR --foo=FOO")
end
end
describe "#dynamic" do
it "creates a dynamic command with the given name" do
expect(Thor::DynamicCommand.new('command').name).to eq('command')
expect(Thor::DynamicCommand.new('command').description).to eq('A dynamically-generated command')
expect(Thor::DynamicCommand.new('command').usage).to eq('command')
expect(Thor::DynamicCommand.new('command').options).to eq({})
end
it "does not invoke an existing method" do
mock = mock()
mock.class.should_receive(:handle_no_command_error).with("to_s")
Thor::DynamicCommand.new('to_s').run(mock)
end
end
describe "#dup" do
it "dup options hash" do
command = Thor::Command.new("can_has", nil, nil, nil, :foo => true, :bar => :required)
command.dup.options.delete(:foo)
expect(command.options[:foo]).to be
end
end
describe "#run" do
it "runs a command by calling a method in the given instance" do
mock = mock()
mock.should_receive(:can_has).and_return {|*args| args }
expect(command.run(mock, [1, 2, 3])).to eq([1, 2, 3])
end
it "raises an error if the method to be invoked is private" do
klass = Class.new do
def self.handle_no_command_error(name)
name
end
private
def can_has
"fail"
end
end
expect(command.run(klass.new)).to eq("can_has")
end
end
end

View File

@@ -0,0 +1,48 @@
require 'helper'
require 'thor/core_ext/hash_with_indifferent_access'
describe Thor::CoreExt::HashWithIndifferentAccess do
before do
@hash = Thor::CoreExt::HashWithIndifferentAccess.new :foo => 'bar', 'baz' => 'bee', :force => true
end
it "has values accessible by either strings or symbols" do
expect(@hash['foo']).to eq('bar')
expect(@hash[:foo]).to eq('bar')
expect(@hash.values_at(:foo, :baz)).to eq(['bar', 'bee'])
expect(@hash.delete(:foo)).to eq('bar')
end
it "handles magic boolean predicates" do
expect(@hash.force?).to be_true
expect(@hash.foo?).to be_true
expect(@hash.nothing?).to be_false
end
it "handles magic comparisions" do
expect(@hash.foo?('bar')).to be_true
expect(@hash.foo?('bee')).to be_false
end
it "maps methods to keys" do
expect(@hash.foo).to eq(@hash['foo'])
end
it "merges keys independent if they are symbols or strings" do
@hash.merge!('force' => false, :baz => "boom")
expect(@hash[:force]).to eq(false)
expect(@hash[:baz]).to eq("boom")
end
it "creates a new hash by merging keys independent if they are symbols or strings" do
other = @hash.merge('force' => false, :baz => "boom")
expect(other[:force]).to eq(false)
expect(other[:baz]).to eq("boom")
end
it "converts to a traditional hash" do
expect(@hash.to_hash.class).to eq(Hash)
expect(@hash).to eq({ 'foo' => 'bar', 'baz' => 'bee', 'force' => true })
end
end

View File

@@ -0,0 +1,115 @@
require 'helper'
require 'thor/core_ext/ordered_hash'
describe Thor::CoreExt::OrderedHash do
before do
@hash = Thor::CoreExt::OrderedHash.new
end
describe "without any items" do
it "returns nil for an undefined key" do
expect(@hash["foo"]).to be_nil
end
it "doesn't iterate through any items" do
@hash.each { fail }
end
it "has an empty key and values list" do
expect(@hash.keys).to be_empty
expect(@hash.values).to be_empty
end
it "must be empty" do
expect(@hash).to be_empty
end
end
describe "with several items" do
before do
@hash[:foo] = "Foo!"
@hash[:bar] = "Bar!"
@hash[:baz] = "Baz!"
@hash[:bop] = "Bop!"
@hash[:bat] = "Bat!"
end
it "returns nil for an undefined key" do
expect(@hash[:boom]).to be_nil
end
it "returns the value for each key" do
expect(@hash[:foo]).to eq("Foo!")
expect(@hash[:bar]).to eq("Bar!")
expect(@hash[:baz]).to eq("Baz!")
expect(@hash[:bop]).to eq("Bop!")
expect(@hash[:bat]).to eq("Bat!")
end
it "iterates through the keys and values in order of assignment" do
arr = []
@hash.each do |key, value|
arr << [key, value]
end
expect(arr).to eq([[:foo, "Foo!"], [:bar, "Bar!"], [:baz, "Baz!"],
[:bop, "Bop!"], [:bat, "Bat!"]])
end
it "returns the keys in order of insertion" do
expect(@hash.keys).to eq([:foo, :bar, :baz, :bop, :bat])
end
it "returns the values in order of insertion" do
expect(@hash.values).to eq(["Foo!", "Bar!", "Baz!", "Bop!", "Bat!"])
end
it "does not move an overwritten node to the end of the ordering" do
@hash[:baz] = "Bip!"
expect(@hash.values).to eq(["Foo!", "Bar!", "Bip!", "Bop!", "Bat!"])
@hash[:foo] = "Bip!"
expect(@hash.values).to eq(["Bip!", "Bar!", "Bip!", "Bop!", "Bat!"])
@hash[:bat] = "Bip!"
expect(@hash.values).to eq(["Bip!", "Bar!", "Bip!", "Bop!", "Bip!"])
end
it "appends another ordered hash while preserving ordering" do
other_hash = Thor::CoreExt::OrderedHash.new
other_hash[1] = "one"
other_hash[2] = "two"
other_hash[3] = "three"
expect(@hash.merge(other_hash).values).to eq(["Foo!", "Bar!", "Baz!", "Bop!", "Bat!", "one", "two", "three"])
end
it "overwrites hash keys with matching appended keys" do
other_hash = Thor::CoreExt::OrderedHash.new
other_hash[:bar] = "bar"
expect(@hash.merge(other_hash)[:bar]).to eq("bar")
expect(@hash[:bar]).to eq("Bar!")
end
it "converts to an array" do
expect(@hash.to_a).to eq([[:foo, "Foo!"], [:bar, "Bar!"], [:baz, "Baz!"], [:bop, "Bop!"], [:bat, "Bat!"]])
end
it "must not be empty" do
expect(@hash).not_to be_empty
end
it "deletes values from hash" do
expect(@hash.delete(:baz)).to eq("Baz!")
expect(@hash.values).to eq(["Foo!", "Bar!", "Bop!", "Bat!"])
expect(@hash.delete(:foo)).to eq("Foo!")
expect(@hash.values).to eq(["Bar!", "Bop!", "Bat!"])
expect(@hash.delete(:bat)).to eq("Bat!")
expect(@hash.values).to eq(["Bar!", "Bop!"])
end
it "returns nil if the value to be deleted can't be found" do
expect(@hash.delete(:nothing)).to be_nil
end
end
end

View File

@@ -0,0 +1,19 @@
require 'helper'
require 'thor/base'
describe "Exit conditions" do
it "exits 0, not bubble up EPIPE, if EPIPE is raised" do
epiped = false
command = Class.new(Thor) do
desc "my_action", "testing EPIPE"
define_method :my_action do
epiped = true
raise Errno::EPIPE
end
end
expect{ command.start(["my_action"]) }.to raise_error(SystemExit)
expect(epiped).to eq(true)
end
end

Some files were not shown because too many files have changed in this diff Show More