15 Commits

Author SHA1 Message Date
9241098247 Woops, missed files on last commit... 2020-01-30 02:41:35 -05:00
e5fd8beec4 continued refactor from few years ago. Added NullDriver moduleLoader test plugin, deleted some older unused or unfinished modules, split SDK off into class includes for the namespace and finished their refactoring. 2020-01-30 02:40:53 -05:00
CJ Laing
0ba68a1e1e Fixed some small bugs and added preliminary Windows support. Overwatch is officially PHP7 dependant now. 2016-11-06 18:07:45 -05:00
CJ Laing
1470b39076 *Removed InputDeviceServices.
* Made a few changes to begin multiplatform support.
* Added NotificationServices skeleton.
2016-11-06 15:33:15 -05:00
ShadowEO
82bc933a0c Moving and preparing for C/S SDK Split. 2016-08-12 22:05:13 -04:00
ShadowEO
23909f688e Replaced LogEcho() with \Overwatch\SDK\v1\Utility::Log() 2016-06-26 02:20:44 -04:00
ShadowEO
b128b0ffd7 Staging core changes for move to SDK. Removed unused, refactored, or deprecated classes. 2016-06-26 02:12:37 -04:00
ShadowEO
f50eb13b5f Added index file for including OverwatchSDK. 2016-06-26 02:09:01 -04:00
ShadowEO
ea0170bc9d Added some beginning work to RPC handler in OverwatchSDK, continuing to work on SDK documentation while writing. 2016-06-26 01:57:23 -04:00
ShadowEO
bc41f385c2 Attempted to document some of InputDeviceServices and refactored libModOverwatch into \Overwatch\SDK\v1\ModuleLoader. 2016-06-26 01:41:17 -04:00
ShadowEO
21db58bbf2 Removed .phar files from repository and added them to ignores. 2016-06-26 00:39:15 -04:00
ShadowEO
7a95a5ba21 Added documentation to gitignore to ignore built documentation. 2016-06-26 00:36:16 -04:00
ShadowEO
c299a11456 Removed built documents from repository and added script to build documentation. 2016-06-26 00:34:30 -04:00
ShadowEO
cc93e649a8 Added phpDocumentor to development requirements and documented SDK. 2016-06-26 00:24:50 -04:00
ShadowEO
b4638e688a Starting developmentof Overwatch SDK v1. 2016-06-24 01:05:20 -04:00
1977 changed files with 1398 additions and 202222 deletions

6
.gitignore vendored
View File

@@ -1 +1,7 @@
build/
docs/
composer.phar
composer.lock
ast.dump
changes
vendor/

View File

@@ -7,13 +7,18 @@
define("OVERWATCH_ROOT", dirname(__FILE__));
define("OVERWATCH_LIBRARIES", OVERWATCH_ROOT."/lib");
define("OVERWATCH_MODULES", OVERWATCH_LIBRARIES."/modules/");
define("OVERWATCH_LIBRARIES", OVERWATCH_ROOT.DIRECTORY_SEPARATOR."lib");
define("OVERWATCH_MODULES", OVERWATCH_LIBRARIES.DIRECTORY_SEPARATOR."modules".DIRECTORY_SEPARATOR);
// Load in module support and required classes.
require_once "vendor/autoload.php";
require_once OVERWATCH_LIBRARIES."/libModOverwatch.class.php";
require_once OVERWATCH_LIBRARIES."/libOverwatchAPI.class.php";
require_once OVERWATCH_LIBRARIES."/libAPIQL.class.php";
require_once OVERWATCH_LIBRARIES.DIRECTORY_SEPARATOR."libModOverwatch.class.php";
require_once OVERWATCH_LIBRARIES.DIRECTORY_SEPARATOR."libOverwatchAPI.class.php";
require_once OVERWATCH_LIBRARIES.DIRECTORY_SEPARATOR."libAPIQL.class.php";
require_once OVERWATCH_ROOT.DIRECTORY_SEPARATOR."sdk".DIRECTORY_SEPARATOR."OverwatchSDK.inc.php";
if(\Overwatch\SDK\Manager::isSDK_Installed("1") == false)
{
die("OverwatchSDK is missing, possibly a bad build, or tree?");
}
use Noodlehaus\Config;
echo PHP_EOL;
@@ -26,12 +31,11 @@ $application_channel = "release";
// Load version information from built-in version file. (used for releases)
$version = file_get_contents(OVERWATCH_ROOT."/build/version");
if(file_exists(OVERWATCH_ROOT.DIRECTORY_SEPARATOR."build".DIRECTORY_SEPARATOR."version"))
{
$version = file_get_contents(OVERWATCH_ROOT.DIRECTORY_SEPARATOR."build".DIRECTORY_SEPARATOR."version");
}
// Define engine version so that plugins can check against it.
// We will want to define the local version information (built into the phar if compiled)
// rather than the git version, unless there's a decent way to compare versions provided by git describe.
define("OVERWATCH_VERSION", $version);
// Load (or override local) version information using git repository.
if (file_exists(".git")) {
@@ -39,34 +43,34 @@ if (file_exists(".git")) {
$application_channel = "dev";
}
// Define engine version so that plugins can check against it.
// We will want to define the local version information (built into the phar if compiled)
// rather than the git version, unless there's a decent way to compare versions provided by git describe.
define("OVERWATCH_VERSION", $version);
// Load the core version from the phar if we're running compiled.
$coreversion = file_get_contents(OVERWATCH_ROOT."/build/core-version");
// Now we'll define the core version of the software as well.
$version = str_replace(PHP_EOL, "", $version);
LogEcho("Overwatchd startup, version: $version (built from: $coreversion), channel: $application_channel", "OVERWATCH");
/**
* Allows a "pretty-print" style output.
* @param mixed $str Log message.
* @param mixed $level Log level or specific part of application.
*/
function LogEcho($str, $level) {
$str = str_replace(PHP_EOL, "", $str);
$level = str_replace(PHP_EOL, "", $level);
echo "[$level] $str".PHP_EOL;
if(file_exists(OVERWATCH_ROOT.DIRECTORY_SEPARATOR."build".DIRECTORY_SEPARATOR."core-version"))
{
$coreversion = file_get_contents(OVERWATCH_ROOT.DIRECTORY_SEPARATOR."build".DIRECTORY_SEPARATOR."core-version");
} else {
$coreversion = $version;
}
LogEcho("Loading built-in API Modules...", "OVERWATCH");
LoadAPIModules(OVERWATCH_MODULES);
// Now we'll define the core version of the software as well.
$coreversion = str_replace(array("\n", "\r"), "", $coreversion);
$version = str_replace(array("\n", "\r"), "", $version);
\Overwatch\SDK\v1\Utility::Log("Overwatchd startup, version: $version (built from: $coreversion), channel: $application_channel", "OVERWATCH");
// We look for a system-wide configuration file for the daemon in /etc. If one is not available,
// we then skip it.
if(file_exists(__FILE__."/overwatchd.conf.dist"))
if(file_exists(__FILE__.DIRECTORY_SEPARATOR."overwatchd.conf.dist"))
{
$configFiles = array(__FILE__."/overwatchd.conf.dist");
$configFiles = array(__FILE__.DIRECTORY_SEPARATOR."overwatchd.conf.dist");
}
if(file_exists("/etc/overwatchd.conf"))
@@ -74,9 +78,9 @@ if(file_exists("/etc/overwatchd.conf"))
array_push($configFiles,"/etc/overwatchd.conf");
}
if(file_exists(__FILE__."/overwatchd.conf"))
if(file_exists(__FILE__.DIRECTORY_SEPARATOR."overwatchd.conf"))
{
array_push($configFiles,__FILE__."/overwatchd.conf");
array_push($configFiles,__FILE__.DIRECTOR_SEPARATOR."overwatchd.conf");
}
if(isset($configFiles))
@@ -90,17 +94,19 @@ if(isset($configFiles))
if(array_key_exists("DEBUG",$arrOverwatch_Config) OR $application_channel == "dev")
{
if($arrOverwatch_Config['DEBUG'] == "true")
if(array_key_exists("DEBUG", $arrOverwatch_Config) AND $arrOverwatch_Config['DEBUG'] == "true")
{
define("DEBUG",true);
LogEcho("Enabling debug mode due to configuration flag..", "DEBUG");
\Overwatch\SDK\v1\Utility::Log("Enabling debug mode due to configuration flag..", "DEBUG");
} elseif ($application_channel == "dev")
{
define("DEBUG", true);
LogEcho("We're running under the dev channel. Debug Ahoy!", "DEBUG");
\Overwatch\SDK\v1\Utility::Log("We're running under the dev channel. Debug Ahoy!", "DEBUG");
}
}
\Overwatch\SDK\v1\Utility::Log("Loading built-in API Modules...", "OVERWATCH");
LoadAPIModules(OVERWATCH_MODULES);
LoadModulesFromConfiguration();
$state = array();
@@ -110,12 +116,29 @@ $state = array();
if(defined("DEBUG"))
{
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
apiql::register("!SHUTDOWN", "Shutdown");
apiql::register("!SHUTDOWN[json]", "Shutdown");
$quit == 2;
} else {
error_reporting(0);
}
function is_base64($str) {
if (!preg_match('~[^0-9a-zA-Z+/=]~', $str)) {
$check = str_split(base64_decode($str));
$x = 0;
foreach ($check as $char) if (ord($char) > 126) $x++;
if ($x/count($check)*100 < 30) return true;
}
return false;
}
// Let's set up the event loop
$loop = React\EventLoop\Factory::create();
@@ -130,20 +153,21 @@ $socket->on('connection', function($connection) use ($clients, &$i) {
// Socket Listener
$connection->on('data', function($message) use ($clients, $connection) {
$connection->lastAPI = str_replace(array("\n", "\r\n", "\r"), '', $message);
if ( base64_encode(base64_decode($connection->lastAPI, true)) === $connection->lastAPI){
if(DEBUG)
{
LogEcho("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS-DEBUG");
}
$connection->lastAPI = base64_decode($connection->lastAPI);
}
if(strtoupper($connection->lastAPI) != strtoupper("shutdown"))
if(strpos($connection->lastAPI, " {") == 0)
{
if (is_base64($connection->lastAPI) == true) {
\Overwatch\SDK\v1\Utility::Log("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS-DEBUG",\Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
$connection->lastAPI = base64_decode($connection->lastAPI);
}
}
$connection->lastAPI = str_replace("'", "\"", $connection->lastAPI);
# Let's pull apart the current request to check for a connection-id item. If it doesn't exist, we'll inject it in.
$command = explode ("{", $connection->lastAPI, 2);
$commandJSON = json_decode("{".$command[1], true);
$command = explode (" {", $connection->lastAPI, 2);
if(isset($command[1]))
{
$commandJSON = json_decode("{".$command[1], true);
}
if(!isset($commandJSON['connection-id']))
{
$commandJSON['connection-id'] = $connection->id;
@@ -152,12 +176,8 @@ $socket->on('connection', function($connection) use ($clients, &$i) {
}
} else {
}
$connection->lastResult = apicall($connection->lastAPI);
LogEcho("Received query: ".$connection->lastAPI." from ".$connection->id, "SOCKET-API");
\Overwatch\SDK\v1\Utility::Log("Received query: ".$connection->lastAPI." from ".$connection->id, "SOCKET-API");
foreach ($clients as $client) {
if ($client->id == $connection->id) {
if($connection->lastResult != NULL)
@@ -181,7 +201,7 @@ if (empty($arrOverwatch_Config['API_PORT'])) {
// Start listening on the configured port.
$socket->listen($API_PORT, "0.0.0.0");
LogEcho("Server listening on port: $API_PORT", "OVERWATCH");
\Overwatch\SDK\v1\Utility::Log("Server listening on port: $API_PORT", "OVERWATCH");
// Now we begin the actual event loop.
$loop->run();
@@ -190,7 +210,7 @@ $loop->run();
*
* @return unknown
*/
function Shutdown() {
function Shutdown($sql) {
global $quit;
global $loop;
$loop->stop();
@@ -222,6 +242,10 @@ function APIcall($call, $apiKey = null) {
{
return NULL;
}
if(is_array($APIResponse))
{
$APIResponse = json_encode($APIResponse);
}
return $APIResponse;
}
}
@@ -231,12 +255,12 @@ function LoadModulesFromConfiguration()
global $arrOverwatch_Config;
if(array_key_exists("MODULES_DIR", $arrOverwatch_Config))
{
LogEcho("External module directory defined, loading external modules...","OVERWATCH");
\Overwatch\SDK\v1\Utility::Log("External module directory defined, loading external modules...","OVERWATCH");
LoadAPIModules($arrOverwatch_Config['MODULES_DIR']);
}
if(array_key_exists("SYSTEM_MODULES_DIR", $arrOverwatch_Config))
{
LogEcho("Loading modules from system store: ".$arrOverwatch_Config['SYSTEM_MODULES_DIR']);
\Overwatch\SDK\v1\Utility::Log("Loading modules from system store: ".$arrOverwatch_Config['SYSTEM_MODULES_DIR']);
LoadAPIModules($arrOverwatch_Config['SYSTEM_MODULES_DIR']);
}
}

34
Todo.md Normal file
View File

@@ -0,0 +1,34 @@
- Fix PHAR builds, require_once statements need absolute path (libOverwatchAPI.class.php:8)
- Complete current functionality refactor (RPC handling, APIQL raw calling, module replacement)
- Move the S2S infrastructure out of the Server2Server module and into a psuedo-module (meaning it's registered with APIQL as if it were a built-in module, but it's actually a built-in part of the core SDK, and will open a Server2Server listening socket on psuedo-module load up).
- Build new "kernel" infrastructure, which abstracts out ReactPHP into an easier to manage framework for the SDK.
- Provide thread (event loop) management
- Provide connection management
- Provide OSA (Operating System Abstraction) Layer to provide full cross-platform support.
- Provide hookable events infrastructure using ReactPHP to provide system-wide hooks for modules
- Provide a centralized configuration interface.
- Enhance ModuleBoilerplate
- Allow modules to declare system requirement checks, and then perform them using the OSAL to abstract commands
- Allow modules to specify required operating system, free memory, etc.
- Allow modules to specify runtime extensions (PHP)
- Allow modules to require the daemon be granted administrative privileges to perform.
- Allow modules to specify kernel types (server, standalone-noasync, etc) to allow for scripts and applications using the Overwatch SDK.
- Allow modules to react to signals from the kernel infrastructure.
- Allow modules to declare query help strings in their docblock, and have it exposed by the system.
- Allow modules to opt-in to S2S usage, as opposed to all commands being readily accessible from the source.
- Enhance ModuleLoader
- Allow modules to be stored in standalone Phar archives, this will allow for a cleaner modules directory.
- Allow built-in modules to started in a disabled state, and not registered with APIQL until enabled.
- Enhance API Call System
- Allow query definitions to be de-registered from the system on module unload. This will prevent the module from being called even though it's supposed to be "unloaded" and in an uninitialized state.
- Provide a "test query" function to allow the Server to Server infrastructure to ask the APIQL library if a query can be handled by it's core.
- Enhance the Server to Server infrastructure.
- The server to server infrastructure running on each overwatch SDK powered server, should keep a state-machine with the modules and versions loaded on each connected server. It should also provide a synchronization architecture for synchronizing the state of the modules between each server.
- Provide a server to server event handling infrastructure. Perhaps in a separate event loop.
## Module Development Guidelines
1. Modules must NEVER call to the connection write themselves, they should use the connection handling system in the SDK.
2. Modules must NEVER create/parse the response/request jacket around the resulting JSON themselves.
3. Modules must NOT attempt to parse the raw TCP data.
4. Modules should not write to the screen without the use of the Overwatch SDK utility class.

4
build-docs Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
vendor/bin/phpdoc -d ./sdk -t ./docs/html/sdk-server --title="Overwatch API Server SDK" --template=clean
vendor/bin/phpdoc -d ./sdk -t ./docs/xml/sdk-server --title="Overwatch API Server SDK" --template=xml

View File

@@ -1,6 +0,0 @@
8-31-2015
---------
Overwatch has now moved out of pre-alpha status and into Milestone 1. Basic functionality of the API Host daemon is complete.
Milestone 1 will have development focused on module support and APIs.
Please remember, The Overwatch daemon is still a private project.

6
compile-executable.cmd Normal file
View File

@@ -0,0 +1,6 @@
@ECHO OFF
git describe > .\build\core-version
git tag > .\build\version
vendor\bin\phar-builder.bat package composer.json

View File

@@ -1,5 +1,5 @@
{
"minimum-stability": "dev",
"minimum-stability": "alpha",
"require": {
"react/react": "^0.4.2",
"react/http": "^0.4.1",
@@ -9,10 +9,14 @@
"ext-bcmath": "*",
"symfony/polyfill-mbstring": "^1.2",
"hassankhan/config": "^0.10.0",
"react/filesystem": "dev-master"
"react/filesystem": "dev-master",
"zetacomponents/document": "^1.3",
"phpdocumentor/reflection": "^3.0",
"nikic/php-parser": "1.*"
},
"require-dev": {
"macfja/phar-builder": "^0.2.4"
"macfja/phar-builder": "^0.2.4",
"phpdocumentor/phpdocumentor": "2.*"
},
"extra": {
"phar-builder": {
@@ -20,7 +24,7 @@
"name": "overwatchd.phar",
"output-dir": "../",
"entry-point": "Ov3rw4tch.class.php",
"include": ["lib","build"],
"include": ["lib","build","sdk"],
"include-dev": false
}
}

1820
composer.lock generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,35 +0,0 @@
<?php
abstract class API_Definition {
public function module_loaded()
{
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if(isset($this->needs_root) and $this->needs_root != "false")
{
if($processUser['name'] == "root")
{
self::API_Register();
$attempted = true;
} else {
$attempted = true;
}
} else {
self::API_Register();
}
return true;
}
abstract public function API_Register();
}
?>

View File

@@ -1,13 +1,12 @@
<?php
include("libAPIQL.class.php");
include("libAPIDefinition.class.php");
if(!defined("OVERWATCH_ROOT"))
{
die("This library requires the Overwatch SDK.");
}
$module = ModuleLoader::startInst();
require_once("sdk/v1/OverwatchServerSDK.v1.inc.php");
$module = \Overwatch\SDK\v1\ModuleLoader::startInst();
$modules = array();
function LoadAPIModules($path = OVERWATCH_MODULES)
{
@@ -19,19 +18,19 @@ function LoadAPIModules($path = OVERWATCH_MODULES)
if(defined("DEBUG"))
{
if($modulename != "." && $modulename != "..") {
LogEcho("Module found: $modulename", "API");
\Overwatch\SDK\v1\Utility::Log("Module found: $modulename", "API");
}
}
if(is_dir($path."$modulename") && $modulename != "." && $modulename != "..")
{
LogEcho("Loading Module: $modulename", "API");
\Overwatch\SDK\v1\Utility::Log("Loading Module: $modulename", "API");
$modules[$modulename] = $module->loadModule($modulename);
if(is_object($modules[$modulename]) && $modules[$modulename]->module_loaded())
if(is_object($modules[$modulename]))
{
// We're good here
if(defined("DEBUG"))
{
LogEcho("API Module Loaded: $modulename", "OVERWATCH");
\Overwatch\SDK\v1\Utility::Log("API Module Loaded: $modulename", "OVERWATCH");
}
}
}

View File

@@ -1 +0,0 @@
a:2:{s:7:"setting";s:32:"713f82c4d3dd6a2caef0bb576a88951f";s:5:"files";a:1:{s:27:"InputDeviceServices.API.php";s:32:"398b6fceb26b4d2476de53cebc6b77ed";}}

View File

@@ -1,221 +0,0 @@
<?php
/**
* InputDeviceServices.API.php
* Provides Input Device Mirroring for Devices, entirely in Userspace.
* @package default
*/
$inputserver_sock = null;
$inputserver_ref = null;
$inputclients = null;
$inputserver_sock = null;
$inputserver_conn = null;
$stream = null;
class InputDeviceServices {
/**
*
* @return unknown
*/
function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
/**
*
* @return unknown
*/
function module_loaded() {
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if($processUser['name'] == "root")
{
self::API_Register();
$attempted = true;
} else {
LogEcho("InputDeviceServices requires Overwatchd to be running as root.", "InputDeviceServices");
$attempted = true;
}
return true;
}
/**
*
*/
function API_Register() {
apiql::register("!OPEN/!INPUT/!SERVER[json]", "InputDeviceServices::InputServer");
apiql::register("!CLOSE/!INPUT/!SERVER", "InputDeviceServices::CloseInputServer");
apiql::register("!CONNECT/!INPUT/!DEVICES[json]", "InputDeviceServices::Connect");
apiql::register("!DISCONNECT/!INPUT/!DEVICES", "InputDeviceServices::Disconnect");
}
/**
*
* @param unknown $sql
* @return unknown
*/
function CloseInputServer($sql) {
global $inputserver_ref;
global $inputserver_sock;
$inputserver_sock->shutdown();
$inputserver_ref->terminate();
return true;
}
function InputServer($sql) {
//echo(var_dump($sql));
global $loop;
global $clients;
global $inputserver_ref;
global $inputserver_sock;
global $inputclients;
$keyboard = $sql['SERVER']['keyboard'];
$mouse = $sql['SERVER']['mouse'];
$inputserver_ref = new React\ChildProcess\Process('exec /opt/uinput-mapper/input-read '.$keyboard.' -G '.$mouse.' -D -C');
$inputserver_ref->on('exit', function($exitCode, $termSignal) use ($clients) {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Event server has stopped.",
"response_code"=>"EVT_201",
"event_server_status"=>"stopped");
foreach ($clients as $client) {
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$inputserver_sock = new React\Socket\Server($loop);
$inputclients = new SplObjectStorage();
$i = 0;
$inputserver_sock->on('connection', function($connection) use ($inputclients, &$i) {
global $inputserver_ref;
global $loop;
LogEcho("Connection to Event Server.", "INPUTS");
$connection->id = ++$i;
$connection->on('end',function() use ($inputserver_ref) {
$inputserver_ref->terminate();
});
$inputclients->attach($connection);
if($inputserver_ref->isRunning() == false)
{$inputserver_ref->start($loop);}
$inputserver_ref->stdout->on('data', function($output) use ($inputclients, $connection, &$i) {
global $inputclients;
foreach ($inputclients as $inputclient) {
if ($inputclient->id == $connection->id) {
$inputclient->write($output);
} else {
continue;
}
}
});
$inputserver_ref->stderr->on('data', function($output) {
if($output != "") LogEcho($output,"INPUTS");
});
});
$inputserver_sock->listen("5055", "0.0.0.0");
LogEcho("Event server started on port 5055", "INPUTS");
//$loop->addTimer(0.001, function($timer) use ($inputserver_ref) {
//});
if($sql['SERVER']['client-string-return'] = 'true')
{
return "CONNECT INPUT DEVICES {'host':'".gethostname()."', 'port': 5055}";
}
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Event server started on port 5055",
"response_code"=>"EVT_200",
"event_server_port"=>5055,
"event_server_status"=>"started");
return json_encode($ResponseArray);
}
function Connect($sql) {
$IP = $sql['DEVICES']['host'];
$PORT = $sql['DEVICES']['port'];
global $loop;
global $input_client_process;
global $input_client;
global $stream;
//$client = new steam_socket_client('tcp://'.$IP.":".$PORT);
//$client_connection = new React\Stream\Steam($client, $loop);
$input_client_process = new React\ChildProcess\Process('/opt/uinput-mapper/input-create -C');
$input_client_process->start($loop);
$input_client_process->stderr->on('data', function ($output) {
if($output != "") LogEcho($output,"INPUTC");
});
$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached($IP, $loop);
$input_client = new React\SocketClient\Connector($loop, $dns);
$input_client->create($IP, $PORT)->then(function (React\Stream\Stream $stream) use ($IP, $PORT) {
LogEcho("Connected to event server on: ".$IP. ":". $PORT,"INPUTC");
$GLOBALS['stream'] = $stream;
$stream->on('data', function($data) {
global $input_client_process;
#if(defined("DEBUG")) LogEcho($data, "INPUTC");
$input_client_process->stdin->write($data);
});
});
#$netcat_process = new React\ChildProcess\Process('exec nc '.$IP.' '.$PORT);
#$netcat_process->start($loop);
#$netcat_process->stdout->on('data', function($output) {
# $input_client_process->stdin->write($output);
# });
//$connection->on('data', function($output) {
// $client_process->stdin->write($output);
//});
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Successfully connected to Event Server socket.",
"response_code"=>"EVT_200C");
return json_encode($ResponseArray);
}
function Disconnect($sql) {
global $input_client_process;
global $input_client;
global $stream;
$input_client_process->terminate();
$stream->end();
//$input_client->end();
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Successfully disconnected from Event Server socket.",
"response_code"=>"EVT_200D");
return json_encode($ResponseArray);
}
}

View File

@@ -1,2 +0,0 @@
#!/bin/bash
/opt/uinput-mapper/input-read $1 -G $2 -D | nc -l 5055

View File

@@ -0,0 +1,19 @@
<?php
class NullDriver extends \Overwatch\SDK\v1\ModuleBoilerplate {
public static $moduleType = "API";
public const requires_root = false;
static function Initialize()
{
\Overwatch\SDK\v1\Utility::Log("NullDriver::Initialize()", "NullDriver-Plugin", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
}
static function API_Register()
{
\Overwatch\SDK\v1\Utility::Log("NullDriver::API_Register()", "NullDriver-Plugin", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
}
}

View File

@@ -18,7 +18,14 @@ class Server2Server {
public function module_loaded()
{
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if(file_exists("/etc"))
{
$processUser = posix_getpwuid(posix_geteuid());
} else {
$processUser = "root";
}
if($attempted == true)
{
return true;
@@ -72,13 +79,13 @@ class Server2Server {
$S2SConnection = new React\SocketClient\Connector($loop, $dns);
$S2SConnection->create($sql['QUERY']['server'], 1337)->then(function (React\Stream\Stream $stream) use ($sql, &$connection, $connectionId) {
LogEcho("Connection ".$connectionId." requested connection to Overwatchd instance: ".$sql['QUERY']['server'].":1337, Query: ".$sql['QUERY']['query'],"Server2Server-Comms");
\Overwatch\SDK\v1\Utility::Log("Connection ".$connectionId." requested connection to Overwatchd instance: ".$sql['QUERY']['server'].":1337, Query: ".$sql['QUERY']['query'],"Server2Server-Comms");
$buf = "";
$sent = false;
$stream->on('data', function($data) use ($sql,&$connection,$connectionId,$stream,&$sent) {
#$data = $buf;
LogEcho("Data to connection $connectionId: $data", "S2S-DEBUG");
\Overwatch\SDK\v1\Utility::Log("Data to connection $connectionId: $data", "S2S-DEBUG");
/*
if(is_json($data))
@@ -108,7 +115,7 @@ class Server2Server {
$sent = true;
});
if ( base64_encode(base64_decode($sql['QUERY']['query'], true)) !== $sql['QUERY']['query']){
#LogEcho("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS");
#\Overwatch\SDK\v1\Utility::Log("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS");
$sql['QUERY']['query'] = base64_encode($sql['QUERY']['query']);
}
$stream->write($sql['QUERY']['query']);

View File

@@ -64,8 +64,10 @@ class SystemServices {
function GetSystemInfo() {
$state['linfo'] = new \Linfo\Linfo;
$state['linfo_parser'] = $state['linfo']->getParser();
$OSArray = parse_ini_file("/etc/os-release");
if(file_exists("/etc/os-release")) { $OSArray = parse_ini_file("/etc/os-release");
} else {
$OSArray = "Windows";
}
$OverwatchVer = constant("OVERWATCH_VERSION");
$KernelVer = $state['linfo_parser']->getKernel();
$KernelArch = $state['linfo_parser']->getCPUArchitecture();
@@ -75,7 +77,6 @@ class SystemServices {
$Devices = $state['linfo_parser']->getDevs();
$Network = $state['linfo_parser']->getNet();
$Battery = $state['linfo_parser']->getBattery();
$Model = $state['linfo_parser']->getModel();
$SysInfo = array('OS'=>$OSArray,
'DaemonVersion'=>$OverwatchVer,
'Kernel'=>$KernelVer,
@@ -85,8 +86,7 @@ class SystemServices {
'Sensors'=>$Sensors,
'Devices'=>$Devices,
'Network'=>$Network,
'Battery'=>$Battery,
'Model'=>$Model);
'Battery'=>$Battery);
$ResponseArray = array("response_type"=>"success",
"response_content"=>$SysInfo,
"response_code"=>200);

View File

@@ -44,11 +44,122 @@ class WorkstationServices {
}
function Libretine_InstallPackage($sql) {}
function Libertine_InstallPackage($sql) {
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
$process = new React\ChildProcess\Process('exec libertine-container-manager install-package -p "'.$sql['PACKAGE']['package']);
$programRequestCode = sha1("INSTALL_LIBERTINE_".$sql['PACKAGE']['package']);
$ResponseArray = array();
$ResponseArray['response_process_marker'] = $programRequestCode;
$process->on('exit', function($exitCode, $termSignal) use ($ResponseArray) {
global $clients;
if($exitCode == 0)
{
$ResponseArray['response_type'] = "success";
$ResponseArray['response_code'] = "LCM_200E";
} else {
$ResponseArray['response_type'] = "failed";
$ResponseArray['response_code'] = "LCM_500E";
}
$ResponseArray['response_exit_code'] = $exitCode;
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stdout->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDO";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stderr->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDE";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
}
function Libertine_RemovePackage($sql) {}
function Libertine_RemovePackage($sql) {
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
$process = new React\ChildProcess\Process('exec libertine-container-manager remove-package -p "'.$sql['PACKAGE']);
$programRequestCode = sha1("REMOVE_LIBERTINE_".$sql['PACKAGE']);
$ResponseArray = array();
$ResponseArray['response_process_marker'] = $programRequestCode;
$process->on('exit', function($exitCode, $termSignal) use ($ResponseArray) {
global $clients;
if($exitCode == 0)
{
$ResponseArray['response_type'] = "success";
$ResponseArray['response_code'] = "LCM_200E";
} else {
$ResponseArray['response_type'] = "failed";
$ResponseArray['response_code'] = "LCM_500E";
}
$ResponseArray['response_exit_code'] = $exitCode;
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stdout->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDO";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stderr->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDE";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
}
function Libertine_CreateContainer($sql) {
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
if(isset($sql['CONTAINER']['container-settings']['id'])) $ContainerID = 'applications';
if(isset($sql['CONTAINER']['container-settings']['name'])) $ContainerName = 'Applications';
@@ -56,6 +167,14 @@ class WorkstationServices {
function Libertine_ConfAptRepository($sql)
{
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
if($sql['REPOSITORY']['operation'] == "add") $process = new React\ChildProcess\Process('exec sudo -u phablet libertine-container-manager configure -a '.$sql['REPOSITORY']['repository']);
if($sql['REPOSITORY']['operation'] == "del" OR $sql['REPOSITORY']['operation'] == "delete") $process = new React\ChildProcess\Process('exec sudo -u phablet libertine-container-manager configure -d '.$sql['REPOSITORY']['repository']);
if(!$process)
@@ -93,6 +212,14 @@ class WorkstationServices {
function Libertine_GetContainers($sql)
{
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
if(file_exists("/home/phablet/.local/share/libertine/ContainersConfig.json"))
{
$ContainersConfig = file_get_contents("/home/phablet/.local/share/libertine/ContainersConfig.json");
@@ -117,7 +244,7 @@ class WorkstationServices {
} else { continue; }
}
$ResponseArray = array("resposne_type"=>"success",
$ResponseArray = array("response_type"=>"success",
"response_data"=>$ContainerArray,
"response_code"=>"LCM_200S");
@@ -144,6 +271,14 @@ class WorkstationServices {
function Libertine_ExecContainer($sql)
{
global $loop;
if(!file_exists("/userdata"))
{
// As Libertine is ONLY to be used on Ubuntu for Mobile devices, this command will error if it is used without a utouch device.
$ResponseArray['response_type'] = "error";
$ResponseArray['response_msg'] = "This machine is not an Ubuntu Touch workstation, Libertine commands are only available on Ubuntu Touch.";
$ResponseArray['response_code'] = 500;
return json_encode($ResponseArray);
}
$process = new React\ChildProcess\Process('exec libertine-container-manager exec -c "'.$sql['CONTAINER']['command']);
$programRequestCode = sha1($sql['CONTAINER']['command']);
$ResponseArray = array();
@@ -159,7 +294,7 @@ class WorkstationServices {
$ResponseArray['response_type'] = "failed";
$ResponseArray['response_code'] = "LCM_500E";
}
$ResponseArray['libertine_exit_code'] = $exitCode;
$ResponseArray['response_exit_code'] = $exitCode;
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
@@ -204,11 +339,19 @@ class WorkstationServices {
*/
function SetLauncherIcon($sql) {
$DesktopArray = $sql['ICON'];
if (!file_exists("/home/phablet")) {
$ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
return json_encode($ErrorArray);
if(!file_exists("/etc"))
{
// We're not running a unix-based OS, error out.
$ResponseArray = array("response_type"=>"error", "response_msg"=>"Members of the Windows family are not yet supported by this module.", "response_code"=>500);
return $ResponseArray;
}
/** As we're going to be expanding Overwatch's reach to all systems, the below code will eventually need to be optimized."
* if (!file_exists("/home/phablet")) {
* $ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
* return json_encode($ErrorArray);
* }
**/
if (!file_exists("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error", "error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
@@ -233,7 +376,7 @@ class WorkstationServices {
//});
file_put_contents($destination, $fileContents);
$MD5 = md5_file($destination);
LogEcho("Uploaded MD5: ".$fileMD5.", Local MD5:".$MD5, "WorkstationServices");
\Overwatch\SDK\v1\Utility::Log("Uploaded MD5: ".$fileMD5.", Local MD5:".$MD5, "WorkstationServices");
if ($MD5 != $fileMD5) {
$ErrorArray = array("response_type"=>"error",
"error_msg"=>"MD5 Checksums do not match. File may be corrupt.",
@@ -255,6 +398,13 @@ class WorkstationServices {
function GetFreeSpace($sql) {
if($sql['SPACE']['volume'] == null)
{
$responseArray = array ("response_type"=>"error",
"response_msg"=>"You need to supply a volume.",
"response_code"=>500);
return json_encode($responseArray);
}
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_free_space($sql['SPACE']['volume']),
"response_code"=>200);
@@ -263,6 +413,13 @@ class WorkstationServices {
function GetStorageCapacity($sql) {
if($sql['SPACE']['volume'] == null)
{
$responseArray = array ("response_type"=>"error",
"response_msg"=>"You need to supply a volume.",
"response_code"=>500);
return json_encode($responseArray);
}
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_total_space($sql['SPACE']['volume']),
"response_code"=>200);
@@ -272,7 +429,12 @@ class WorkstationServices {
function CreateDesktopFile($sql) {
// Check for the existance of the requested .desktop file.
if(!file_exists("/etc"))
{
// We're not running a unix-based OS, error out.
$ResponseArray = array("response_type"=>"error", "response_msg"=>"Members of the Windows family are not yet supported by this module.", "response_code"=>500);
return $ResponseArray;
}
$DesktopArray = $sql['SHORTCUT'];
if (!file_exists("/usr/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error","error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
@@ -295,6 +457,12 @@ class WorkstationServices {
function RemoveDesktopFile($sql) {
if(!file_exists("/etc"))
{
// We're not running a unix-based OS, error out.
$ResponseArray = array("response_type"=>"error", "response_msg"=>"Members of the Windows family are not yet supported by this module.", "response_code"=>500);
return $ResponseArray;
}
$DesktopArray = $sql['SHORTCUT'];
unlink("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
}

View File

@@ -1,564 +0,0 @@
<?php
/**
* PHP Hooks Class
*
* The PHP Hooks Class is a fork of the WordPress filters hook system rolled in to a class to be ported
* into any php based system
*
* This class is heavily based on the WordPress plugin API and most (if not all) of the code comes from there.
*
*
* @version 0.1.3
* @copyright 2012 - 2014
* @author Ohad Raz (email: admin@bainternet.info)
* @link http://en.bainternet.info
*
* @license GNU General Public LIcense v3.0 - license.txt
* 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 NON-INFRINGEMENT. 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.
*
* @package PHP Hooks
*/
if (!class_exists('Hooks')){
/**
* Hooks
*/
class Hooks
{
/**
* $filters holds list of hooks
* @access public
* @since 0.1
* @var array
*/
var $filters = array();
/**
* $merged_filters
* @var array
*/
var $merged_filters = array();
/**
* $actions
* @var array
*/
var $actions = array();
/**
* $current_filter holds the name of the current filter
* @access public
* @since 0.1
* @var array
*/
var $current_filter = array();
/**
* __construct class constructor
* @access public
* @since 0.1
*/
public function __construct($args = null)
{
$this->filters = array();
$this->merged_filters = array();
$this->actions = array();
$this->current_filter = array();
}
/**
* FILTERS
*/
/**
* add_filter Hooks a function or method to a specific filter action.
* @access public
* @since 0.1
* @param string $tag The name of the filter to hook the $function_to_add to.
* @param callback $function_to_add The name of the function to be called when the filter is applied.
* @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
* @param int $accepted_args optional. The number of arguments the function accept (default 1).
* @return boolean true
*/
public function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
$idx = $this->_filter_build_unique_id($tag, $function_to_add, $priority);
$this->filters[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
unset( $this->merged_filters[ $tag ] );
return true;
}
/**
* remove_filter Removes a function from a specified filter hook.
* @access public
* @since 0.1
* @param string $tag The filter hook to which the function to be removed is hooked.
* @param callback $function_to_remove The name of the function which should be removed.
* @param int $priority optional. The priority of the function (default: 10).
* @param int $accepted_args optional. The number of arguments the function accepts (default: 1).
* @return boolean Whether the function existed before it was removed.
*/
public function remove_filter( $tag, $function_to_remove, $priority = 10 ) {
$function_to_remove = $this->_filter_build_unique_id($tag, $function_to_remove, $priority);
$r = isset($this->filters[$tag][$priority][$function_to_remove]);
if ( true === $r) {
unset($this->filters[$tag][$priority][$function_to_remove]);
if ( empty($this->filters[$tag][$priority]) )
unset($this->filters[$tag][$priority]);
unset($this->merged_filters[$tag]);
}
return $r;
}
/**
* remove_all_filters Remove all of the hooks from a filter.
* @access public
* @since 0.1
* @param string $tag The filter to remove hooks from.
* @param int $priority The priority number to remove.
* @return bool True when finished.
*/
public function remove_all_filters($tag, $priority = false) {
if( isset($this->filters[$tag]) ) {
if( false !== $priority && isset($this->filters[$tag][$priority]) )
unset($this->filters[$tag][$priority]);
else
unset($this->filters[$tag]);
}
if( isset($this->merged_filters[$tag]) )
unset($this->merged_filters[$tag]);
return true;
}
/**
* has_filter Check if any filter has been registered for a hook.
* @access public
* @since 0.1
* @param string $tag The name of the filter hook.
* @param callback $function_to_check optional.
* @return mixed If $function_to_check is omitted, returns boolean for whether the hook has anything registered.
* When checking a specific function, the priority of that hook is returned, or false if the function is not attached.
* When using the $function_to_check argument, this function may return a non-boolean value that evaluates to false
* (e.g.) 0, so use the === operator for testing the return value.
*/
public function has_filter($tag, $function_to_check = false) {
$has = !empty($this->filters[$tag]);
if ( false === $function_to_check || false == $has )
return $has;
if ( !$idx = $this->_filter_build_unique_id($tag, $function_to_check, false) )
return false;
foreach ( (array) array_keys($this->filters[$tag]) as $priority ) {
if ( isset($this->filters[$tag][$priority][$idx]) )
return $priority;
}
return false;
}
/**
* apply_filters Call the functions added to a filter hook.
* @access public
* @since 0.1
* @param string $tag The name of the filter hook.
* @param mixed $value The value on which the filters hooked to <tt>$tag</tt> are applied on.
* @param mixed $var,... Additional variables passed to the functions hooked to <tt>$tag</tt>.
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function apply_filters($tag, $value) {
$args = array();
// Do 'all' actions first
if ( isset($this->filters['all']) ) {
$this->current_filter[] = $tag;
$args = func_get_args();
$this->_call_all_hook($args);
}
if ( !isset($this->filters[$tag]) ) {
if ( isset($this->filters['all']) )
array_pop($this->current_filter);
return $value;
}
if ( !isset($this->filters['all']) )
$this->current_filter[] = $tag;
// Sort
if ( !isset( $this->merged_filters[ $tag ] ) ) {
ksort($this->filters[$tag]);
$this->merged_filters[ $tag ] = true;
}
reset( $this->filters[ $tag ] );
if ( empty($args) )
$args = func_get_args();
do {
foreach( (array) current($this->filters[$tag]) as $the_ )
if ( !is_null($the_['function']) ){
$args[1] = $value;
$value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
}
} while ( next($this->filters[$tag]) !== false );
array_pop( $this->current_filter );
return $value;
}
/**
* apply_filters_ref_array Execute functions hooked on a specific filter hook, specifying arguments in an array.
* @access public
* @since 0.1
* @param string $tag The name of the filter hook.
* @param array $args The arguments supplied to the functions hooked to <tt>$tag</tt>
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function apply_filters_ref_array($tag, $args) {
// Do 'all' actions first
if ( isset($this->filters['all']) ) {
$this->current_filter[] = $tag;
$all_args = func_get_args();
$this->_call_all_hook($all_args);
}
if ( !isset($this->filters[$tag]) ) {
if ( isset($this->filters['all']) )
array_pop($this->current_filter);
return $args[0];
}
if ( !isset($this->filters['all']) )
$this->current_filter[] = $tag;
// Sort
if ( !isset( $this->merged_filters[ $tag ] ) ) {
ksort($this->filters[$tag]);
$this->merged_filters[ $tag ] = true;
}
reset( $this->filters[ $tag ] );
do {
foreach( (array) current($this->filters[$tag]) as $the_ )
if ( !is_null($the_['function']) )
$args[0] = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
} while ( next($this->filters[$tag]) !== false );
array_pop( $this->current_filter );
return $args[0];
}
/**
* ACTIONS
*/
/**
* add_action Hooks a function on to a specific action.
* @access public
* @since 0.1
* @param string $tag The name of the action to which the $function_to_add is hooked.
* @param callback $function_to_add The name of the function you wish to be called.
* @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
* @param int $accepted_args optional. The number of arguments the function accept (default 1).
*/
public function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
return $this->add_filter($tag, $function_to_add, $priority, $accepted_args);
}
/**
* has_action Check if any action has been registered for a hook.
* @access public
* @since 0.1
* @param string $tag The name of the action hook.
* @param callback $function_to_check optional.
* @return mixed If $function_to_check is omitted, returns boolean for whether the hook has anything registered.
* When checking a specific function, the priority of that hook is returned, or false if the function is not attached.
* When using the $function_to_check argument, this function may return a non-boolean value that evaluates to false
* (e.g.) 0, so use the === operator for testing the return value.
*/
public function has_action($tag, $function_to_check = false) {
return $this->has_filter($tag, $function_to_check);
}
/**
* remove_action Removes a function from a specified action hook.
* @access public
* @since 0.1
* @param string $tag The action hook to which the function to be removed is hooked.
* @param callback $function_to_remove The name of the function which should be removed.
* @param int $priority optional The priority of the function (default: 10).
* @return boolean Whether the function is removed.
*/
public function remove_action( $tag, $function_to_remove, $priority = 10 ) {
return $this->remove_filter( $tag, $function_to_remove, $priority );
}
/**
* remove_all_actions Remove all of the hooks from an action.
* @access public
* @since 0.1
* @param string $tag The action to remove hooks from.
* @param int $priority The priority number to remove them from.
* @return bool True when finished.
*/
public function remove_all_actions($tag, $priority = false) {
return $this->remove_all_filters($tag, $priority);
}
/**
* do_action Execute functions hooked on a specific action hook.
* @access public
* @since 0.1
* @param string $tag The name of the action to be executed.
* @param mixed $arg,... Optional additional arguments which are passed on to the functions hooked to the action.
* @return null Will return null if $tag does not exist in $filter array
*/
public function do_action($tag, $arg = '') {
if ( ! isset($this->actions) )
$this->actions = array();
if ( ! isset($this->actions[$tag]) )
$this->actions[$tag] = 1;
else
++$this->actions[$tag];
// Do 'all' actions first
if ( isset($this->filters['all']) ) {
$this->current_filter[] = $tag;
$all_args = func_get_args();
$this->_call_all_hook($all_args);
}
if ( !isset($this->filters[$tag]) ) {
if ( isset($this->filters['all']) )
array_pop($this->current_filter);
return;
}
if ( !isset($this->filters['all']) )
$this->current_filter[] = $tag;
$args = array();
if ( is_array($arg) && 1 == count($arg) && isset($arg[0]) && is_object($arg[0]) ) // array(&$this)
$args[] =& $arg[0];
else
$args[] = $arg;
for ( $a = 2; $a < func_num_args(); $a++ )
$args[] = func_get_arg($a);
// Sort
if ( !isset( $this->merged_filters[ $tag ] ) ) {
ksort($this->filters[$tag]);
$this->merged_filters[ $tag ] = true;
}
reset( $this->filters[ $tag ] );
do {
foreach ( (array) current($this->filters[$tag]) as $the_ )
if ( !is_null($the_['function']) )
call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
} while ( next($this->filters[$tag]) !== false );
array_pop($this->current_filter);
}
/**
* do_action_ref_array Execute functions hooked on a specific action hook, specifying arguments in an array.
* @access public
* @since 0.1
* @param string $tag The name of the action to be executed.
* @param array $args The arguments supplied to the functions hooked to <tt>$tag</tt>
* @return null Will return null if $tag does not exist in $filter array
*/
public function do_action_ref_array($tag, $args) {
if ( ! isset($this->actions) )
$this->actions = array();
if ( ! isset($this->actions[$tag]) )
$this->actions[$tag] = 1;
else
++$this->actions[$tag];
// Do 'all' actions first
if ( isset($this->filters['all']) ) {
$this->current_filter[] = $tag;
$all_args = func_get_args();
$this->_call_all_hook($all_args);
}
if ( !isset($this->filters[$tag]) ) {
if ( isset($this->filters['all']) )
array_pop($this->current_filter);
return;
}
if ( !isset($this->filters['all']) )
$this->current_filter[] = $tag;
// Sort
if ( !isset( $merged_filters[ $tag ] ) ) {
ksort($this->filters[$tag]);
$merged_filters[ $tag ] = true;
}
reset( $this->filters[ $tag ] );
do {
foreach( (array) current($this->filters[$tag]) as $the_ )
if ( !is_null($the_['function']) )
call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
} while ( next($this->filters[$tag]) !== false );
array_pop($this->current_filter);
}
/**
* did_action Retrieve the number of times an action is fired.
* @access public
* @since 0.1
* @param string $tag The name of the action hook.
* @return int The number of times action hook <tt>$tag</tt> is fired
*/
public function did_action($tag) {
if ( ! isset( $this->actions ) || ! isset( $this->actions[$tag] ) )
return 0;
return $this->actions[$tag];
}
/**
* HELPERS
*/
/**
* current_filter Retrieve the name of the current filter or action.
* @access public
* @since 0.1
* @return string Hook name of the current filter or action.
*/
public function current_filter() {
return end( $this->current_filter );
}
/**
* Retrieve the name of the current action.
*
* @since 0.1.2
*
* @uses current_filter()
*
* @return string Hook name of the current action.
*/
function current_action() {
return $this->current_filter();
}
/**
* Retrieve the name of a filter currently being processed.
*
* The function current_filter() only returns the most recent filter or action
* being executed. did_action() returns true once the action is initially
* processed. This function allows detection for any filter currently being
* executed (despite not being the most recent filter to fire, in the case of
* hooks called from hook callbacks) to be verified.
*
* @since 0.1.2
*
* @see current_filter()
* @see did_action()
* @global array $wp_current_filter Current filter.
*
* @param null|string $filter Optional. Filter to check. Defaults to null, which
* checks if any filter is currently being run.
* @return bool Whether the filter is currently in the stack
*/
function doing_filter( $filter = null ) {
if ( null === $filter ) {
return ! empty( $this->current_filter );
}
return in_array( $filter, $this->current_filter );
}
/**
* Retrieve the name of an action currently being processed.
*
* @since 0.1.2
*
* @uses doing_filter()
*
* @param string|null $action Optional. Action to check. Defaults to null, which checks
* if any action is currently being run.
* @return bool Whether the action is currently in the stack.
*/
function doing_action( $action = null ) {
return $this->doing_filter( $action );
}
/**
* _filter_build_unique_id Build Unique ID for storage and retrieval.
* @param string $tag Used in counting how many hooks were applied
* @param callback $function Used for creating unique id
* @param int|bool $priority Used in counting how many hooks were applied. If === false and $function is an object reference, we return the unique id only if it already has one, false otherwise.
* @return string|bool Unique ID for usage as array key or false if $priority === false and $function is an object reference, and it does not already have a unique id.
*/
private function _filter_build_unique_id($tag, $function, $priority) {
static $filter_id_count = 0;
if ( is_string($function) )
return $function;
if ( is_object($function) ) {
// Closures are currently implemented as objects
$function = array( $function, '' );
} else {
$function = (array) $function;
}
if (is_object($function[0]) ) {
// Object Class Calling
if ( function_exists('spl_object_hash') ) {
return spl_object_hash($function[0]) . $function[1];
} else {
$obj_idx = get_class($function[0]).$function[1];
if ( !isset($function[0]->filter_id) ) {
if ( false === $priority )
return false;
$obj_idx .= isset($this->filters[$tag][$priority]) ? count((array)$this->filters[$tag][$priority]) : $filter_id_count;
$function[0]->filter_id = $filter_id_count;
++$filter_id_count;
} else {
$obj_idx .= $function[0]->filter_id;
}
return $obj_idx;
}
} else if ( is_string($function[0]) ) {
// Static Calling
return $function[0].$function[1];
}
}
/**
* __call_all_hook
* @access public
* @since 0.1
* @param (array) $args [description]
*/
public function __call_all_hook($args) {
reset( $this->filters['all'] );
do {
foreach( (array) current($this->filters['all']) as $the_ )
if ( !is_null($the_['function']) )
call_user_func_array($the_['function'], $args);
} while ( next($this->filters['all']) !== false );
}
}//end class
}//end if
global $hooks;
$hooks = new Hooks();
$hooks->do_action('After_Hooks_Setup',$hooks);

View File

@@ -1,42 +0,0 @@
<?php namespace OverwatchSDK;
class Network
{
// Internet based functions
function DownloadFile($src, $dest)
{
}
// DNS-SD functions
function ScanForService($servType) {}
function PublishService($servType, $name, $port, $des) {}
// IP Management
function ChangeIP($dev, $ip, $gateway, $subnetmask) {}
function DNS_ListServers() {}
function DNS_AddServer($ip) {}
function DNS_RemServer($ip) {}
}
class System
{
}
class IO
{
}
class Hardware
{
}

39
sdk/OverwatchSDK.inc.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
/**
* Overwatch SDK
*
* Provides abstracted functions and boilerplate code for modules and the core.
* @version 1.0.0
* @copyright 2016-* Tox Communications
* @author ShadowEO <dreamcaster23@gmail.com>
* @package OverwatchSDK
*/
namespace Overwatch\SDK;
if(file_exists(dirname(__FILE__)."/v1/OverwatchServerSDK.v1.inc.php"))
{
require_once(dirname(__FILE__)."/v1/OverwatchServerSDK.v1.inc.php");
}
/**
* Provides functions to manage and enumerate available SDK versions.
* @api
*/
class Manager
{
/**
* Check to see if requested SDK version is available
* @param int $version Version Number of SDK requested
* @return bool Whether SDK is available in this version of Overwatchd or not.
*/
static function isSDK_installed($version)
{
if(file_exists(dirname(__FILE__)."/v1"))
{
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Overwatch\SDK\v1;
abstract class ModuleBoilerplate
{
/**
* Register API commands and initialize module.
*
* It is recommended to place all your apiql::register statements here along with any initialization you may need.
*/
abstract protected static function Initialize();
/**
* Register API Commands and initialize module.
* @deprecated 1.0.0 Will no longer be used by modules after boilerplate has been established unless module specifically calls it.
*/
abstract protected static function API_Register();
/**
* Prepares a static instance of the calling module.
* @return object $inst Self-reference
*/
public static function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new static;
}
return $inst;
}
/**
* Used internally to provide a static reference with which to get class name.
* @return string Class name
*/
protected static function module_who() {
return __CLASS__;
}
/**
* This method is called upon the module being loaded successfully and the object being instanced properly.
* @return boolean If the module initalized successfully.
*/
static function module_loaded() {
static $attempted = null;
$class = get_called_class();
if(defined(get_called_class().'::requires_root') && static::requires_root == true)
{
if($processUser['name'] != "root")
{
\Overwatch\SDK\v1\Utility::Log(static::module_who().' requires Overwatchd to be running with root privileges.', static::module_who(), \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR);
return false;
}
}
if(method_exists($class, "Initialize"))
{
\Overwatch\SDK\v1\Utility::Log($class.' is a valid SDK-based module.', static::module_who(), \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
$status = static::Initialize();
$attempted = true;
if(isset($status))
{
return $status;
} else {
return true;
}
}
if(method_exists($class, "API_Register"))
{
\Overwatch\SDK\v1\Utility::Log(static::module_who().' is using legacy (alpha) module code. This is deprecated and will soon be removed.', static::module_who(), \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
$status = static::API_Register();
$attempted = true;
if(isset($status))
{
return $status;
} else {
return true;
}
}
if($attempted == true)
{
return true;
}
}
}

View File

@@ -0,0 +1,487 @@
<?php
/**
* Overwatch SDK Version 1.0
*
* This is the SDK for the Overwatch Daemon
* @package Overwatch\SDK\1.0
* @author ShadowEO <dreamcaster23@gmail.com>
* @version 1.0.0
* @copyright 2016-* Tox Communications
* @category Development
*/
namespace Overwatch\SDK\v1;
/**
* Utilities
*
* Provides common utility functions such as logging.
*/
class Utility {
/**
* Used to tell the logging functions to use the info level and apply appropriate formatting.
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_INFO = 0;
/**
* Used to tell the logging functions to use the warning level and apply appropriate formatting.
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_WARN = 1;
/**
* Used to tell the logging functions to use the error level and apply appropriate formatting. Tells LogToConsole to output to stderr
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_ERROR = 2;
/**
* Used to tell the logging functions to use the debugging level and apply appropriate formatting.
*
* NOTE: <b>Messages logged using the debug label will not be written to console or file unless debug mode is enabled.</b>
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_DEBUG = 3;
/**
* Used to tell the logging functions to log to stdout/stderr
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOG_CONSOLE = 4;
/**
* Used to tell the logging functions to log to a file
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOG_FILE = 5;
/**
* Logs messages to stdout
*
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param int $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @return void
*/
protected function LogToConsole($msg, $component, $level = OVR_UTIL_LOGLEVEL_INFO)
{
$oTimeStamp = new DateTime();
$sTimeStamp = $oTimeStamp->format('m-d-Y H:i:s');
$component=strtoupper($component);
switch ($level)
{
// TODO: Look into different console codes for coloring and effects
case OVR_UTIL_LOGLEVEL_ERROR:
error_log("[$sTimeStamp][$component] $msg");
break;
default:
echo("[$sTimeStamp][$component] $msg");
break;
}
}
/**
* Logs messages
*
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param int $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @param int $type Logging destination
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_FILE OVR_UTIL_LOG_FILE
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_CONSOLE OVR_UTIL_LOG_CONSOLE
* @param string $file File path to log to (if type is `OVR_UTIL_LOG_FILE`)
* @api
* @return void
*/
public function Log($msg, $component, $level = OVR_UTIL_LOGLEVEL_INFO, $type = OVR_UTIL_LOG_CONSOLE, $file = null)
{
switch($type)
{
case OVR_UTIL_LOG_CONSOLE:
LogToConsole($msg, $component, $level);
break;
case OVR_UTIL_LOG_FILE:
if($file == NULL)
{
return false;
}
LogToFile($msg, $component, $level = OVR_UTIL_LOGLEVEL_INFO, $file);
break;
}
}
/**
* Logs specified messages to a file.
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param string $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @param string $file File to write the message to.
* @return void
*/
protected function LogToFile($msg, $component, $level = OVR_UTIL_LOGLEVEL_INFO, $file)
{
$oTimeStamp = new DateTime();
$sTimeStamp = $oTimeStamp->format('m-d-Y H:i:s');
$component=strtoupper($component);
switch($level)
{
case OVR_UTIL_LOGLEVEL_ERROR:
error_log("[$sTimeStamp][$component] $msg", 0, $file);
break;
default:
file_put_contents($file, "[$sTimeStamp][$component] $msg", FILE_APPEND);
}
}
}
/**
* Provides a standard implementation for managing connections in the Overwatch API server.
*/
class ConnectionManager
{
// TODO: Implement functionality, remove from core.
/** @var int Stores the amount of currently on-going connection */
protected $iCurrentConnections;
/** @var int Stores the amount of total connections in the lifetime of the server process. */
protected $iTotalConnections;
/** @var mixed Stores hierarchy of connections */
protected $ConnectionHierarchy;
/**
* Accept a connection from the ReactPHP stack
*
* Accept a connection and place it in the list of connections.
* @param \React\Socket\Connection $oConnection Connection object returned from the `on('connection')` ReactPHP event.
* @param int $iConnection Optional, used to denote the connection ID (deprecated)
* @return string Identifying Hash for Connection
* @api
*/
public function AcceptConnection($oConnection, $iConnection = null)
{}
/**
* Write packet to an accepted connection.
*
* If a connection hash is not provided, it will broadcast to all clients.
* @param string $zPacket Packet to send to socket.
* @param string $sConnectionHash Optional, Hash to identify the connection to write to.
* @api
* @return void
*/
public function Write($zPacket, $sConnectionHash = null) {}
/**
* Add a connection to the connections store
*
* @param mixed $Connection Connection Information
* @return string Connection Hash
*/
private function __addConnection($Connection)
{}
/**
* Remove a connection from the connections store
*
* @param string $sConnectionHash Connection hash to identify the connection to remove
* @return boolean
*/
private function __remConnection($sConnectionHash)
{}
/**
* Retrieve a Connection Object from the connection store for the matching connection hash
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return \React\Socket\Connection
*/
private function __getObjectForHash($sConnectionHash)
{}
/**
* Signal disconnection to a client. This will send a disconnection response JSON message for the client to process.
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return void
*/
private function __signalDisconnection($sConnectionHash)
{}
/**
* Force a client to disconnect, this preforms __signalDisconnection and then closes the connection, removing it from the connection store.
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return boolean
*/
private function __forceDisconnection($sConnectionHash)
{}
// TODO: Implement this function: public function setLastCall($zCall, $sConnectionHash) {}
}
/**
* Module boilerplate
*
* Provides a boilerplate to base modules on.
*/
abstract class Module
{
/**
* Register API commands and initialize module.
*
* It is recommended to place all your apiql::register statements here along with any initialization you may need.
*/
abstract protected function Initialize();
/**
* Register API Commands and initialize module.
* @deprecated 1.0.0 Will no longer be used by modules after boilerplate has been established unless module specifically calls it.
*/
abstract protected function API_Register();
/**
* Prepares a static instance of the calling module.
* @return object $inst Self-reference
*/
protected function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new static;
}
return $inst;
}
/**
* Used internally to provide a static reference with which to get class name.
* @return string Class name
*/
protected static function module_who() {
return __CLASS__;
}
/**
* This method is called upon the module being loaded successfully and the object being instanced properly.
* @return boolean If the module initalized successfully.
*/
protected function module_loaded() {
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if(defined(static::module_who().'::requires_root'))
{
if($processUser['name'] != "root")
{
\Overwatch\SDK\v1\Utility::Log(static::module_who().' requires Overwatchd to be running with root privileges.', static::module_who(), \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR);
return false;
}
}
if(strtoupper(static::$moduleType) == "API" or !isset($static::$moduleType))
{
if(function_exists(static::module_who()."::API_Register"))
{
\Overwatch\SDK\v1\Utility::Log(static::module_who().' is using legacy (alpha) module code. This is deprecated and will soon be removed.', static::module_who(), \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
$status = static::API_Register();
$attempted = true;
if(!isset($status))
{
return $status;
} else {
return true;
}
}
if(function_exists(static::module_who()."::Initalize"))
{
$status = static::Initialize();
$attempted = true;
if(!isset($status))
{
return $status;
} else {
return true;
}
}
}
}
}
/**
* OverwatchSDK Module Loader
*
* Abstracts the overwatch module loader into an easier to use API.
*/
class ModuleLoader
{
/**
* @var \ModuleLoader $moduleLoader Simple Module Loader Object
*/
protected $moduleLoader;
/**
* @ignore
*/
protected static $modules;
/**
* Registers Module Loader API functions with APIQL
* @return void
*/
protected function __registerModuleAPI()
{
if(class_exists("apiql"))
{
apiql::register("!LOAD/!MODULE[json]", "\Overwatch\SDK\v1\ModuleLoader::__apiql_loadModule");
apiql::register("!UNLOAD/!MODULE[json]", "\Overwatch\SDK\v1\ModuleLoader::__apiql_unloadModule");
}
}
/**
* @ignore */
public static function __apiql_loadModule($sql)
{}
/**
* @ignore */
public static function __apiql_unloadModule($sql)
{}
/**
* Loads specified module from path.
*
* @param string $modulePath Path to module directory
* @param string $moduleName Name of Module to be loaded
* @param string $type File extension of module (defaults to .API.php)
* @return bool Whether the module is loaded successfully or not.
*/
protected function __loadModule($modulePath, $moduleName, $type = "API.php")
{
self::$modules[$moduleName] = $module->loadModule($moduleName, $modulePath, $type);
if(is_object(self::$modules[$moduleName]) && self::$modules[$moduleName]->module_loaded())
{
\Overwatch\SDK\v1\Utility::Log("Module loaded successfully: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
return true;
} else {
\Overwatch\SDK\v1\Utility::Log("Module failed to load successfully: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
return false;
}
}
/**
* Unloads specified module from system
*
* @param string $module Module name to unload
* @return bool Whether the module is unloaded successfully
*/
protected function __unloadModule($module)
{
if(self::$moduleLoader->unloadModule($module) == true)
{
return true;
} else {
return false;
}
}
/**
* Loads all modules from specified path
*
* @param string $path Path to Overwatch Modules (defaults to OVERWATCH_MODULES)
* @return void
* @api
*/
function LoadModulesFromPath($path = OVERWATCH_MODULES)
{
$modulesArray = scandir($path);
foreach($modulesArray as $moduleName)
{
if($moduleName != "." && $moduleName != "..")
{
\Overwatch\SDK\v1\Utility::Log("Module found: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
}
if(is_dir($path."$moduleName") && $moduleName != "." && $moduleName != "..")
{
\Overwatch\SDK\v1\Utility::Log("Loading Module: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
self::__loadModule($path, $moduleName);
}
}
}
}
/**
* RPC Handler
*
* Provides methods to handle RPC requests from Overwatchd
*/
class RPC
{
/**
* Selects the client RPC type in RPC::DoCall
* @see DoCall() RPC::DoCall
*/
const OVR_RPC_TYPE_CLIENT = 1;
/**
* Selects the Server to Server RPC type in RPC::DoCall
* @see DoCall() RPC::DoCall
*/
const OVR_RPC_TYPE_S2S = 2;
/**
* Calls a function using an APIQL statement
*
* @param mixed $zPacket APIQL Statement
* @return mixed Response from API
* @see Overwatch\SDK\v1\Module Module Boilerplate (See Examples)
*/
private function __apiql_doCall($zPacket)
{
// TODO: Mangle headers to set connection-id
$response = apiql::query($zPacket);
return $response;
}
/**
* Calls a function using pure JSON-RPC
*
* @param mixed $zPacket JSON-RPC Packet
* @return mixed Response from called function
*/
private function __rpc_doCall($zPacket)
{
// TODO: Pull apart JSON packet and process through ClassName::__rpc_ClassMethod to prevent RPC calls from calling private functions
}
/**
* Call functions from RPC requests
* @param mixed $zPacket Incoming RPC Packet
* @param integer $iType RPC Call Type
* @see OVR_RPC_TYPE_CLIENT
* @see OVR_RPC_TYPE_S2S
* @return mixed Response from API
* @api
*/
protected function DoCall($zPacket, $iType = \Overwatch\SDK\v1\RPC::OVR_RPC_TYPE_CLIENT)
{}
}
?>

View File

@@ -0,0 +1,162 @@
<?php
/**
* Overwatch SDK Version 1.0
*
* This is the SDK for the Overwatch Daemon
* @package Overwatch\SDK\1.0
* @author ShadowEO <dreamcaster23@gmail.com>
* @version 1.0.0
* @copyright 2016-* Tox Communications
* @category Development
*/
namespace Overwatch\SDK\v1;
require_once("Utility.class.php");
/**
* Provides a standard implementation for managing connections in the Overwatch API server.
*/
class ConnectionManager
{
// TODO: Implement functionality, remove from core.
/** @var int Stores the amount of currently on-going connection */
protected $iCurrentConnections;
/** @var int Stores the amount of total connections in the lifetime of the server process. */
protected $iTotalConnections;
/** @var mixed Stores hierarchy of connections */
protected $ConnectionHierarchy;
/**
* Accept a connection from the ReactPHP stack
*
* Accept a connection and place it in the list of connections.
* @param \React\Socket\Connection $oConnection Connection object returned from the `on('connection')` ReactPHP event.
* @param int $iConnection Optional, used to denote the connection ID (deprecated)
* @return string Identifying Hash for Connection
* @api
*/
public function AcceptConnection($oConnection, $iConnection = null)
{}
/**
* Write packet to an accepted connection.
*
* If a connection hash is not provided, it will broadcast to all clients.
* @param string $zPacket Packet to send to socket.
* @param string $sConnectionHash Optional, Hash to identify the connection to write to.
* @api
* @return void
*/
public function Write($zPacket, $sConnectionHash = null) {}
/**
* Add a connection to the connections store
*
* @param mixed $Connection Connection Information
* @return string Connection Hash
*/
private function __addConnection($Connection)
{}
/**
* Remove a connection from the connections store
*
* @param string $sConnectionHash Connection hash to identify the connection to remove
* @return boolean
*/
private function __remConnection($sConnectionHash)
{}
/**
* Retrieve a Connection Object from the connection store for the matching connection hash
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return \React\Socket\Connection
*/
private function __getObjectForHash($sConnectionHash)
{}
/**
* Signal disconnection to a client. This will send a disconnection response JSON message for the client to process.
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return void
*/
private function __signalDisconnection($sConnectionHash)
{}
/**
* Force a client to disconnect, this preforms __signalDisconnection and then closes the connection, removing it from the connection store.
*
* @param string $sConnectionHash Connection hash to identify the connection.
* @return boolean
*/
private function __forceDisconnection($sConnectionHash)
{}
// TODO: Implement this function: public function setLastCall($zCall, $sConnectionHash) {}
}
require_once("ModuleBoilerplate.class.php");
require_once("moduleLoader.class.php");
/**
* RPC Handler
*
* Provides methods to handle RPC requests from Overwatchd
*/
class RPC
{
/**
* Selects the client RPC type in RPC::DoCall
* @see DoCall() RPC::DoCall
*/
const OVR_RPC_TYPE_CLIENT = 1;
/**
* Selects the Server to Server RPC type in RPC::DoCall
* @see DoCall() RPC::DoCall
*/
const OVR_RPC_TYPE_S2S = 2;
/**
* Calls a function using an APIQL statement
*
* @param mixed $zPacket APIQL Statement
* @return mixed Response from API
* @see Overwatch\SDK\v1\Module Module Boilerplate (See Examples)
*/
private function __apiql_doCall($zPacket)
{
// TODO: Mangle headers to set connection-id
$response = apiql::query($zPacket);
return $response;
}
/**
* Calls a function using pure JSON-RPC
*
* @param mixed $zPacket JSON-RPC Packet
* @return mixed Response from called function
*/
private function __rpc_doCall($zPacket)
{
// TODO: Pull apart JSON packet and process through ClassName::__rpc_ClassMethod to prevent RPC calls from calling private functions
}
/**
* Call functions from RPC requests
* @param mixed $zPacket Incoming RPC Packet
* @param integer $iType RPC Call Type
* @see OVR_RPC_TYPE_CLIENT
* @see OVR_RPC_TYPE_S2S
* @return mixed Response from API
* @api
*/
protected function DoCall($zPacket, $iType = \Overwatch\SDK\v1\RPC::OVR_RPC_TYPE_CLIENT)
{}
}
?>

141
sdk/v1/Utility.class.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
namespace Overwatch\SDK\v1;
/**
* Utilities
*
* Provides common utility functions such as logging.
*/
class Utility {
/**
* Used to tell the logging functions to use the info level and apply appropriate formatting.
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_INFO = 0;
/**
* Used to tell the logging functions to use the warning level and apply appropriate formatting.
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_WARN = 1;
/**
* Used to tell the logging functions to use the error level and apply appropriate formatting. Tells LogToConsole to output to stderr
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_ERROR = 2;
/**
* Used to tell the logging functions to use the debugging level and apply appropriate formatting.
*
* NOTE: <b>Messages logged using the debug label will not be written to console or file unless debug mode is enabled.</b>
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOGLEVEL_DEBUG = 3;
/**
* Used to tell the logging functions to log to stdout/stderr
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOG_CONSOLE = 4;
/**
* Used to tell the logging functions to log to a file
* @see \Overwatch\SDK\v1\Utility::Log() Logging function
*/
const OVR_UTIL_LOG_FILE = 5;
/**
* Logs messages to stdout
*
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param int $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @return void
*/
static function LogToConsole($msg, $component, $level = \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO)
{
$oTimeStamp = new \DateTime();
$sTimeStamp = $oTimeStamp->format('m-d-Y H:i:s');
$component=strtoupper($component);
switch ($level)
{
// TODO: Look into different console codes for coloring and effects
case \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG:
if(defined('\\DEBUG'))
{
echo("[$sTimeStamp][$component] $msg".PHP_EOL);
}
break;
case \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR:
error_log("[$sTimeStamp][$component] $msg".PHP_EOL);
break;
default:
echo("[$sTimeStamp][$component] $msg".PHP_EOL);
break;
}
}
/**
* Logs messages
*
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param int $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @param int $type Logging destination
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_FILE OVR_UTIL_LOG_FILE
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_CONSOLE OVR_UTIL_LOG_CONSOLE
* @param string $file File path to log to (if type is `OVR_UTIL_LOG_FILE`)
* @api
* @return void
*/
public static function Log($msg, $component, $level = \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO, $type = \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_CONSOLE, $file = null)
{
switch($type)
{
case \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_CONSOLE:
\Overwatch\SDK\v1\Utility::LogToConsole($msg, $component, $level);
break;
case \Overwatch\SDK\v1\Utility::OVR_UTIL_LOG_FILE:
if($file == NULL)
{
return false;
}
\Overwatch\SDK\v1\Utility::LogToFile($msg, $component, $level = \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO, $file);
break;
}
}
/**
* Logs specified messages to a file.
* @param string $msg Message to write to Log
* @param string $component Component that the message is coming from
* @param string $level Logging Level
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO OVR_UTIL_LOGLEVEL_INFO
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR OVR_UTIL_LOGLEVEL_ERROR
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG OVR_UTIL_LOGLEVEL_DEBUG
* @see \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_WARN OVR_UTIL_LOGLEVEL_WARN
* @param string $file File to write the message to.
* @return void
*/
protected static function LogToFile($msg, $component, $level = \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_INFO, $file)
{
$oTimeStamp = new \DateTime();
$sTimeStamp = $oTimeStamp->format('m-d-Y H:i:s');
$component=strtoupper($component);
switch($level)
{
case \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_ERROR:
error_log("[$sTimeStamp][$component] $msg".PHP_EOL, 0, $file);
break;
default:
file_put_contents($file, "[$sTimeStamp][$component] $msg".PHP_EOL, FILE_APPEND);
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Overwatch\SDK\v1;
/**
* OverwatchSDK Module Loader
*
* Abstracts the overwatch module loader into an easier to use API.
*/
class ModuleLoader
{
private $_module_list = array();
public static function startInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
public function loadModule($module, $path = OVERWATCH_MODULES, $type = "API.php") {
if($path == null)
{
$path = dirname(__FILE__);
$path_specified = false;
} else {
$path_specified = true;
}
if($module === null)
die("Error: Module is null.");
if($path_specified != true)
{
if(!file_exists("$path/modules"))
die("Error: Modules directory missing.");
if(!file_exists("$path/modules/$module"))
die("Error: Module '$module' not found.");
if(!file_exists("$path/modules/$module/$module.$type"))
die("Error: Module '$module' corrupted.");
require_once "modules/$module/$module.$type";
} else {
require_once "$path/$module/$module.$type";
}
$this->_module_list[$module]['stat'] = 'loading';
if($this->isModuleLoaded($module) == false)
{
$this->_module_list[$module]['inst'] = $module::loadInst();
} else {
\Overwatch\SDK\v1\Utility::Log("$module has already been loaded. Loader bug workaround #1.", "WORKAROUND");
}
if(!call_user_func(array($this->_module_list[$module]['inst'], 'module_loaded'))) {
$this->_module_list[$module]['stat'] = 'unloaded';
die("Error: Module '$module' didn't load.");
}
$this->_module_list[$module]['stat'] = 'loaded';
return $this->_module_list[$module]['inst'];
}
public function unloadModule($module = null) {
$path = dirname(__FILE__);
if($module === null)
die("Error: Module is null.");
if(is_array($this->_module_list[$module]) &&
is_object($this->_module_list[$module]['inst'])) {
$this->_module_list[$module]['stat'] = 'unloaded';
unset($this->_module_list[$module]['inst']);
return true;
}
return false;
}
public function listModules() {
return is_array($this->_module_list)?$this->_module_list:array();
}
public function isModuleLoaded($module) {
if($this->_module_list[$module]['stat'] == "loaded")
{
return true;
} else {
return false;
}
}
/**
* Loads all modules from specified path
*
* @param string $path Path to Overwatch Modules (defaults to OVERWATCH_MODULES)
* @return void
* @api
*/
function LoadModulesFromPath($path = OVERWATCH_MODULES)
{
$modulesArray = scandir($path);
foreach($modulesArray as $moduleName)
{
if($moduleName != "." && $moduleName != "..")
{
\Overwatch\SDK\v1\Utility::Log("Module found: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
}
if(is_dir($path."$moduleName") && $moduleName != "." && $moduleName != "..")
{
\Overwatch\SDK\v1\Utility::Log("Loading Module: $moduleName", "API-MODLOADER", \Overwatch\SDK\v1\Utility::OVR_UTIL_LOGLEVEL_DEBUG);
self::__loadModule($path, $moduleName);
}
}
}
}

7
vendor/autoload.php vendored
View File

@@ -1,7 +0,0 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit0cf5e3b00d3012d92e986b80c38c2a9f::getLoader();

View File

@@ -1 +0,0 @@
../macfja/phar-builder/bin/phar-builder

View File

@@ -1 +0,0 @@
../macfja/phar-builder/bin/phar-builder.php

View File

@@ -1 +0,0 @@
../clue/phar-composer/bin/phar-composer

View File

@@ -1 +0,0 @@
/vendor

View File

@@ -1,19 +0,0 @@
language: php
php:
- 5.3
- 5.6
- 7
- hhvm
matrix:
allow_failures:
- php: 7
- php: hhvm
install:
- composer install --prefer-source --no-interaction
script:
- phpunit --coverage-text

View File

@@ -1,133 +0,0 @@
# Changelog
## 1.0.0 (2015-11-15)
* First stable release, now following SemVer.
* Feature: Can now be installed as a `require-dev` Composer dependency and
supports running as `./vendor/bin/phar-composer`.
(#36 by @radford)
* Fix: Actually exclude `vendor/` directory. This prevents processing all
vendor files twice and reduces build time by 50%.
(#38 by @radford)
* Fix: Fix error reporting when processing invalid project paths.
(#56 by @staabm and @clue)
* Fix: Fix description of `phar-composer install` command.
(#47 by @staabm)
* Updated documentation, tests and project structure.
(#54, #57, #58 and #59 by @clue)
## 0.5.0 (2014-07-10)
* Feature: The `search` command is the new default if you do not pass any command
([#13](https://github.com/clue/phar-composer/pull/13)).
You can now use the following command to get started:
```bash
$ phar-composer
```
* Fix: Pass through STDERR output of child processes instead of aborting
([#33](https://github.com/clue/phar-composer/pull/33))
* Fix: Do not timeout when child process takes longer than 60s.
This also helps users with slower internet connections.
([#31](https://github.com/clue/phar-composer/pull/31))
* Fix: Update broken dependencies
([#18](https://github.com/clue/phar-composer/pull/18))
* Fix: Fixed an undocumented config key
([#14](https://github.com/clue/phar-composer/pull/14), thanks @mikey179)
## 0.4.0 (2013-09-12)
* Feature: New `install` command will now both build the given package and then
install it into the system-wide bin directory `/usr/local/bin` (usually already
in your `$PATH`). This works for any package name or URL just like with the
`build` command, e.g.:
```bash
$ phar-composer install phpunit/phpunit
```
After some (lengthy) build output, you should now be able to run it by just issuing:
```bash
$ phpunit
```
* Feature: New `search` command provides an interactive command line search.
It will ask for the package name and issue an search via packagist.org's API and
present a list of matching packages. So if you don't know the exact package name,
you can now use the following command:
```bash
$ phar-composer search boris
```
* Feature: Both `build` and `install` commands now also optionally accept an
additional target directory to place the resulting phar into.
## 0.3.0 (2013-08-21)
* Feature: Resulting phar files can now be executed on systems without
ext-phar (#8). This vastly improves portability for legacy setups by including
a small startup script which self-extracts the current archive into a temporary
directory.
* Feature: Resulting phar files can now be executed without the phar file name
extension. E.g. this convenient feature now allows you to move your `~demo.phar`
to `/usr/bin/demo` for easy system wide installations.
* Fix: Resolving absolute paths to `vendor/autoload.php`
## 0.2.0 (2013-08-15)
* Feature: Packages can now also be cloned from any git URLs (#9), like this:
```bash
$ phar-composer build https://github.com/clue/phar-composer.git
```
The above will clone the repository and check out the default branch.
You can also specify either a tag or branch name very similar to how composer works:
```bash
$ phar-composer build https://github.com/clue/phar-composer.git:dev-master
```
## 0.1.0 (2013-08-12)
* Feature: Packages listed on packagist.org can now automatically be downloaded and installed
prior to generating phar (#7), like this:
```bash
$ phar-composer build clue/phar-composer
```
The above will download and install the latest stable tagged release (if any).
You can also specify a tagged version like this:
```bash
$ phar-composer build clue/phar-composer:0.1.*
```
Or you can specify to install the head of a given branch like this:
```bash
$ phar-composer build clue/phar-composer:dev-master
```
## 0.0.2 (2013-05-25)
* Feature: Bundle complete project directories
## 0.0.1 (2013-05-18)
* First tagged release

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013 Christian Lück
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.

View File

@@ -1,240 +0,0 @@
# clue/phar-composer [![Build Status](https://travis-ci.org/clue/phar-composer.png?branch=master)](https://travis-ci.org/clue/phar-composer)
Simple phar creation for any project managed via composer.
It takes your existing project's `composer.json` and builds an executable phar
for your project among with its bundled dependencies.
* Create a single executable phar archive, including its dependencies (i.e. vendor directory included)
* Automated build process
* Zero additional configuration
**Table of contents**
* [Usage](#usage)
* [phar-composer](#phar-composer)
* [phar-composer build](#phar-composer-build)
* [phar-composer install](#phar-composer-install)
* [phar-composer search](#phar-composer-search)
* [Install](#install)
* [As a phar (recommended)](#as-a-phar-recommended)
* [Updating phar](#updating-phar)
* [Installation using Composer](#installation-using-composer)
* [Updating dependency](#updating-dependency)
* [Manual Installation from Source](#manual-installation-from-source)
* [Updating manually](#updating-manually)
* [License](#license)
## Usage
Once clue/phar-composer is [installed](#install), you can use it via command line like this.
### phar-composer
This tool supports several sub-commands. To get you started, you can now use the following simple command:
```bash
$ phar-composer
```
This will actually execute the `search` command that allows you to interactively search and build any package
listed on packagist (see below description of the [search command](#phar-composer-search) for more details).
### phar-composer build
The `build` command can be used to build an executable single-file phar (php archive) for any project
managed by composer:
```bash
$ phar-composer build ~/path/to/your/project
```
The second argument can be pretty much everything that can be resolved to a valid project managed by composer.
Besides creating phar archives for locally installed packages like above, you can also easily download and
bundle packages from packagist.org like this:
```bash
$ phar-composer build d11wtq/boris
```
The above will download and install the latest stable tagged release (if any).
You can also specify a tagged version like this:
```bash
$ phar-composer build clue/phar-composer:~1.0
```
Or you can specify to install the head of a given branch like this:
```bash
$ phar-composer build clue/phar-composer:dev-master
```
A similar syntax can be used to clone a package from any git URL. This is particularly
useful for private packages or temporary git clones not otherwise listed on packagist:
```bash
$ phar-composer build https://github.com/composer/composer.git
```
The above will clone the repository and check out the default branch.
Again, you can specify either a tag or branch name very similar to how composer works:
```bash
$ phar-composer build https://github.com/composer/composer.git:dev-master
```
### phar-composer install
The `install` command will both build the given package and then
install it into the system-wide bin directory `/usr/local/bin` (usually already
in your `$PATH`). This works for any package name or URL just like with the
`build` command, e.g.:
```bash
$ phar-composer install phpunit/phpunit
```
After some (lengthy) build output, you should now be able to run it by just issuing:
```bash
$ phpunit
```
> In essence, the `install` command will basically just issue a `build` and then
`sudo mv $target.phar /usr/local/bin/$target`. It will ask you for your sudo password
when necessary, so it's not needed (and in fact not *recommended*) to run the whole
comamnd via `sudo`.
### phar-composer search
The `search` command provides an interactive command line search.
It will ask for the package name and issue an search via packagist.org's API and
present a list of matching packages. So if you don't know the exact package name,
you can use the following command:
```bash
$ phar-composer search boris
```
It uses an interactive command line menu to ask you for the matching package name,
its version and will then offer you to either `build` or `install` it.
## Install
You can grab a copy of clue/phar-composer in either of the following ways.
### As a phar (recommended)
You can simply download a pre-compiled and ready-to-use version as a Phar
to any directory.
Simply download the latest `phar-composer.phar` file from our
[releases page](https://github.com/clue/phar-composer/releases):
[Latest release](https://github.com/clue/phar-composer/releases/latest)
That's it already. You can now verify everything works by running this:
```bash
$ cd ~/Downloads
$ php phar-composer.phar --version
```
The above usage examples assume you've installed phar-composer system-wide to your $PATH (recommended),
so you have the following options:
1. Only use phar-composer locally and adjust the usage examples: So instead of
running `$ phar-composer --version`, you have to type `$ php phar-composer.phar --version`.
2. Use phar-composer's `install` command to install itself to your $PATH by running:
```bash
$ php phar-composer.phar install clue/phar-composer
```
3. Or you can manually make the `phar-composer.phar` executable and move it to your $PATH by running:
```bash
$ chmod 755 phar-composer.phar
$ sudo mv phar-composer.phar /usr/local/bin/phar-composer
```
If you have installed phar-composer system-wide, you can now verify everything works by running:
```bash
$ phar-composer --version
```
#### Updating phar
There's no separate `update` procedure, simply download the latest release again
and overwrite the existing phar.
Again, if you have already installed phar-composer system-wide, this is as easy as
running a self-installation like this:
```bash
$ phar-composer install clue/phar-composer
```
### Installation using Composer
Alternatively, you can also install phar-composer as part of your development dependencies.
You will likely want to use the `require-dev` section to exclude phar-composer in your production environment.
You can either modify your `composer.json` manually or run the following command to include the latest tagged release:
```bash
$ composer require --dev clue/phar-composer
```
Now you should be able to invoke the following command in your project root:
```bash
$ ./vendor/bin/phar-composer --version
```
> Note: You should only invoke and rely on the main phar-composer bin file.
Installing this project as a non-dev dependency in order to use its
source code as a library is *not supported*.
#### Updating dependency
Just run `composer update clue/phar-composer` to update to the latest release.
### Manual Installation from Source
This project requires PHP 5.3+ and Composer:
```bash
$ git clone https://github.com/clue/phar-composer.git
$ cd phar-composer
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar install
```
You can now verify everything works by running phar-composer like this:
```bash
$ php bin/phar-composer --version
```
Optionally, you can now build the above mentioned `phar-composer.phar` yourself by issuing:
```bash
$ php bin/phar-composer build
```
Optionally, you can now follow the above instructions for a [system-wide installation](#as-a-phar-recommended).
#### Updating manually
```bash
$ git pull
$ php composer.phar install
```
## License
MIT

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env php
<?php
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
fwrite(STDERR, 'ERROR: Composer dependencies not properly set up! Run "composer install" or see README.md for more details' . PHP_EOL);
exit(1);
}
$app = new Clue\PharComposer\App();
$app->run();

View File

@@ -1,29 +0,0 @@
{
"name": "clue/phar-composer",
"description": "Simple phar creation for your projects managed via composer",
"keywords": ["executable phar", "build process", "bundle dependencies"],
"homepage": "https://github.com/clue/phar-composer",
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"email": "christian@lueck.tv"
}
],
"require": {
"herrera-io/box": "~1.5",
"symfony/console": "~2.1",
"symfony/finder": "~2.1",
"symfony/process": "~2.1",
"knplabs/packagist-api": "~1.0"
},
"autoload": {
"psr-0": {"Clue": "src/"}
},
"bin": ["bin/phar-composer"],
"extra": {
"phar": {
"bundler": "composer"
}
}
}

View File

@@ -1,702 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "fa5d19b9023764157514f2b1b36431ff",
"packages": [
{
"name": "doctrine/inflector",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/54b8333d2a5682afdc690060c1cf384ba9f47f08",
"reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Inflector\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/",
"role": "Creator"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "http://jmsyst.com",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"inflection",
"pluarlize",
"singuarlize",
"string"
],
"time": "2013-01-10 21:49:15"
},
{
"name": "guzzle/common",
"version": "v3.8.0",
"target-dir": "Guzzle/Common",
"source": {
"type": "git",
"url": "https://github.com/guzzle/common.git",
"reference": "eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/common/zipball/eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e",
"reference": "eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/event-dispatcher": ">=2.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Common": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Common libraries used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"collection",
"common",
"event",
"exception"
],
"time": "2013-12-05 23:39:20"
},
{
"name": "guzzle/http",
"version": "v3.8.0",
"target-dir": "Guzzle/Http",
"source": {
"type": "git",
"url": "https://github.com/guzzle/http.git",
"reference": "b497e6b6a5a85751ae0c6858d677f7c4857dd171"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/http/zipball/b497e6b6a5a85751ae0c6858d677f7c4857dd171",
"reference": "b497e6b6a5a85751ae0c6858d677f7c4857dd171",
"shasum": ""
},
"require": {
"guzzle/common": "self.version",
"guzzle/parser": "self.version",
"guzzle/stream": "self.version",
"php": ">=5.3.2"
},
"suggest": {
"ext-curl": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Http": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "HTTP libraries used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"client",
"curl",
"http",
"http client"
],
"time": "2013-12-04 22:21:25"
},
{
"name": "guzzle/parser",
"version": "v3.8.0",
"target-dir": "Guzzle/Parser",
"source": {
"type": "git",
"url": "https://github.com/guzzle/parser.git",
"reference": "77cae6425bc3466e1e47bdf6d2eebc15a092dbcc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/parser/zipball/77cae6425bc3466e1e47bdf6d2eebc15a092dbcc",
"reference": "77cae6425bc3466e1e47bdf6d2eebc15a092dbcc",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Parser": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Interchangeable parsers used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"URI Template",
"cookie",
"http",
"message",
"url"
],
"time": "2013-10-24 00:04:09"
},
{
"name": "guzzle/stream",
"version": "v3.8.0",
"target-dir": "Guzzle/Stream",
"source": {
"type": "git",
"url": "https://github.com/guzzle/stream.git",
"reference": "a86111d9ac7db31d65a053c825869409fe8fc83f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/stream/zipball/a86111d9ac7db31d65a053c825869409fe8fc83f",
"reference": "a86111d9ac7db31d65a053c825869409fe8fc83f",
"shasum": ""
},
"require": {
"guzzle/common": "self.version",
"php": ">=5.3.2"
},
"suggest": {
"guzzle/http": "To convert Guzzle request objects to PHP streams"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Stream": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle stream wrapper component",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"component",
"stream"
],
"time": "2013-07-30 22:07:23"
},
{
"name": "herrera-io/box",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/herrera-io/php-box.git",
"reference": "596278d729b45ba2dab3f53897d62ac51e0db909"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/herrera-io/php-box/zipball/596278d729b45ba2dab3f53897d62ac51e0db909",
"reference": "596278d729b45ba2dab3f53897d62ac51e0db909",
"shasum": ""
},
"require": {
"ext-phar": "*",
"phine/path": "~1.0",
"php": ">=5.3.3"
},
"require-dev": {
"herrera-io/annotations": "~1.0",
"herrera-io/phpunit-test-case": "1.*",
"mikey179/vfsstream": "1.1.0",
"phpseclib/phpseclib": "~0.3",
"phpunit/phpunit": "3.7.*"
},
"suggest": {
"herrera-io/annotations": "For compacting annotated docblocks.",
"phpseclib/phpseclib": "For verifying OpenSSL signed phars without the phar extension."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Herrera\\Box": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A library for simplifying the PHAR build process.",
"homepage": "http://herrera-io.github.com/php-box",
"keywords": [
"phar"
],
"time": "2013-11-09 17:22:29"
},
{
"name": "knplabs/packagist-api",
"version": "v1.1",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/packagist-api.git",
"reference": "5d46003cfb1aca37efc84e8a0f1e8ba276a8245e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/packagist-api/zipball/5d46003cfb1aca37efc84e8a0f1e8ba276a8245e",
"reference": "5d46003cfb1aca37efc84e8a0f1e8ba276a8245e",
"shasum": ""
},
"require": {
"doctrine/inflector": "~1.0",
"guzzle/http": "~3.0",
"php": ">=5.3.2"
},
"require-dev": {
"phpspec/php-diff": "*@dev",
"phpspec/phpspec2": "1.0.*@dev"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-0": {
"Packagist\\Api\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "KnpLabs Team",
"homepage": "http://knplabs.com"
}
],
"description": "Packagist API client.",
"homepage": "http://knplabs.com",
"keywords": [
"api",
"composer",
"packagist"
],
"time": "2013-11-22 09:55:31"
},
{
"name": "phine/exception",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/phine/lib-exception.git",
"reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phine/lib-exception/zipball/150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
"reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"league/phpunit-coverage-listener": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Phine\\Exception": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A PHP library for improving the use of exceptions.",
"homepage": "https://github.com/phine/lib-exception",
"keywords": [
"exception"
],
"time": "2013-08-27 17:43:25"
},
{
"name": "phine/path",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/phine/lib-path.git",
"reference": "cbe1a5eb6cf22958394db2469af9b773508abddd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phine/lib-path/zipball/cbe1a5eb6cf22958394db2469af9b773508abddd",
"reference": "cbe1a5eb6cf22958394db2469af9b773508abddd",
"shasum": ""
},
"require": {
"phine/exception": "~1.0",
"php": ">=5.3.3"
},
"require-dev": {
"league/phpunit-coverage-listener": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Phine\\Path": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A PHP library for improving the use of file system paths.",
"homepage": "https://github.com/phine/lib-path",
"keywords": [
"file",
"path",
"system"
],
"time": "2013-10-15 22:58:04"
},
{
"name": "symfony/console",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "4c1ed2ff514bd85ee186eebb010ccbdeeab05af7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/4c1ed2ff514bd85ee186eebb010ccbdeeab05af7",
"reference": "4c1ed2ff514bd85ee186eebb010ccbdeeab05af7",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/event-dispatcher": "~2.1"
},
"suggest": {
"symfony/event-dispatcher": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2014-01-01 08:14:50"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.4.1",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "e3ba42f6a70554ed05749e61b829550f6ac33601"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/e3ba42f6a70554ed05749e61b829550f6ac33601",
"reference": "e3ba42f6a70554ed05749e61b829550f6ac33601",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/dependency-injection": "~2.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
"time": "2013-12-28 08:12:03"
},
{
"name": "symfony/finder",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
"reference": "6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Finder/zipball/6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a",
"reference": "6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Finder\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
"time": "2014-01-01 08:14:50"
},
{
"name": "symfony/process",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "58fdccb311e44f28866f976c2d7b3227e9f713db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/58fdccb311e44f28866f976c2d7b3227e9f713db",
"reference": "58fdccb311e44f28866f976c2d7b3227e9f713db",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Process\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
"time": "2014-01-05 02:10:50"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": [
],
"platform": [
],
"platform-dev": [
]
}

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="PharComposer Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -1,64 +0,0 @@
<?php
namespace Clue\PharComposer;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputInterface;
class App extends BaseApplication
{
private $isDefault = false;
public function __construct()
{
parent::__construct('phar-composer', '@git_tag@');
$this->add(new Command\Build());
$this->add(new Command\Search());
$this->add(new Command\Install());
// GUI feature disabled for now, see #35
// $this->add(new Command\Gui());
}
/**
* Gets the name of the command based on input.
*
* @param InputInterface $input The input interface
*
* @return string The command name
*/
protected function getCommandName(InputInterface $input)
{
if ($input->getFirstArgument() === null && !$input->hasParameterOption(array('--help', '-h'))) {
$this->isDefault = true;
return $this->getDefaultCommandName();
}
return parent::getCommandName($input);
}
private function getDefaultCommandName()
{
$gui = $this->has('gui') ? $this->get('gui') : null;
if ($gui instanceof Command\Gui && $gui->hasZenity()) {
return 'gui';
}
return 'search';
}
/**
* Overridden so that the application doesn't expect the command
* name to be the first argument.
*/
public function getDefinition()
{
$inputDefinition = parent::getDefinition();
if ($this->isDefault) {
// clear out the normal first argument, which is the command name
$inputDefinition->setArguments();
}
return $inputDefinition;
}
}

View File

@@ -1,48 +0,0 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Clue\PharComposer\Phar\Packager;
class Build extends Command
{
protected function configure()
{
$this->setName('build')
->setDescription('Build phar for the given composer project')
->addArgument('path', InputArgument::OPTIONAL, 'Path to project directory or composer.json', '.')
->addArgument('target', InputArgument::OPTIONAL, 'Path to write phar output to (defaults to project name)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput(function ($line) use ($output) {
$output->write($line);
});
$packager->coerceWritable();
$pharer = $packager->getPharer($input->getArgument('path'));
$target = $input->getArgument('target');
if ($target !== null) {
$pharer->setTarget($target);
}
$pharer->build();
}
}

View File

@@ -1,283 +0,0 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Packagist\Api\Client;
use Packagist\Api\Result\Result;
use Packagist\Api\Result\Package;
use Packagist\Api\Result\Package\Version;
use Clue\PharComposer\Phar\Packager;
use React\EventLoop\Factory;
use Clue\Zenity\React\Launcher;
use Clue\Zenity\React\Builder;
class Gui extends Command
{
protected function configure()
{
$this->setName('gui')
->setDescription('Interactive GUI (requires Zenity, likely only on Linux/etc.)');
}
public function hasZenity()
{
return $this->hasBin('zenity');
}
private function hasBin($bin)
{
foreach (explode(PATH_SEPARATOR, getenv('PATH')) as $path) {
$path = rtrim($path, '/') . '/' . $bin;
if (file_exists($path)) {
return true;
}
}
return false;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$loop = Factory::create();
$launcher = new Launcher($loop);
$builder = new Builder($launcher);
$packager = new Packager();
$packager->setOutput(function ($line) use ($builder) {
$builder->info(strip_tags($line))->waitReturn();
});
$packager->coerceWritable(0);
foreach (array('gksudo', 'kdesudo', 'cocoasudo', 'sudo') as $bin) {
if ($this->hasBin($bin)) {
$packager->setBinSudo($bin);
break;
}
}
$packager->setOutput($output);
$menu = $builder->listMenu(
array(
'search' => 'Search package online',
'local' => 'Select local package',
'url' => 'Build from git-Repository',
'about' => 'About clue/phar-composer'
),
'Action'
);
$menu->setTitle('clue/phar-composer');
$menu->setWindowIcon('info');
$menu->setCancelLabel('Quit');
$selection = $menu->waitReturn();
if ($selection === 'search') {
$pharer = $this->doSearch($builder, $packager);
} elseif ($selection === 'local') {
do {
$dir = $builder->directorySelection()->waitReturn();
if ($dir === false) {
return;
}
try {
$pharer = $packager->getPharer($dir);
break;
}
catch (\Exception $e) {
$builder->error('Could not initialize composer package:' . PHP_EOL . PHP_EOL . $e->getMessage())->waitReturn();
}
} while(true);
} elseif ($selection === 'url') {
$pharer = $this->doUrl($builder, $packager);
} else {
return;
}
if ($pharer === null) {
return;
}
$action = $builder->listMenu(
array(
'build' => 'Build project',
'install' => 'Install project system-wide'
),
'Action for "' . $pharer->getPackageRoot()->getName() .'"' /*,
'Quit' */
)->waitReturn();
if ($action === 'build') {
$this->doBuild($builder, $packager, $pharer);
} elseif ($action ==='install') {
$this->doInstall($builder, $packager, $pharer);
} else {
return;
}
$builder->info('Successfully built ' . $pharer->getTarget() . '!')->waitReturn();
}
protected function doSearch(Builder $builder, Packager $packager)
{
$oldname = null;
do {
$dialog = $builder->entry('Search (partial) project name', $oldname);
$dialog->setTitle('Search project name');
$name = $dialog->waitReturn();
if ($name === false) {
return;
}
$oldname = $name;
$pulsate = $builder->pulsate('Searching for "' . $name . '"...');
$pulsate->setNoCancel(true);
$pulsate->run();
$packagist = new Client();
$choices = array();
foreach ($packagist->search($name) as $package) {
/* @var $package Result */
$choices[$package->getName()] = array(
$package->getName(),
mb_strimwidth($package->getDescription(), 0, 80, '…', 'utf-8'),
$package->getDownloads()
);
}
$pulsate->close();
if (!$choices) {
$builder->warning('No package matching "' . $name .'" found!')->waitReturn();
$name = false;
continue;
}
$table = $builder->table($choices, array('Name', 'Description', 'Downloads'), 'Select matching package');
$table->setTitle('Select matching package');
$table->setCancelLabel('Back to Search');
$table->setWidth(1000);
$table->setHeight(600);
$name = $table->waitReturn();
} while (is_bool($name));
$pulsate = $builder->pulsate('Selected <info>' . $name . '</info>, listing versions...');
$pulsate->setNoCancel(true);
$pulsate->run();
$package = $packagist->get($name);
/* @var $package Package */
$choices = array();
foreach ($package->getVersions() as $version) {
/* @var $version Version */
$time = new \DateTime($version->getTime());
$time = $time->format('Y-m-d H:i:s');
$bin = $version->getBin();
if ($bin) {
$bin = '☑ ' . array_shift($bin);
} else {
$bin = '☐ no executable bin';
}
$choices[$version->getVersion()] = array(
$version->getVersion(),
$time,
$bin
);
}
$pulsate->close();
if (!$choices) {
$builder->warning('No versions for package "' . $name .'" found!')->waitReturn();
return;
}
$dialog = $builder->table($choices, array('Version', 'Date', 'Binary'), 'Select available version');
$dialog->setWidth(800);
$dialog->setHeight(300);
$version = $dialog->waitReturn();
if (is_bool($version)) {
return;
}
$pulsate = $builder->pulsate('Installing to temporary directory...')->run();
$pharer = $packager->getPharer($name, $version);
$pulsate->close();
return $pharer;
}
protected function doUrl(Builder $builder, Packager $packager)
{
do {
$url = $builder->entry('Git URL to clone')->waitReturn();
if ($url === false) {
return;
}
$pulsate = $builder->pulsate('Cloning and installing from git...')->run();
try {
$pharer = $packager->getPharer($url);
$pulsate->close();
return $pharer;
}
catch (\Exception $e) {
$pulsate->close();
$builder->error('Unable to clone repository:' . PHP_EOL . PHP_EOL . $e->getMessage())->waitReturn();
}
} while(true);
}
protected function doInstall(Builder $builder, Packager $packager, PharComposer $pharer)
{
$pulsate = $builder->pulsate('Installing...')->run();
$path = $packager->getSystemBin($pharer);
$packager->install($pharer, $path);
$pulsate->close();
}
protected function doBuild(Builder $builder, Packager $packager, PharComposer $pharer)
{
$pulsate = $builder->pulsate('Waiting for target file name...')->run();
$save = $builder->fileSave('Location to write file to', $pharer->getTarget());
$target = $save->waitReturn();
if ($target === false) {
return;
}
$pulsate->close();
$pulsate = $builder->pulsate('Building target file...')->run();
$pharer->setTarget($target);
$pharer->build();
$pulsate->close();
}
}

View File

@@ -1,44 +0,0 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Command\Command;
use Clue\PharComposer\Phar\Packager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\DialogHelper;
class Install extends Command
{
protected function configure()
{
$this->setName('install')
->setDescription('Install phar into system wide binary directory')
->addArgument('name', InputArgument::OPTIONAL, 'Project name or path', '.')
->addArgument('path', InputArgument::OPTIONAL, 'Path to install to', '/usr/local/bin');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput($output);
$packager->coerceWritable();
$pharer = $packager->getPharer($input->getArgument('name'));
$path = $packager->getSystemBin($pharer, $input->getArgument('path'));
if (is_file($path)) {
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
if (!$dialog->askConfirmation($output, 'Overwrite existing file <info>' . $path . '</info>? [y] > ')) {
$output->writeln('Aborting');
return;
}
}
$packager->install($pharer, $path);
}
}

View File

@@ -1,153 +0,0 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Packagist\Api\Client;
use Packagist\Api\Result\Result;
use Packagist\Api\Result\Package;
use Packagist\Api\Result\Package\Version;
use Clue\PharComposer\Phar\Packager;
class Search extends Command
{
protected function configure()
{
$this->setName('search')
->setDescription('Interactive search for project name')
->addArgument('name', InputArgument::OPTIONAL, 'Project name or path', null);
}
protected function select(OutputInterface $output, $label, array $choices, $abortable = null)
{
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
if (!$choices) {
$output->writeln('<error>No matching packages found</error>');
return;
}
// TODO: skip dialog, if exact match
if ($abortable === true) {
$abortable = '<hl>Abort</hl>';
} elseif ($abortable === false) {
$abortable = null;
}
$select = array_merge(array(0 => $abortable), array_values($choices));
if ($abortable === null) {
unset($select[0]);
}
$index = $dialog->select($output, $label, $select);
if ($index == 0) {
return null;
}
$indices = array_keys($choices);
return $indices[$index - 1];
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput($output);
$packager->coerceWritable();
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
$name = $input->getArgument('name');
do {
if ($name === null) {
// ask for input
$name = $dialog->ask($output, 'Enter (partial) project name > ');
} else {
$output->writeln('Searching for <info>' . $name . '</info>...');
}
$packagist = new Client();
$choices = array();
foreach ($packagist->search($name) as $package) {
/* @var $package Result */
$label = str_pad($package->getName(), 39) . ' ';
$label = str_replace($name, '<info>' . $name . '</info>', $label);
$label .= $package->getDescription();
$label .= ' (⤓' . $package->getDownloads() . ')';
$choices[$package->getName()] = $label;
}
$name = $this->select($output, 'Select matching package', $choices, 'Start new search');
} while ($name === null);
$output->writeln('Selected <info>' . $name . '</info>, listing versions...');
$package = $packagist->get($name);
/* @var $package Package */
$choices = array();
foreach ($package->getVersions() as $version) {
/* @var $version Version */
$label = $version->getVersion();
$bin = $version->getBin();
if ($bin === null) {
$label .= ' (<error>no executable bin</error>)';
} else {
$label .= ' (☑ executable bin)';
}
$choices[$version->getVersion()] = $label;
}
$version = $this->select($output, 'Select available version', $choices);
$action = $this->select(
$output,
'Action',
array(
'build' => 'Build project',
'install' => 'Install project system-wide'
),
'Quit'
);
if ($action === null) {
return;
}
$pharer = $packager->getPharer($name, $version);
if ($action === 'install') {
$path = $packager->getSystemBin($pharer);
$packager->install($pharer, $path);
} else {
$pharer->build();
}
}
}

View File

@@ -1,39 +0,0 @@
<?php
namespace Clue\PharComposer;
/**
* Interface for logging outout.
*
* TODO: should be used in the Command classes as well
*/
class Logger
{
private $output = true;
/**
* set output function to use to output log messages
*
* @param callable|boolean $output callable that receives a single $line argument or boolean echo
*
* TODO: think about whether this should be a constructor instead
*/
public function setOutput($output)
{
$this->output = $output;
}
public function log($message)
{
$this->output($message . PHP_EOL);
}
private function output($message)
{
if ($this->output === true) {
echo $message;
} elseif ($this->output !== false) {
call_user_func($this->output, $message);
}
}
}

View File

@@ -1,114 +0,0 @@
<?php
namespace Clue\PharComposer\Package;
/**
* Provides access to pathes defined in package autoload configuration.
*
* @see Package
*/
class Autoload
{
/**
* package autoload definition
*
* @type array
*/
private $autoload;
public function __construct(array $autoload)
{
$this->autoload = $autoload;
}
/**
* returns all pathes defined by PSR-0
*
* @return string[]
*/
public function getPsr0()
{
if (!isset($this->autoload['psr-0'])) {
return array();
}
$resources = array();
foreach ($this->autoload['psr-0'] as $namespace => $paths) {
foreach($this->correctSinglePath($paths) as $path) {
// TODO: this is not correct actually... should work for most repos nevertheless
// TODO: we have to take target-dir into account
$resources[] = $this->buildNamespacePath($namespace, $path);
}
}
return $resources;
}
/**
* corrects a single path into a list of pathes
*
* PSR autoloader may define a single or multiple paths.
*
* @param string|string[] $paths
* @return string[]
*/
private function correctSinglePath($paths)
{
if (is_array($paths)) {
return $paths;
}
return array($paths);
}
/**
* builds namespace path from given namespace and given path
*
* @param string $namespace
* @param string $path
* @return string
*/
private function buildNamespacePath($namespace, $path)
{
if ($namespace === '') {
return $path;
}
$namespace = str_replace('\\', '/', $namespace);
if ($path === '') {
// namespace in project root => namespace is path
return $namespace;
}
// namespace in sub-directory => add namespace to path
return rtrim($path, '/') . '/' . $namespace;
}
/**
* returns list of class file resources
*
* @return string[]
*/
public function getClassmap()
{
if (!isset($this->autoload['classmap'])) {
return array();
}
return $this->autoload['classmap'];
}
/**
* returns list of files defined in autoload
*
* @return string[]
*/
public function getFiles()
{
if (!isset($this->autoload['files'])) {
return array();
}
return $this->autoload['files'];
}
}

View File

@@ -1,106 +0,0 @@
<?php
namespace Clue\PharComposer\Package;
use Symfony\Component\Finder\Finder;
use Clue\PharComposer\Logger;
/**
* A bundle represents all resources from a package that should be bundled into
* the target phar.
*/
class Bundle implements \IteratorAggregate
{
/**
* list of resources in this bundle
*
* @type array
*/
private $resources = array();
/**
* create bundle from given package
*
* @param Package $package
* @param Logger $logger
* @return Bundle
*/
public static function from(Package $package, Logger $logger)
{
return $package->getBundler($logger)->bundle();
}
/**
* add given file to bundle
*
* @param string $file
* @return Bundle
*/
public function addFile($file)
{
$this->resources[] = $file;
return $this;
}
/**
* add given directory to bundle
*
* @param Finder $dir
* @return Bundle
*/
public function addDir(Finder $dir)
{
$this->resources[] = $dir;
return $this;
}
/**
* checks if a bundle contains given resource
*
* @param string $resource
* @return bool
*/
public function contains($resource)
{
foreach ($this->resources as $containedResource) {
if (is_string($containedResource) && $containedResource == $resource) {
return true;
}
if ($containedResource instanceof Finder && $this->directoryContains($containedResource, $resource)) {
return true;
}
}
return false;
}
/**
* checks if given directory contains given resource
*
* @param Finder $dir
* @param string $resource
* @return bool
*/
private function directoryContains(Finder $dir, $resource)
{
foreach ($dir as $containedResource) {
/* @var $containedResource \SplFileInfo */
if (substr($containedResource->getRealPath(), 0, strlen($resource)) == $resource) {
return true;
}
}
return false;
}
/**
* returns list of resources
*
* @return \Traversable
*/
public function getIterator()
{
return new \ArrayIterator($this->resources);
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
/**
* The Bundler is responsible for creating a Bundle containing all files which belong to a given package
*/
interface BundlerInterface
{
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle();
}

View File

@@ -1,50 +0,0 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Package;
use Symfony\Component\Finder\Finder;
/**
* The default Bundler instance which bundles the whole package directory
*/
class Complete implements BundlerInterface
{
/**
* package the bundler is for
*
* @type Package
*/
private $package;
/**
*
* @type Logger
*/
private $logger;
public function __construct(Package $package, Logger $logger)
{
$this->package = $package;
$this->logger = $logger;
}
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle()
{
$bundle = new Bundle();
$iterator = Finder::create()
->files()
->ignoreVCS(true)
->filter($this->package->getBlacklistFilter())
->exclude($this->package->getPathVendorRelative())
->in($this->package->getDirectory());
$this->logger->log(' Adding whole project directory "' . $this->package->getDirectory() . '"');
return $bundle->addDir($iterator);
}
}

View File

@@ -1,97 +0,0 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Package;
use Clue\PharComposer\Package\Autoload;
use Symfony\Component\Finder\Finder;
/**
* Only bundle files explicitly defined in this package's bin and autoload section
*/
class Explicit implements BundlerInterface
{
/**
* package the bundler is for
*
* @type Package
*/
private $package;
/**
*
* @type Logger
*/
private $logger;
public function __construct(Package $package, Logger $logger)
{
$this->package = $package;
$this->logger = $logger;
}
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle()
{
$bundle = new Bundle();
$this->bundleBins($bundle);
$autoload = $this->package->getAutoload();
$this->bundlePsr0($bundle, $autoload);
$this->bundleClassmap($bundle, $autoload);
$this->bundleFiles($bundle, $autoload);
return $bundle;
}
private function bundleBins(Bundle $bundle)
{
foreach ($this->package->getBins() as $bin) {
$this->logger->log(' adding "' . $bin . '"');
$bundle->addFile($bin);
}
}
private function bundlePsr0(Bundle $bundle, Autoload $autoload)
{
foreach ($autoload->getPsr0() as $path) {
$this->addDir($bundle, $path);
}
}
private function bundleClassmap(Bundle $bundle, Autoload $autoload)
{
foreach($autoload->getClassmap() as $path) {
$this->addFile($bundle, $path);
}
}
private function bundleFiles(Bundle $bundle, Autoload $autoload)
{
foreach($autoload->getFiles() as $path) {
$this->addFile($bundle, $path);
}
}
private function addFile(Bundle $bundle, $file)
{
$this->logger->log(' adding "' . $file . '"');
$bundle->addFile($this->package->getAbsolutePath($file));
}
private function addDir(Bundle $bundle, $dir)
{
$dir = $this->package->getAbsolutePath(rtrim($dir, '/') . '/');
$this->logger->log(' adding "' . $dir . '"');
$bundle->addDir(Finder::create()
->files()
//->filter($this->getBlacklistFilter())
->ignoreVCS(true)
->in($dir));
}
}

View File

@@ -1,168 +0,0 @@
<?php
namespace Clue\PharComposer\Package;;
use Symfony\Component\Finder\SplFileInfo;
use Clue\PharComposer\Package\Bundler\Explicit as ExplicitBundler;
use Clue\PharComposer\Package\Bundler\Complete as CompleteBundler;
use Clue\PharComposer\Package\Autoload;
use Clue\PharComposer\Logger;
/**
* The package represents either the main/root package or one of the vendor packages.
*/
class Package
{
/**
* Instantiate package
*
* @param array $package package information (parsed composer.json)
* @param string $directory base directory of this package
*/
public function __construct(array $package, $directory)
{
$this->package = $package;
$this->directory = $directory;
}
/**
* get package name as defined in composer.json
*
* @return string
*/
public function getName()
{
return isset($this->package['name']) ? $this->package['name'] : 'unknown';
}
/**
* Get path to vendor directory (relative to package directory)
*
* @return string
*/
public function getPathVendorRelative()
{
$vendor = 'vendor';
if (isset($this->package['config']['vendor-dir'])) {
$vendor = $this->package['config']['vendor-dir'];
}
return $vendor;
}
/**
* Get absolute path to vendor directory
*
* @return string
*/
public function getPathVendor()
{
return $this->getAbsolutePath($this->getPathVendorRelative() . '/');
}
/**
* Get package directory (the directory containing its composer.json)
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Get Bundler instance to bundle this package
*
* @param Logger $logger
* @return BundlerInterface
*/
public function getBundler(Logger $logger)
{
$bundlerName = 'complete';
if (isset($this->package['extra']['phar']['bundler'])) {
$bundlerName = $this->package['extra']['phar']['bundler'];
}
if ($bundlerName === 'composer') {
return new ExplicitBundler($this, $logger);
} elseif ($bundlerName === 'complete') {
return new CompleteBundler($this, $logger);
} else {
$logger->log('Invalid bundler "' . $bundlerName . '" specified in package "' . $this->getName() . '", will fall back to "complete" bundler');
return new CompleteBundler($this, $logger);
}
}
/**
* Get Autoload instance containing all autoloading information
*
* Only used for ExplicitBundler at the moment.
*
* @return Autoload
*/
public function getAutoload()
{
return new Autoload(isset($this->package['autoload']) ? $this->package['autoload'] : array());
}
/**
* Get list of files defined as "bin" (absolute paths)
*
* @return string[]
*/
public function getBins()
{
if (!isset($this->package['bin'])) {
return array();
}
$bins = array();
foreach ($this->package['bin'] as $bin) {
$bins []= $this->getAbsolutePath($bin);
}
return $bins;
}
/**
* Get blacklisted files which are not to be included
*
* Hardcoded to exclude composer.phar and phar-composer.phar at the moment.
*
* @return string[]
*/
public function getBlacklist()
{
return array(
$this->getAbsolutePath('composer.phar'),
$this->getAbsolutePath('phar-composer.phar')
);
}
/**
* Gets a filter function to exclude blacklisted files
*
* Only used for CompleteBundler at the moment
*
* @return Closure
* @uses self::getBlacklist()
*/
public function getBlacklistFilter()
{
$blacklist = $this->getBlacklist();
return function (SplFileInfo $file) use ($blacklist) {
return in_array($file->getPathname(), $blacklist) ? false : null;
};
}
/**
* Get absolute path for the given package-relative path
*
* @param string $path
* @return string
*/
public function getAbsolutePath($path)
{
return $this->directory . ltrim($path, '/');
}
}

View File

@@ -1,313 +0,0 @@
<?php
namespace Clue\PharComposer\Phar;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Console\Output\OutputInterface;
class Packager
{
const PATH_BIN = '/usr/local/bin';
private $output;
private $binSudo = 'sudo';
public function __construct()
{
$this->setOutput(true);
}
private function log($message)
{
$fn = $this->output;
$fn($message . PHP_EOL);
}
public function setBinSudo($bin)
{
$this->binSudo = $bin;
}
public function setOutput($fn)
{
if ($fn instanceof OutputInterface) {
$fn = function ($line) use ($fn) {
$fn->write($line);
};
} elseif ($fn === true) {
$fn = function ($line) {
echo $line;
};
} elseif ($fn === false) {
$fn = function () { };
}
$this->output = $fn;
}
/**
* ensure writing phar files is enabled or respawn with PHP setting which allows writing
*
* @param int $wait
* @return void
* @uses assertWritable()
*/
public function coerceWritable($wait = 1)
{
try {
$this->assertWritable();
}
catch (UnexpectedValueException $e) {
if (!function_exists('pcntl_exec')) {
$this->log('<error>' . $e->getMessage() . '</error>');
return;
}
$this->log('<info>' . $e->getMessage() . ', trying to re-spawn with correct config</info>');
if ($wait) {
sleep($wait);
}
$args = array_merge(array('php', '-d phar.readonly=off'), $_SERVER['argv']);
if (pcntl_exec('/usr/bin/env', $args) === false) {
$this->log('<error>Unable to switch into new configuration</error>');
return;
}
}
}
/**
* ensure writing phar files is enabled or throw an exception
*
* @throws UnexpectedValueException
*/
public function assertWritable()
{
if (ini_get('phar.readonly') === '1') {
throw new UnexpectedValueException('Your configuration disabled writing phar files (phar.readonly = On), please update your configuration or run with "php -d phar.readonly=off ' . $_SERVER['argv'][0].'"');
}
}
public function getPharer($path, $version = null)
{
if ($version !== null) {
// TODO: should be the other way around
$path .= ':' . $version;
}
$step = 1;
$steps = 1;
if ($this->isPackageUrl($path)) {
$url = $path;
$version = null;
$steps = 3;
if (preg_match('/(.+)\:((?:dev\-|v\d)\S+)$/i', $url, $match)) {
$url = $match[1];
$version = $match[2];
if (substr($version, 0, 4) === 'dev-') {
$version = substr($version, 4);
}
}
$path = $this->getDirTemporary();
$finder = new ExecutableFinder();
$git = $finder->find('git', '/usr/bin/git');
$that = $this;
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Cloning <info>' . $url . '</info> into temporary directory <info>' . $path . '</info>',
function() use ($that, $url, $path, $version, $git) {
$that->exec($git . ' clone ' . escapeshellarg($url) . ' ' . escapeshellarg($path));
if ($version !== null) {
$this->exec($git . ' checkout ' . escapeshellarg($version) . ' 2>&1', $path);
}
},
'Cloning base repository completed'
);
$pharcomposer = new PharComposer($path . '/composer.json');
$package = $pharcomposer->getPackageRoot()->getName();
if (is_file('composer.phar')) {
$command = $finder->find('php', '/usr/bin/php') . ' composer.phar';
} else {
$command = $finder->find('composer', '/usr/bin/composer');
}
$command .= ' install --no-dev --no-progress --no-scripts';
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Installing dependencies for <info>' . $package . '</info> into <info>' . $path . '</info> (using <info>' . $command . '</info>)',
function () use ($that, $command, $path) {
try {
$that->exec($command, $path);
}
catch (UnexpectedValueException $e) {
throw new UnexpectedValueException('Installing dependencies via composer failed', 0, $e);
}
},
'Downloading dependencies completed'
);
} elseif ($this->isPackageName($path)) {
if (is_dir($path)) {
$this->log('<info>There\'s also a directory with the given name</info>');
}
$steps = 2;
$package = $path;
$path = $this->getDirTemporary();
$finder = new ExecutableFinder();
if (is_file('composer.phar')) {
$command = $finder->find('php', '/usr/bin/php') . ' composer.phar';
} else {
$command = $finder->find('composer', '/usr/bin/composer');
}
$command .= ' create-project ' . escapeshellarg($package) . ' ' . escapeshellarg($path) . ' --no-dev --no-progress --no-scripts';
$that = $this;
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Installing <info>' . $package . '</info> to temporary directory <info>' . $path . '</info> (using <info>' . $command . '</info>)',
function () use ($that, $command) {
try {
$that->exec($command);
}
catch (UnexpectedValueException $e) {
throw new UnexpectedValueException('Installing package via composer failed', 0, $e);
}
},
'Downloading package completed'
);
}
if (is_dir($path)) {
$path = rtrim($path, '/') . '/composer.json';
}
if (!is_file($path)) {
throw new InvalidArgumentException('The given path "' . $path . '" is not a readable file');
}
$pharer = new PharComposer($path);
$pharer->setOutput($this->output);
$pharer->setStep($step);
$pathVendor = $pharer->getPathVendor();
if (!is_dir($pathVendor)) {
throw new RuntimeException('Project is not installed via composer. Run "composer install" manually');
}
return $pharer;
}
public function measure($fn)
{
$time = microtime(true);
$fn();
return max(microtime(true) - $time, 0);
}
public function displayMeasure($title, $fn, $success)
{
$this->log($title);
$time = $this->measure($fn);
$this->log('');
$this->log(' <info>OK</info> - ' . $success .' (after ' . round($time, 1) . 's)');
}
public function exec($cmd, $chdir = null)
{
$nl = true;
//
$output = $this->output;
$process = new Process($cmd, $chdir);
$process->setTimeout(null);
$process->start();
$code = $process->wait(function($type, $data) use ($output, &$nl) {
if ($nl === true) {
$data = "\n" . $data;
$nl = false;
}
if (substr($data, -1) === "\n") {
$nl = true;
$data = substr($data, 0, -1);
}
$data = str_replace("\n", "\n ", $data);
$output($data);
});
if ($nl) {
$this->log('');
}
if ($code !== 0) {
throw new UnexpectedValueException('Error status code: ' . $process->getExitCodeText() . ' (code ' . $code . ')');
}
}
public function install(PharComposer $pharer, $path)
{
$pharer->build();
$this->log('Move resulting phar to <info>' . $path . '</info>');
$this->exec($this->binSudo . ' -- mv -f ' . escapeshellarg($pharer->getTarget()) . ' ' . escapeshellarg($path));
$this->log('');
$this->log(' <info>OK</info> - Moved to <info>' . $path . '</info>');
}
public function getSystemBin(PharComposer $pharer, $path = null)
{
// no path given => place in system bin path
if ($path === null) {
$path = self::PATH_BIN;
}
// no slash => path is relative to system bin path
if (strpos($path, '/') === false) {
$path = self::PATH_BIN . '/' . $path;
}
// TODO: check path is in $PATH environment
// path is actually a directory => append package name
if (is_dir($path)) {
$path = rtrim($path, '/') . '/' . basename($pharer->getTarget(), '.phar');
}
return $path;
}
private function isPackageName($path)
{
return !!preg_match('/^[^\s\/]+\/[^\s\/]+(\:[^\s]+)?$/i', $path);
}
private function isPackageUrl($path)
{
return (strpos($path, '://') !== false && @parse_url($path) !== false);
}
private function getDirTemporary()
{
$path = sys_get_temp_dir() . '/phar-composer' . mt_rand(0,9);
while (is_dir($path)) {
$path .= mt_rand(0, 9);
}
return $path;
}
}

View File

@@ -1,262 +0,0 @@
<?php
namespace Clue\PharComposer\Phar;
use Symfony\Component\Finder\Finder;
use Herrera\Box\StubGenerator;
use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Finder\SplFileInfo;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Package\Package;
/**
* The PharComposer is responsible for collecting options and then building the target phar
*/
class PharComposer
{
private $pathProject;
private $package;
private $main = null;
private $target = null;
private $logger;
private $step = '?';
public function __construct($path)
{
$path = realpath($path);
$this->package = $this->loadJson($path);
$this->pathProject = dirname($path) . '/';
$this->logger = new Logger();
}
/**
* set output function to use to output log messages
*
* @param callable|boolean $output callable that receives a single $line argument or boolean echo
*/
public function setOutput($output)
{
$this->logger->setOutput($output);
}
public function getTarget()
{
if ($this->target === null) {
if (isset($this->package['name'])) {
// skip vendor name from package name
$this->target = substr($this->package['name'], strpos($this->package['name'], '/') + 1);
} else {
$this->target = basename($this->pathProject);
}
$this->target .= '.phar';
}
return $this->target;
}
public function setTarget($target)
{
// path is actually a directory => append package name
if (is_dir($target)) {
$this->target = null;
$target = rtrim($target, '/') . '/' . $this->getTarget();
}
$this->target = $target;
return $this;
}
public function getMain()
{
if ($this->main === null) {
foreach ($this->getPackageRoot()->getBins() as $path) {
if (!file_exists($path)) {
throw new UnexpectedValueException('Bin file "' . $path . '" does not exist');
}
$this->main = $path;
break;
}
}
return $this->main;
}
public function setMain($main)
{
$this->main = $main;
return $this;
}
/**
* base project path. all files MUST BE relative to this location
*
* @return string
*/
public function getBase()
{
return $this->pathProject;
}
/**
* get absolute path to vendor directory
*
* @return string
*/
public function getPathVendor()
{
return $this->getPackageRoot()->getPathVendor();
}
/**
*
* @return Package
*/
public function getPackageRoot()
{
return new Package($this->package, $this->pathProject);
}
/**
*
* @return Package[]
*/
public function getPackagesDependencies()
{
$packages = array();
$pathVendor = $this->getPathVendor();
// load all installed packages (use installed.json which also includes version instead of composer.lock)
if (is_file($pathVendor . 'composer/installed.json')) {
// file does not exist if there's nothing to be installed
$installed = $this->loadJson($pathVendor . 'composer/installed.json');
foreach ($installed as $package) {
$dir = $package['name'] . '/';
if (isset($package['target-dir'])) {
$dir .= trim($package['target-dir'], '/') . '/';
}
$dir = $pathVendor . $dir;
$packages []= new Package($package, $dir);
}
}
return $packages;
}
public function build()
{
$this->log('[' . $this->step . '/' . $this->step.'] Creating phar <info>' . $this->getTarget() . '</info>');
$time = microtime(true);
$pathVendor = $this->getPathVendor();
if (!is_dir($pathVendor)) {
throw new RuntimeException('Directory "' . $pathVendor . '" not properly installed, did you run "composer install"?');
}
$target = $this->getTarget();
if (file_exists($target)) {
$this->log(' - Remove existing file <info>' . $target . '</info> (' . $this->getSize($target) . ')');
if(unlink($target) === false) {
throw new UnexpectedValueException('Unable to remove existing phar archive "'.$target.'"');
}
}
$targetPhar = TargetPhar::create($target, $this);
$this->log(' - Adding main package');
$targetPhar->addBundle(Bundle::from($this->getPackageRoot(), $this->logger));
$this->log(' - Adding composer base files');
// explicitly add composer autoloader
$targetPhar->addFile($pathVendor . 'autoload.php');
// TODO: check for vendor/bin !
// only add composer base directory (no sub-directories!)
$targetPhar->buildFromIterator(new \GlobIterator($pathVendor . 'composer/*.*', \FilesystemIterator::KEY_AS_FILENAME));
foreach ($this->getPackagesDependencies() as $package) {
$this->log(' - Adding dependency "' . $package->getName() . '" from "' . $this->getPathLocalToBase($package->getDirectory()) . '"');
$targetPhar->addBundle(Bundle::from($package, $this->logger));
}
$this->log(' - Setting main/stub');
$chmod = 0755;
$main = $this->getMain();
if ($main === null) {
$this->log(' WARNING: No main bin file defined! Resulting phar will NOT be executable');
} else {
$generator = StubGenerator::create()
->index($this->getPathLocalToBase($main))
->extract(true)
->banner("Bundled by phar-composer with the help of php-box.\n\n@link https://github.com/clue/phar-composer");
$lines = file($main, FILE_IGNORE_NEW_LINES);
if (substr($lines[0], 0, 2) === '#!') {
$this->log(' Using referenced shebang "'. $lines[0] . '"');
$generator->shebang($lines[0]);
// remove shebang from main file and add (overwrite)
unset($lines[0]);
$targetPhar->addFromString($this->getPathLocalToBase($main), implode("\n", $lines));
}
$targetPhar->setStub($generator->generate());
$chmod = octdec(substr(decoct(fileperms($main)),-4));
$this->log(' Using referenced chmod ' . sprintf('%04o', $chmod));
}
$targetPhar->finalize();
if ($chmod !== null) {
$this->log(' Applying chmod ' . sprintf('%04o', $chmod));
if (chmod($target, $chmod) === false) {
throw new UnexpectedValueException('Unable to chmod target file "' . $target .'"');
}
}
$time = max(microtime(true) - $time, 0);
$this->log('');
$this->log(' <info>OK</info> - Creating <info>' . $this->getTarget() .'</info> (' . $this->getSize($this->getTarget()) . ') completed after ' . round($time, 1) . 's');
}
private function getSize($path)
{
return round(filesize($path) / 1024, 1) . ' KiB';
}
public function getPathLocalToBase($path)
{
if (strpos($path, $this->pathProject) !== 0) {
throw new UnexpectedValueException('Path "' . $path . '" is not within base project path "' . $this->pathProject . '"');
}
return substr($path, strlen($this->pathProject));
}
public function log($message)
{
$this->logger->log($message);
}
public function setStep($step)
{
$this->step = $step;
}
private function loadJson($path)
{
$ret = json_decode(file_get_contents($path), true);
if ($ret === null) {
var_dump(json_last_error(), JSON_ERROR_SYNTAX);
throw new InvalidArgumentException('Unable to parse given path "' . $path . '"');
}
return $ret;
}
}

View File

@@ -1,112 +0,0 @@
<?php
namespace Clue\PharComposer\Phar;
use Herrera\Box\Box;
use Traversable;
use Clue\PharComposer\Package\Bundle;
/**
* Represents the target phar to be created.
*
* TODO: replace PharComposer with a new BasePath class
*/
class TargetPhar
{
/**
*
* @type PharComposer
*/
private $pharComposer;
/**
*
* @type Box
*/
private $box;
/**
* constructor
*
* @param Box $box
* @param PharComposer $pharComposer
*/
public function __construct(Box $box, PharComposer $pharComposer)
{
$this->box = $box;
$this->box->getPhar()->startBuffering();
$this->pharComposer = $pharComposer;
}
/**
* create new instance in target path
*
* @param string $target
* @param PharComposer $pharComposer
* @return TargetPhar
*/
public static function create($target, PharComposer $pharComposer)
{
return new self(Box::create($target), $pharComposer);
}
/**
* finalize writing of phar file
*/
public function finalize()
{
$this->box->getPhar()->stopBuffering();
}
/**
* adds given list of resources to phar
*
* @param Bundle $bundle
*/
public function addBundle(Bundle $bundle)
{
foreach ($bundle as $resource) {
if (is_string($resource)) {
$this->addFile($resource);
} else {
$this->buildFromIterator($resource);
}
}
}
/**
* Adds a file to the Phar, after compacting it and replacing its
* placeholders.
*
* @param string $file The file name.
*/
public function addFile($file)
{
$this->box->addFile($file, $this->pharComposer->getPathLocalToBase($file));
}
/**
* Similar to Phar::buildFromIterator(), except the files will be compacted
* and their placeholders replaced.
*
* @param Traversable $iterator The iterator.
*/
public function buildFromIterator(Traversable $iterator)
{
$this->box->buildFromIterator($iterator, $this->pharComposer->getBase());
}
/**
* Used to set the PHP loader or bootstrap stub of a Phar archive
*
* @param string $stub
*/
public function setStub($stub)
{
$this->box->getPhar()->setStub($stub);
}
public function addFromString($local, $contents)
{
$this->box->addFromString($local, $contents);
}
}

View File

@@ -1,44 +0,0 @@
<?php
use Clue\PharComposer\Logger as Logger;
class LoggerTest extends TestCase
{
/**
* instance to test
*
* @type Logger
*/
private $logger;
/**
* set up test environment
*/
public function setUp()
{
$this->logger = new Logger();
}
/**
* @test
*/
public function echosToStdOutByDefault()
{
ob_start();
$this->logger->log('some informational message');
$this->assertEquals('some informational message' . PHP_EOL,
ob_get_contents()
);
ob_end_clean();
}
/**
* @test
*/
public function callsGivenOutputFunctionWhenSet()
{
$that = $this;
$this->logger->setOutput(function($message) use($that) { $that->assertEquals('some informational message' . PHP_EOL, $message);});
$this->logger->log('some informational message');
}
}

View File

@@ -1,86 +0,0 @@
<?php
use Clue\PharComposer\Package\Autoload;
class AutoloadTest extends TestCase
{
private function createAutoload(array $autoload)
{
return new Autoload($autoload);
}
/**
* @test
*/
public function returnsEmptyPsr0ListIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getPsr0()
);
}
/**
* @test
*/
public function returnsAllPathesDefinedByPsr0WithSinglePath()
{
$path = realpath(__DIR__ . '/../src');
$this->assertEquals(array($path . '/Clue'),
$this->createAutoload(array('psr-0' => array('Clue' => $path)))
->getPsr0()
);
}
/**
* @test
*/
public function returnsAllPathesDefinedByPsr0WithSeveralPathes()
{
$path = realpath(__DIR__ . '/../src');
$this->assertEquals(array($path . '/Clue', $path . '/Clue'),
$this->createAutoload(array('psr-0' => array('Clue' => array($path, $path))))
->getPsr0()
);
}
/**
* @test
*/
public function returnsEmptyClassmapIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getClassmap()
);
}
/**
* @test
*/
public function returnsClassmapAsDefined()
{
$this->assertEquals(array('Example/SomeClass' => 'src/Example/SomeClass.php'),
$this->createAutoload(array('classmap' => array('Example/SomeClass' => 'src/Example/SomeClass.php')))
->getClassmap()
);
}
/**
* @test
*/
public function returnsEmptyFilelistIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getFiles()
);
}
/**
* @test
*/
public function returnsFilelistAsDefined()
{
$this->assertEquals(array('foo.php', 'bar.php'),
$this->createAutoload(array('files' => array('foo.php', 'bar.php')))->getFiles()
);
}
}

View File

@@ -1,103 +0,0 @@
<?php
use Clue\PharComposer\Package\Bundler\Explicit as ExplicitBundler;
use Clue\PharComposer\Package\Package;
class ExplicitBundlerTest extends TestCase
{
/**
* instance to test
*
* @type ExplicitBundler
*/
private $explicitBundler;
private $package;
private $path;
/**
* set up test environment
*/
public function setUp()
{
$this->path = realpath(__DIR__ . '/../../../');
$this->package = new Package(array('bin' => array('bin/example'),
'autoload' => array('files' => array('foo.php'),
'classmap' => array('src/Example/SomeClass.php'),
'psr-0' => array('Clue' => 'src')
),
),
$this->path . '/'
);
$this->explicitBundler = new ExplicitBundler($this->package, $this->createMock('Clue\PharComposer\Logger'));
}
private function createMock($class)
{
return $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
}
/**
* @test
*/
public function addsBinariesDefinedByPackage()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/bin/example'),
'Failed asserting that "bin/example" is contained in bundle'
);
}
/**
* @test
*/
public function addsFilesDefinedByAutoload()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/foo.php'),
'Failed asserting that "foo.php" is contained in bundle'
);
}
/**
* @test
*/
public function addsFilesDefinedByClassmap()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/src/Example/SomeClass.php'),
'Failed asserting that "src/Example/SomeClass.php" is contained in bundle'
);
}
/**
* @test
*/
public function addsAllPathesDefinedByPsr0WithSinglePath()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/src/Clue/'),
'Failed asserting that ' . $this->path . '/src/Clue/ is contained in bundle'
);
}
/**
* @test
*/
public function addsAllPathesDefinedByPsr0WithSeveralPathes()
{
$this->package = new Package(array('autoload' => array('psr-0' => array('Clue' => array('src/',
'src/'
)
)
)
),
$this->path . '/'
);
$this->explicitBundler = new ExplicitBundler($this->package, $this->createMock('Clue\PharComposer\Logger'));
$bundle = $this->explicitBundler->bundle();
$this->assertTrue($bundle->contains($this->path . '/src'),
'Failed asserting that ' . $this->path . '/src' . ' is contained in bundle'
);
}
}

View File

@@ -1,99 +0,0 @@
<?php
use Clue\PharComposer\Package\Package;
use Clue\PharComposer\Package\Autoload;
class PackageTest extends TestCase
{
public function testConstructorDefaults()
{
$package = new Package(array(), 'dir/');
$this->assertEquals(new Autoload(array()), $package->getAutoload());
$this->assertEquals(array(), $package->getBins());
$this->assertEquals('dir/', $package->getDirectory());
$this->assertEquals('unknown', $package->getName());
$this->assertEquals('dir/vendor/', $package->getPathVendor());
}
public function testConstructorData()
{
$package = new Package(array(
'name' => 'test/test',
'bin' => array('bin/main', 'bin2'),
'config' => array(
'vendor-dir' => 'src/vendors'
)
), 'dir/');
$this->assertEquals(array('dir/bin/main', 'dir/bin2'), $package->getBins());
$this->assertEquals('test/test', $package->getName());
$this->assertEquals('dir/src/vendors/', $package->getPathVendor());
}
private function createMockLogger()
{
return $this->getMockBuilder('Clue\PharComposer\Logger')
->disableOriginalConstructor()
->getMock();
}
public function testConstructorBundlerComposer()
{
$package = new Package(array(
'extra' => array(
'phar' => array(
'bundler' => 'composer'
)
)
), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Explicit',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerCompleteWithExplicitConfig()
{
$package = new Package(array(
'extra' => array(
'phar' => array(
'bundler' => 'complete'
)
)
), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerCompleteAsDefault()
{
$package = new Package(array(), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerInvalid()
{
$package = new Package(array(
'name' => 'cool-package',
'extra' => array(
'phar' => array(
'bundler' => 'foo'
)
)
), 'dir/');
$mockLogger = $this->createMockLogger();
$mockLogger->expects($this->once())
->method('log')
->with($this->equalTo('Invalid bundler "foo" specified in package "cool-package", will fall back to "complete" bundler'));
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($mockLogger)
);
}
}

View File

@@ -1,62 +0,0 @@
<?php
use Clue\PharComposer\Phar\Packager;
class PackagerTest extends TestCase
{
private $packager;
public function setUp()
{
$this->packager = new Packager();
}
/**
*
* @param string $expectedOutput
* @param string $command
* @dataProvider provideExecCommands
*/
public function testExec($expectedOutput, $command)
{
$this->expectOutputString($expectedOutput);
$this->packager->exec($command);
}
public function provideExecCommands()
{
return array(
array("\n output\n", 'echo output'),
array("\n error\n", 'echo error >&2'),
array("\n mixed\n errors\n", 'echo mixed && echo errors >&1'),
);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage not installed
*/
public function testEmptyNotInstalled()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/01-empty');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage not a readable file
*/
public function testNoComposer()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/02-no-composer');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage not a readable file
*/
public function testNoComposerMissing()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/02-no-composer/composer.json');
}
}

View File

@@ -1,42 +0,0 @@
<?php
use Clue\PharComposer\Phar\PharComposer;
class PharComposerTest extends TestCase
{
public function testConstructor()
{
$pharcomposer = new PharComposer(__DIR__ . '/../../composer.json');
$this->assertEquals($this->getPathProjectAbsolute('/') . '/', $pharcomposer->getBase());
$this->assertEquals($this->getPathProjectAbsolute('bin/phar-composer'), $pharcomposer->getMain());
$this->assertInstanceOf('Clue\PharComposer\Package\Package', $pharcomposer->getPackageRoot());
$this->assertNotCount(0, $pharcomposer->getPackagesDependencies());
$this->assertEquals($this->getPathProjectAbsolute('vendor') . '/', $pharcomposer->getPathVendor());
$this->assertEquals('phar-composer.phar', $pharcomposer->getTarget());
return $pharcomposer;
}
/**
* @param PharComposer $pharcomposer
* @depends testConstructor
*/
public function testSetters(PharComposer $pharcomposer)
{
$pharcomposer->setMain('example/phar-composer.php');
$this->assertEquals('example/phar-composer.php', $pharcomposer->getMain());
$pharcomposer->setTarget('test.phar');
$this->assertEquals('test.phar', $pharcomposer->getTarget());
return $pharcomposer;
}
private function getPathProjectAbsolute($path)
{
return realpath(__DIR__ . '/../../' . $path);
}
}

View File

@@ -1,117 +0,0 @@
<?php
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Phar\TargetPhar;
class TargetPharTest extends TestCase
{
/**
* instance to test
*
* @ype TargetPhar
*/
private $targetPhar;
private $mockPhar;
private $mockBox;
private $mockPharComposer;
/**
* set up test environment
*/
public function setUp()
{
$this->mockPhar = $this->createMock('\Phar');
$this->mockBox = $this->createMock('Herrera\Box\Box');
$this->mockBox->expects($this->any())
->method('getPhar')
->will($this->returnValue($this->mockPhar));
$this->mockPharComposer = $this->createMock('Clue\PharComposer\Phar\PharComposer');
$this->targetPhar = new TargetPhar($this->mockBox, $this->mockPharComposer);
}
private function createMock($class)
{
return $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
}
/**
* @test
*/
public function addFileCalculatesLocalPartForBox()
{
$this->mockPharComposer->expects($this->once())
->method('getPathLocalToBase')
->with($this->equalTo('path/to/package/file.php'))
->will($this->returnValue('file.php'));
$this->mockBox->expects($this->once())
->method('addFile')
->with($this->equalTo('path/to/package/file.php'), $this->equalTo('file.php'));
$this->targetPhar->addFile('path/to/package/file.php');
}
/**
* @test
*/
public function buildFromIteratorProvidesBasePathForBox()
{
$mockTraversable = $this->getMock('\Iterator');
$this->mockPharComposer->expects($this->once())
->method('getBase')
->will($this->returnValue('path/to/package'));
$this->mockBox->expects($this->once())
->method('buildFromIterator')
->with($this->equalTo($mockTraversable), $this->equalTo('path/to/package'));
$this->targetPhar->buildFromIterator($mockTraversable);
}
/**
* @test
*/
public function addPackageAddsResourcesFromCalculatedBundle()
{
$bundle = new Bundle();
$bundle->addFile('path/to/package/file.php');
$this->mockPharComposer->expects($this->once())
->method('getPathLocalToBase')
->with($this->equalTo('path/to/package/file.php'))
->will($this->returnValue('file.php'));
$this->mockBox->expects($this->once())
->method('addFile')
->with($this->equalTo('path/to/package/file.php'), $this->equalTo('file.php'));
$mockFinder = $this->createMock('Symfony\Component\Finder\Finder');
$bundle->addDir($mockFinder);
$this->mockPharComposer->expects($this->once())
->method('getBase')
->will($this->returnValue('path/to/package'));
$this->mockBox->expects($this->once())
->method('buildFromIterator')
->with($this->equalTo($mockFinder), $this->equalTo('path/to/package'));
$this->targetPhar->addBundle($bundle);
}
/**
* @test
*/
public function setsStubOnUnderlyingPhar()
{
$this->mockPhar->expects($this->once())
->method('setStub')
->with($this->equalTo('some stub code'));
$this->targetPhar->setStub('some stub code');
}
/**
* @test
*/
public function finalizeStopsBufferingOnUnderlyingPhar()
{
$this->mockPhar->expects($this->once())
->method('stopBuffering');
$this->targetPhar->finalize();
}
}

View File

@@ -1,8 +0,0 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
class TestCase extends PHPUnit_Framework_TestCase
{
}

View File

@@ -1,3 +0,0 @@
{
"name": "vendor/empty"
}

View File

@@ -1,3 +0,0 @@
# Readme
Not a valid project directory (does not contain a `composer.json` file).

View File

@@ -1,413 +0,0 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
if ('\\' == $class[0]) {
$class = substr($class, 1);
}
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative) {
return false;
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -1,21 +0,0 @@
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
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.

View File

@@ -1,9 +0,0 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -1,19 +0,0 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'540044e0591873ddd06a920c6c94cf8f' => $vendorDir . '/wyrihaximus/ticking-promise/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'2cce0c19c44d4e6b9b83d392d5dcc239' => $vendorDir . '/wyrihaximus/react-child-process-promise/src/functions_include.php',
'7c9b72b4e40cc7adcca6fd17b1bf4c8d' => $vendorDir . '/indigophp/hash-compat/src/hash_equals.php',
'43d9263e52ab88b5668a28ee36bd4e65' => $vendorDir . '/indigophp/hash-compat/src/hash_pbkdf2.php',
'e0925b39a86673e84a647fb972717393' => $vendorDir . '/wyrihaximus/cpu-core-detector/src/functions_include.php',
'f3b28a95ab3f0417f7cb7996f4fa734a' => $vendorDir . '/wyrihaximus/react-child-process-pool/src/functions_include.php',
'5e93f83f32d7c16b062898884b9a20d8' => $vendorDir . '/react/filesystem/src/functions_include.php',
);

View File

@@ -1,15 +0,0 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'SamBurns\\ConfigFileParser\\' => array($vendorDir . '/samburns/config-file-parser/src'),
'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
'Evenement' => array($vendorDir . '/evenement/evenement/src'),
'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'),
'' => array($vendorDir . '/webignition/readable-duration/src'),
);

View File

@@ -1,37 +0,0 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'WyriHaximus\\React\\ChildProcess\\Pool\\' => array($vendorDir . '/wyrihaximus/react-child-process-pool/src'),
'WyriHaximus\\React\\ChildProcess\\Messenger\\' => array($vendorDir . '/wyrihaximus/react-child-process-messenger/src'),
'WyriHaximus\\React\\' => array($vendorDir . '/wyrihaximus/ticking-promise/src', $vendorDir . '/wyrihaximus/react-child-process-promise/src'),
'WyriHaximus\\CpuCoreDetector\\' => array($vendorDir . '/wyrihaximus/cpu-core-detector/src'),
'Tivie\\OS\\' => array($vendorDir . '/tivie/php-os-detector/src'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Rych\\ByteSize\\' => array($vendorDir . '/rych/bytesize/src'),
'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
'React\\SocketClient\\' => array($vendorDir . '/react/socket-client/src'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
'React\\Http\\' => array($vendorDir . '/react/http/src'),
'React\\HttpClient\\' => array($vendorDir . '/react/http-client/src'),
'React\\Filesystem\\' => array($vendorDir . '/react/filesystem/src'),
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
'React\\ChildProcess\\' => array($vendorDir . '/react/child-process'),
'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
'Noodlehaus\\' => array($vendorDir . '/hassankhan/config/src'),
'MacFJA\\Symfony\\Console\\Filechooser\\' => array($vendorDir . '/macfja/symfony-console-filechooser/lib'),
'MacFJA\\PharBuilder\\' => array($vendorDir . '/macfja/phar-builder/app'),
'Linfo\\' => array($vendorDir . '/linfo/linfo/src/Linfo'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
);

View File

@@ -1,70 +0,0 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit0cf5e3b00d3012d92e986b80c38c2a9f
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit0cf5e3b00d3012d92e986b80c38c2a9f', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit0cf5e3b00d3012d92e986b80c38c2a9f', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire0cf5e3b00d3012d92e986b80c38c2a9f($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire0cf5e3b00d3012d92e986b80c38c2a9f($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

View File

@@ -1,245 +0,0 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'540044e0591873ddd06a920c6c94cf8f' => __DIR__ . '/..' . '/wyrihaximus/ticking-promise/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'2cce0c19c44d4e6b9b83d392d5dcc239' => __DIR__ . '/..' . '/wyrihaximus/react-child-process-promise/src/functions_include.php',
'7c9b72b4e40cc7adcca6fd17b1bf4c8d' => __DIR__ . '/..' . '/indigophp/hash-compat/src/hash_equals.php',
'43d9263e52ab88b5668a28ee36bd4e65' => __DIR__ . '/..' . '/indigophp/hash-compat/src/hash_pbkdf2.php',
'e0925b39a86673e84a647fb972717393' => __DIR__ . '/..' . '/wyrihaximus/cpu-core-detector/src/functions_include.php',
'f3b28a95ab3f0417f7cb7996f4fa734a' => __DIR__ . '/..' . '/wyrihaximus/react-child-process-pool/src/functions_include.php',
'5e93f83f32d7c16b062898884b9a20d8' => __DIR__ . '/..' . '/react/filesystem/src/functions_include.php',
);
public static $prefixLengthsPsr4 = array (
'W' =>
array (
'WyriHaximus\\React\\ChildProcess\\Pool\\' => 36,
'WyriHaximus\\React\\ChildProcess\\Messenger\\' => 41,
'WyriHaximus\\React\\' => 18,
'WyriHaximus\\CpuCoreDetector\\' => 28,
),
'T' =>
array (
'Tivie\\OS\\' => 9,
),
'S' =>
array (
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\Console\\' => 26,
),
'R' =>
array (
'Rych\\ByteSize\\' => 14,
'React\\Stream\\' => 13,
'React\\Socket\\' => 13,
'React\\SocketClient\\' => 19,
'React\\Promise\\' => 14,
'React\\Http\\' => 11,
'React\\HttpClient\\' => 17,
'React\\Filesystem\\' => 17,
'React\\EventLoop\\' => 16,
'React\\Dns\\' => 10,
'React\\ChildProcess\\' => 19,
'React\\Cache\\' => 12,
),
'P' =>
array (
'Psr\\Http\\Message\\' => 17,
),
'N' =>
array (
'Noodlehaus\\' => 11,
),
'M' =>
array (
'MacFJA\\Symfony\\Console\\Filechooser\\' => 35,
'MacFJA\\PharBuilder\\' => 19,
),
'L' =>
array (
'Linfo\\' => 6,
),
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
),
);
public static $prefixDirsPsr4 = array (
'WyriHaximus\\React\\ChildProcess\\Pool\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-pool/src',
),
'WyriHaximus\\React\\ChildProcess\\Messenger\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-messenger/src',
),
'WyriHaximus\\React\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/ticking-promise/src',
1 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-promise/src',
),
'WyriHaximus\\CpuCoreDetector\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/cpu-core-detector/src',
),
'Tivie\\OS\\' =>
array (
0 => __DIR__ . '/..' . '/tivie/php-os-detector/src',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Rych\\ByteSize\\' =>
array (
0 => __DIR__ . '/..' . '/rych/bytesize/src',
),
'React\\Stream\\' =>
array (
0 => __DIR__ . '/..' . '/react/stream/src',
),
'React\\Socket\\' =>
array (
0 => __DIR__ . '/..' . '/react/socket/src',
),
'React\\SocketClient\\' =>
array (
0 => __DIR__ . '/..' . '/react/socket-client/src',
),
'React\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/react/promise/src',
),
'React\\Http\\' =>
array (
0 => __DIR__ . '/..' . '/react/http/src',
),
'React\\HttpClient\\' =>
array (
0 => __DIR__ . '/..' . '/react/http-client/src',
),
'React\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/react/filesystem/src',
),
'React\\EventLoop\\' =>
array (
0 => __DIR__ . '/..' . '/react/event-loop/src',
),
'React\\Dns\\' =>
array (
0 => __DIR__ . '/..' . '/react/dns/src',
),
'React\\ChildProcess\\' =>
array (
0 => __DIR__ . '/..' . '/react/child-process',
),
'React\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/react/cache/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Noodlehaus\\' =>
array (
0 => __DIR__ . '/..' . '/hassankhan/config/src',
),
'MacFJA\\Symfony\\Console\\Filechooser\\' =>
array (
0 => __DIR__ . '/..' . '/macfja/symfony-console-filechooser/lib',
),
'MacFJA\\PharBuilder\\' =>
array (
0 => __DIR__ . '/..' . '/macfja/phar-builder/app',
),
'Linfo\\' =>
array (
0 => __DIR__ . '/..' . '/linfo/linfo/src/Linfo',
),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
);
public static $prefixesPsr0 = array (
'S' =>
array (
'SamBurns\\ConfigFileParser\\' =>
array (
0 => __DIR__ . '/..' . '/samburns/config-file-parser/src',
),
),
'G' =>
array (
'Guzzle\\Tests' =>
array (
0 => __DIR__ . '/..' . '/guzzle/guzzle/tests',
),
'Guzzle' =>
array (
0 => __DIR__ . '/..' . '/guzzle/guzzle/src',
),
),
'E' =>
array (
'Evenement' =>
array (
0 => __DIR__ . '/..' . '/evenement/evenement/src',
),
),
'D' =>
array (
'Doctrine\\Common\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib',
),
),
);
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/..' . '/webignition/readable-duration/src',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixesPsr0;
$loader->fallbackDirsPsr0 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$fallbackDirsPsr0;
}, null, ClassLoader::class);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
vendor/
composer.lock
composer.phar
phpunit.xml

View File

@@ -1,21 +0,0 @@
language: php
sudo: false
cache:
directory:
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
install:
- composer install -n
script:
- phpunit

View File

@@ -1,19 +0,0 @@
Copyright (c) 2006-2015 Doctrine Project
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.

View File

@@ -1,6 +0,0 @@
# Doctrine Inflector
Doctrine Inflector is a small library that can perform string manipulations
with regard to upper-/lowercase and singular/plural forms of words.
[![Build Status](https://travis-ci.org/doctrine/inflector.svg?branch=master)](https://travis-ci.org/doctrine/inflector)

View File

@@ -1,29 +0,0 @@
{
"name": "doctrine/inflector",
"type": "library",
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"keywords": ["string", "inflection", "singularize", "pluralize"],
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.*"
},
"autoload": {
"psr-0": { "Doctrine\\Common\\Inflector\\": "lib/" }
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}

View File

@@ -1,482 +0,0 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Inflector;
/**
* Doctrine inflector has static methods for inflecting text.
*
* The methods in these classes are from several different sources collected
* across several different php projects and several different authors. The
* original author names and emails are not known.
*
* Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
*
* @link www.doctrine-project.org
* @since 1.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class Inflector
{
/**
* Plural inflector rules.
*
* @var array
*/
private static $plural = array(
'rules' => array(
'/(s)tatus$/i' => '\1\2tatuses',
'/(quiz)$/i' => '\1zes',
'/^(ox)$/i' => '\1\2en',
'/([m|l])ouse$/i' => '\1ice',
'/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
'/(x|ch|ss|sh)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(hive)$/i' => '\1s',
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
'/sis$/i' => 'ses',
'/([ti])um$/i' => '\1a',
'/(p)erson$/i' => '\1eople',
'/(m)an$/i' => '\1en',
'/(c)hild$/i' => '\1hildren',
'/(f)oot$/i' => '\1eet',
'/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
'/us$/i' => 'uses',
'/(alias)$/i' => '\1es',
'/(analys|ax|cris|test|thes)is$/i' => '\1es',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
),
'uninflected' => array(
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
),
'irregular' => array(
'atlas' => 'atlases',
'axe' => 'axes',
'beef' => 'beefs',
'brother' => 'brothers',
'cafe' => 'cafes',
'chateau' => 'chateaux',
'child' => 'children',
'cookie' => 'cookies',
'corpus' => 'corpuses',
'cow' => 'cows',
'criterion' => 'criteria',
'curriculum' => 'curricula',
'demo' => 'demos',
'domino' => 'dominoes',
'echo' => 'echoes',
'foot' => 'feet',
'fungus' => 'fungi',
'ganglion' => 'ganglions',
'genie' => 'genies',
'genus' => 'genera',
'graffito' => 'graffiti',
'hippopotamus' => 'hippopotami',
'hoof' => 'hoofs',
'human' => 'humans',
'iris' => 'irises',
'leaf' => 'leaves',
'loaf' => 'loaves',
'man' => 'men',
'medium' => 'media',
'memorandum' => 'memoranda',
'money' => 'monies',
'mongoose' => 'mongooses',
'motto' => 'mottoes',
'move' => 'moves',
'mythos' => 'mythoi',
'niche' => 'niches',
'nucleus' => 'nuclei',
'numen' => 'numina',
'occiput' => 'occiputs',
'octopus' => 'octopuses',
'opus' => 'opuses',
'ox' => 'oxen',
'penis' => 'penises',
'person' => 'people',
'plateau' => 'plateaux',
'runner-up' => 'runners-up',
'sex' => 'sexes',
'soliloquy' => 'soliloquies',
'son-in-law' => 'sons-in-law',
'syllabus' => 'syllabi',
'testis' => 'testes',
'thief' => 'thieves',
'tooth' => 'teeth',
'tornado' => 'tornadoes',
'trilby' => 'trilbys',
'turf' => 'turfs',
'volcano' => 'volcanoes',
)
);
/**
* Singular inflector rules.
*
* @var array
*/
private static $singular = array(
'rules' => array(
'/(s)tatuses$/i' => '\1\2tatus',
'/^(.*)(menu)s$/i' => '\1\2',
'/(quiz)zes$/i' => '\\1',
'/(matr)ices$/i' => '\1ix',
'/(vert|ind)ices$/i' => '\1ex',
'/^(ox)en/i' => '\1',
'/(alias)(es)*$/i' => '\1',
'/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1',
'/(analys|ax|cris|test|thes)es$/i' => '\1is',
'/(shoe|slave)s$/i' => '\1',
'/(o)es$/i' => '\1',
'/ouses$/' => 'ouse',
'/([^a])uses$/' => '\1us',
'/([m|l])ice$/i' => '\1ouse',
'/(x|ch|ss|sh)es$/i' => '\1',
'/(m)ovies$/i' => '\1\2ovie',
'/(s)eries$/i' => '\1\2eries',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/([^fo])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
'/(c)hildren$/i' => '\1\2hild',
'/(f)eet$/i' => '\1oot',
'/(n)ews$/i' => '\1\2ews',
'/eaus$/' => 'eau',
'/^(.*us)$/' => '\\1',
'/s$/i' => '',
),
'uninflected' => array(
'.*[nrlm]ese',
'.*deer',
'.*fish',
'.*measles',
'.*ois',
'.*pox',
'.*sheep',
'.*ss',
),
'irregular' => array(
'criteria' => 'criterion',
'curves' => 'curve',
'emphases' => 'emphasis',
'foes' => 'foe',
'hoaxes' => 'hoax',
'media' => 'medium',
'neuroses' => 'neurosis',
'waves' => 'wave',
'oases' => 'oasis',
)
);
/**
* Words that should not be inflected.
*
* @var array
*/
private static $uninflected = array(
'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media',
'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine',
'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting',
'wildebeest', 'Yengeese'
);
/**
* Method cache array.
*
* @var array
*/
private static $cache = array();
/**
* The initial state of Inflector so reset() works.
*
* @var array
*/
private static $initialState = array();
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*
* @param string $word The word to tableize.
*
* @return string The tableized word.
*/
public static function tableize($word)
{
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*
* @param string $word The word to classify.
*
* @return string The classified word.
*/
public static function classify($word)
{
return str_replace(" ", "", ucwords(strtr($word, "_-", " ")));
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.
*
* @param string $word The word to camelize.
*
* @return string The camelized word.
*/
public static function camelize($word)
{
return lcfirst(self::classify($word));
}
/**
* Uppercases words with configurable delimeters between words.
*
* Takes a string and capitalizes all of the words, like PHP's built-in
* ucwords function. This extends that behavior, however, by allowing the
* word delimeters to be configured, rather than only separating on
* whitespace.
*
* Here is an example:
* <code>
* <?php
* $string = 'top-o-the-morning to all_of_you!';
* echo \Doctrine\Common\Inflector\Inflector::ucwords($string);
* // Top-O-The-Morning To All_of_you!
*
* echo \Doctrine\Common\Inflector\Inflector::ucwords($string, '-_ ');
* // Top-O-The-Morning To All_Of_You!
* ?>
* </code>
*
* @param string $string The string to operate on.
* @param string $delimiters A list of word separators.
*
* @return string The string with all delimeter-separated words capitalized.
*/
public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-")
{
return preg_replace_callback(
'/[^' . preg_quote($delimiters, '/') . ']+/',
function($matches) {
return ucfirst($matches[0]);
},
$string
);
}
/**
* Clears Inflectors inflected value caches, and resets the inflection
* rules to the initial values.
*
* @return void
*/
public static function reset()
{
if (empty(self::$initialState)) {
self::$initialState = get_class_vars('Inflector');
return;
}
foreach (self::$initialState as $key => $val) {
if ($key != 'initialState') {
self::${$key} = $val;
}
}
}
/**
* Adds custom inflection $rules, of either 'plural' or 'singular' $type.
*
* ### Usage:
*
* {{{
* Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
* Inflector::rules('plural', array(
* 'rules' => array('/^(inflect)ors$/i' => '\1ables'),
* 'uninflected' => array('dontinflectme'),
* 'irregular' => array('red' => 'redlings')
* ));
* }}}
*
* @param string $type The type of inflection, either 'plural' or 'singular'
* @param array $rules An array of rules to be added.
* @param boolean $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
*
* @return void
*/
public static function rules($type, $rules, $reset = false)
{
foreach ($rules as $rule => $pattern) {
if ( ! is_array($pattern)) {
continue;
}
if ($reset) {
self::${$type}[$rule] = $pattern;
} else {
self::${$type}[$rule] = ($rule === 'uninflected')
? array_merge($pattern, self::${$type}[$rule])
: $pattern + self::${$type}[$rule];
}
unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]);
if (isset(self::${$type}['merged'][$rule])) {
unset(self::${$type}['merged'][$rule]);
}
if ($type === 'plural') {
self::$cache['pluralize'] = self::$cache['tableize'] = array();
} elseif ($type === 'singular') {
self::$cache['singularize'] = array();
}
}
self::${$type}['rules'] = $rules + self::${$type}['rules'];
}
/**
* Returns a word in plural form.
*
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*/
public static function pluralize($word)
{
if (isset(self::$cache['pluralize'][$word])) {
return self::$cache['pluralize'][$word];
}
if (!isset(self::$plural['merged']['irregular'])) {
self::$plural['merged']['irregular'] = self::$plural['irregular'];
}
if (!isset(self::$plural['merged']['uninflected'])) {
self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
}
if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['pluralize'][$word];
}
if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $word;
return $word;
}
foreach (self::$plural['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['pluralize'][$word];
}
}
}
/**
* Returns a word in singular form.
*
* @param string $word The word in plural form.
*
* @return string The word in singular form.
*/
public static function singularize($word)
{
if (isset(self::$cache['singularize'][$word])) {
return self::$cache['singularize'][$word];
}
if (!isset(self::$singular['merged']['uninflected'])) {
self::$singular['merged']['uninflected'] = array_merge(
self::$singular['uninflected'],
self::$uninflected
);
}
if (!isset(self::$singular['merged']['irregular'])) {
self::$singular['merged']['irregular'] = array_merge(
self::$singular['irregular'],
array_flip(self::$plural['irregular'])
);
}
if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) {
self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')';
self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['singularize'][$word];
}
if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $word;
return $word;
}
foreach (self::$singular['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['singularize'][$word];
}
}
self::$cache['singularize'][$word] = $word;
return $word;
}
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/Doctrine/Tests/TestInit.php"
>
<testsuites>
<testsuite name="Doctrine Inflector Test Suite">
<directory>./tests/Doctrine/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./lib/Doctrine/</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
</exclude>
</groups>
</phpunit>

View File

@@ -1,395 +0,0 @@
<?php
namespace Doctrine\Tests\Common\Inflector;
use Doctrine\Tests\DoctrineTestCase;
use Doctrine\Common\Inflector\Inflector;
class InflectorTest extends DoctrineTestCase
{
/**
* Singular & Plural test data. Returns an array of sample words.
*
* @return array
*/
public function dataSampleWords()
{
Inflector::reset();
// In the format array('singular', 'plural')
return array(
array('', ''),
array('Alias', 'Aliases'),
array('alumnus', 'alumni'),
array('analysis', 'analyses'),
array('aquarium', 'aquaria'),
array('arch', 'arches'),
array('atlas', 'atlases'),
array('axe', 'axes'),
array('baby', 'babies'),
array('bacillus', 'bacilli'),
array('bacterium', 'bacteria'),
array('bureau', 'bureaus'),
array('bus', 'buses'),
array('Bus', 'Buses'),
array('cactus', 'cacti'),
array('cafe', 'cafes'),
array('calf', 'calves'),
array('categoria', 'categorias'),
array('chateau', 'chateaux'),
array('cherry', 'cherries'),
array('child', 'children'),
array('church', 'churches'),
array('circus', 'circuses'),
array('city', 'cities'),
array('cod', 'cod'),
array('cookie', 'cookies'),
array('copy', 'copies'),
array('crisis', 'crises'),
array('criterion', 'criteria'),
array('curriculum', 'curricula'),
array('curve', 'curves'),
array('deer', 'deer'),
array('demo', 'demos'),
array('dictionary', 'dictionaries'),
array('domino', 'dominoes'),
array('dwarf', 'dwarves'),
array('echo', 'echoes'),
array('elf', 'elves'),
array('emphasis', 'emphases'),
array('family', 'families'),
array('fax', 'faxes'),
array('fish', 'fish'),
array('flush', 'flushes'),
array('fly', 'flies'),
array('focus', 'foci'),
array('foe', 'foes'),
array('food_menu', 'food_menus'),
array('FoodMenu', 'FoodMenus'),
array('foot', 'feet'),
array('fungus', 'fungi'),
array('glove', 'gloves'),
array('half', 'halves'),
array('hero', 'heroes'),
array('hippopotamus', 'hippopotami'),
array('hoax', 'hoaxes'),
array('house', 'houses'),
array('human', 'humans'),
array('identity', 'identities'),
array('index', 'indices'),
array('iris', 'irises'),
array('kiss', 'kisses'),
array('knife', 'knives'),
array('leaf', 'leaves'),
array('life', 'lives'),
array('loaf', 'loaves'),
array('man', 'men'),
array('matrix', 'matrices'),
array('matrix_row', 'matrix_rows'),
array('medium', 'media'),
array('memorandum', 'memoranda'),
array('menu', 'menus'),
array('Menu', 'Menus'),
array('mess', 'messes'),
array('moose', 'moose'),
array('motto', 'mottoes'),
array('mouse', 'mice'),
array('neurosis', 'neuroses'),
array('news', 'news'),
array('NodeMedia', 'NodeMedia'),
array('nucleus', 'nuclei'),
array('oasis', 'oases'),
array('octopus', 'octopuses'),
array('pass', 'passes'),
array('person', 'people'),
array('plateau', 'plateaux'),
array('potato', 'potatoes'),
array('powerhouse', 'powerhouses'),
array('quiz', 'quizzes'),
array('radius', 'radii'),
array('reflex', 'reflexes'),
array('roof', 'roofs'),
array('runner-up', 'runners-up'),
array('scarf', 'scarves'),
array('scratch', 'scratches'),
array('series', 'series'),
array('sheep', 'sheep'),
array('shelf', 'shelves'),
array('shoe', 'shoes'),
array('son-in-law', 'sons-in-law'),
array('species', 'species'),
array('splash', 'splashes'),
array('spy', 'spies'),
array('stimulus', 'stimuli'),
array('stitch', 'stitches'),
array('story', 'stories'),
array('syllabus', 'syllabi'),
array('tax', 'taxes'),
array('terminus', 'termini'),
array('thesis', 'theses'),
array('thief', 'thieves'),
array('tomato', 'tomatoes'),
array('tooth', 'teeth'),
array('tornado', 'tornadoes'),
array('try', 'tries'),
array('vertex', 'vertices'),
array('virus', 'viri'),
array('volcano', 'volcanoes'),
array('wash', 'washes'),
array('watch', 'watches'),
array('wave', 'waves'),
array('wharf', 'wharves'),
array('wife', 'wives'),
array('woman', 'women'),
);
}
/**
* testInflectingSingulars method
*
* @dataProvider dataSampleWords
* @return void
*/
public function testInflectingSingulars($singular, $plural)
{
$this->assertEquals(
$singular,
Inflector::singularize($plural),
"'$plural' should be singularized to '$singular'"
);
}
/**
* testInflectingPlurals method
*
* @dataProvider dataSampleWords
* @return void
*/
public function testInflectingPlurals($singular, $plural)
{
$this->assertEquals(
$plural,
Inflector::pluralize($singular),
"'$singular' should be pluralized to '$plural'"
);
}
/**
* testCustomPluralRule method
*
* @return void
*/
public function testCustomPluralRule()
{
Inflector::reset();
Inflector::rules('plural', array('/^(custom)$/i' => '\1izables'));
$this->assertEquals(Inflector::pluralize('custom'), 'customizables');
Inflector::rules('plural', array('uninflected' => array('uninflectable')));
$this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable');
Inflector::rules('plural', array(
'rules' => array('/^(alert)$/i' => '\1ables'),
'uninflected' => array('noflect', 'abtuse'),
'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes')
));
$this->assertEquals(Inflector::pluralize('noflect'), 'noflect');
$this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse');
$this->assertEquals(Inflector::pluralize('alert'), 'alertables');
$this->assertEquals(Inflector::pluralize('amaze'), 'amazable');
$this->assertEquals(Inflector::pluralize('phone'), 'phonezes');
}
/**
* testCustomSingularRule method
*
* @return void
*/
public function testCustomSingularRule()
{
Inflector::reset();
Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1'));
$this->assertEquals(Inflector::singularize('epler'), 'eple');
$this->assertEquals(Inflector::singularize('jenter'), 'jente');
Inflector::rules('singular', array(
'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'),
'uninflected' => array('singulars'),
'irregular' => array('spins' => 'spinor')
));
$this->assertEquals(Inflector::singularize('inflectors'), 'inflecta');
$this->assertEquals(Inflector::singularize('contributors'), 'contributa');
$this->assertEquals(Inflector::singularize('spins'), 'spinor');
$this->assertEquals(Inflector::singularize('singulars'), 'singulars');
}
/**
* test that setting new rules clears the inflector caches.
*
* @return void
*/
public function testRulesClearsCaches()
{
Inflector::reset();
$this->assertEquals(Inflector::singularize('Bananas'), 'Banana');
$this->assertEquals(Inflector::pluralize('Banana'), 'Bananas');
Inflector::rules('singular', array(
'rules' => array('/(.*)nas$/i' => '\1zzz')
));
$this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.');
Inflector::rules('plural', array(
'rules' => array('/(.*)na$/i' => '\1zzz'),
'irregular' => array('corpus' => 'corpora')
));
$this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.');
$this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.');
}
/**
* Test resetting inflection rules.
*
* @return void
*/
public function testCustomRuleWithReset()
{
Inflector::reset();
$uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x');
$pluralIrregular = array('as' => 'ases');
Inflector::rules('singular', array(
'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'),
'uninflected' => $uninflected,
), true);
Inflector::rules('plural', array(
'rules' => array(
'/^(.*)(a|e|o|u)l$/i' => '\1\2is',
),
'uninflected' => $uninflected,
'irregular' => $pluralIrregular
), true);
$this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois');
$this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas');
$this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool');
$this->assertEquals(Inflector::singularize('Atlas'), 'Atlas');
}
/**
* Test basic ucwords functionality.
*
* @return void
*/
public function testUcwords()
{
$this->assertSame('Top-O-The-Morning To All_of_you!', Inflector::ucwords( 'top-o-the-morning to all_of_you!'));
}
/**
* Test ucwords functionality with custom delimeters.
*
* @return void
*/
public function testUcwordsWithCustomDelimeters()
{
$this->assertSame('Top-O-The-Morning To All_Of_You!', Inflector::ucwords( 'top-o-the-morning to all_of_you!', '-_ '));
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsTableize
* @return void
*/
public function testTableize($expected, $word)
{
$this->assertSame($expected, Inflector::tableize($word));
}
/**
* Strings which are used for testTableize.
*
* @return array
*/
public function dataStringsTableize()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('foo_bar', 'FooBar'),
array('f0o_bar', 'F0oBar'),
);
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsClassify
* @return void
*/
public function testClassify($expected, $word)
{
$this->assertSame($expected, Inflector::classify($word));
}
/**
* Strings which are used for testClassify.
*
* @return array
*/
public function dataStringsClassify()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('FooBar', 'foo_bar'),
array('FooBar', 'foo bar'),
array('F0oBar', 'f0o bar'),
array('F0oBar', 'f0o bar'),
array('FooBar', 'foo_bar_'),
);
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsCamelize
* @return void
*/
public function testCamelize($expected, $word)
{
$this->assertSame($expected, Inflector::camelize($word));
}
/**
* Strings which are used for testCamelize.
*
* @return array
*/
public function dataStringsCamelize()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('fooBar', 'foo_bar'),
array('fooBar', 'foo bar'),
array('f0oBar', 'f0o bar'),
array('f0oBar', 'f0o bar'),
);
}
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Doctrine\Tests;
/**
* Base testcase class for all Doctrine testcases.
*/
abstract class DoctrineTestCase extends \PHPUnit_Framework_TestCase
{
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file bootstraps the test environment.
*/
namespace Doctrine\Tests;
error_reporting(E_ALL | E_STRICT);
// register silently failing autoloader
spl_autoload_register(function($class)
{
if (0 === strpos($class, 'Doctrine\Tests\\')) {
$path = __DIR__.'/../../'.strtr($class, '\\', '/').'.php';
if (is_file($path) && is_readable($path)) {
require_once $path;
return true;
}
} else if (0 === strpos($class, 'Doctrine\Common\\')) {
$path = __DIR__.'/../../../lib/'.($class = strtr($class, '\\', '/')).'.php';
if (is_file($path) && is_readable($path)) {
require_once $path;
return true;
}
}
});

View File

@@ -1,2 +0,0 @@
composer.lock
vendor

View File

@@ -1,10 +0,0 @@
language: php
php:
- 5.4
before_script:
- wget http://getcomposer.org/composer.phar
- php composer.phar install
script: phpunit --coverage-text

View File

@@ -1,8 +0,0 @@
CHANGELOG
=========
* 2.0.0 (2012-11-02)
* Require PHP >=5.4.0
* Added EventEmitterTrait
* Removed EventEmitter2

View File

@@ -1,19 +0,0 @@
Copyright (c) 2011 Igor Wiedler
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.

View File

@@ -1,83 +0,0 @@
# Événement
Événement is a very simple event dispatching library for PHP.
It has the same design goals as [Silex](http://silex-project.org) and
[Pimple](http://pimple-project.org), to empower the user while staying concise
and simple.
It is very strongly inspired by the EventEmitter API found in
[node.js](http://nodejs.org).
[![Build Status](https://secure.travis-ci.org/igorw/evenement.png?branch=master)](http://travis-ci.org/igorw/evenement)
## Fetch
The recommended way to install Événement is [through composer](http://getcomposer.org).
Just create a composer.json file for your project:
```JSON
{
"require": {
"evenement/evenement": "2.0.*"
}
}
```
**Note:** The `2.0.*` version of Événement requires PHP 5.4. If you are
using PHP 5.3, please use the `1.0.*` version:
```JSON
{
"require": {
"evenement/evenement": "1.0.*"
}
}
```
And run these two commands to install it:
$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install
Now you can add the autoloader, and you will have access to the library:
```php
<?php
require 'vendor/autoload.php';
```
## Usage
### Creating an Emitter
```php
<?php
$emitter = new Evenement\EventEmitter();
```
### Adding Listeners
```php
<?php
$emitter->on('user.created', function (User $user) use ($logger) {
$logger->log(sprintf("User '%s' was created.", $user->getLogin()));
});
```
### Emitting Events
```php
<?php
$emitter->emit('user.created', array($user));
```
Tests
-----
$ phpunit
License
-------
MIT, see LICENSE.

View File

@@ -1,25 +0,0 @@
{
"name": "evenement/evenement",
"description": "Événement is a very simple event dispatching library for PHP",
"keywords": ["event-dispatcher", "event-emitter"],
"license": "MIT",
"authors": [
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
}
],
"require": {
"php": ">=5.4.0"
},
"autoload": {
"psr-0": {
"Evenement": "src"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
}
}

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="Evenement Test Suite">
<directory>./tests/Evenement/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -1,17 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
class EventEmitter implements EventEmitterInterface
{
use EventEmitterTrait;
}

View File

@@ -1,22 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
interface EventEmitterInterface
{
public function on($event, callable $listener);
public function once($event, callable $listener);
public function removeListener($event, callable $listener);
public function removeAllListeners($event = null);
public function listeners($event);
public function emit($event, array $arguments = []);
}

View File

@@ -1,67 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement;
trait EventEmitterTrait
{
protected $listeners = [];
public function on($event, callable $listener)
{
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $listener;
}
public function once($event, callable $listener)
{
$onceListener = function () use (&$onceListener, $event, $listener) {
$this->removeListener($event, $onceListener);
call_user_func_array($listener, func_get_args());
};
$this->on($event, $onceListener);
}
public function removeListener($event, callable $listener)
{
if (isset($this->listeners[$event])) {
if (false !== $index = array_search($listener, $this->listeners[$event], true)) {
unset($this->listeners[$event][$index]);
}
}
}
public function removeAllListeners($event = null)
{
if ($event !== null) {
unset($this->listeners[$event]);
} else {
$this->listeners = [];
}
}
public function listeners($event)
{
return isset($this->listeners[$event]) ? $this->listeners[$event] : [];
}
public function emit($event, array $arguments = [])
{
foreach ($this->listeners($event) as $listener) {
call_user_func_array($listener, $arguments);
}
}
}

View File

@@ -1,235 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement\Tests;
use Evenement\EventEmitter;
class EventEmitterTest extends \PHPUnit_Framework_TestCase
{
private $emitter;
public function setUp()
{
$this->emitter = new EventEmitter();
}
public function testAddListenerWithLambda()
{
$this->emitter->on('foo', function () {});
}
public function testAddListenerWithMethod()
{
$listener = new Listener();
$this->emitter->on('foo', [$listener, 'onFoo']);
}
public function testAddListenerWithStaticMethod()
{
$this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']);
}
public function testAddListenerWithInvalidListener()
{
try {
$this->emitter->on('foo', 'not a callable');
$this->fail();
} catch (\Exception $e) {
}
}
public function testOnce()
{
$listenerCalled = 0;
$this->emitter->once('foo', function () use (&$listenerCalled) {
$listenerCalled++;
});
$this->assertSame(0, $listenerCalled);
$this->emitter->emit('foo');
$this->assertSame(1, $listenerCalled);
$this->emitter->emit('foo');
$this->assertSame(1, $listenerCalled);
}
public function testOnceWithArguments()
{
$capturedArgs = [];
$this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) {
$capturedArgs = array($a, $b);
});
$this->emitter->emit('foo', array('a', 'b'));
$this->assertSame(array('a', 'b'), $capturedArgs);
}
public function testEmitWithoutArguments()
{
$listenerCalled = false;
$this->emitter->on('foo', function () use (&$listenerCalled) {
$listenerCalled = true;
});
$this->assertSame(false, $listenerCalled);
$this->emitter->emit('foo');
$this->assertSame(true, $listenerCalled);
}
public function testEmitWithOneArgument()
{
$test = $this;
$listenerCalled = false;
$this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) {
$listenerCalled = true;
$test->assertSame('bar', $value);
});
$this->assertSame(false, $listenerCalled);
$this->emitter->emit('foo', ['bar']);
$this->assertSame(true, $listenerCalled);
}
public function testEmitWithTwoArguments()
{
$test = $this;
$listenerCalled = false;
$this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) {
$listenerCalled = true;
$test->assertSame('bar', $arg1);
$test->assertSame('baz', $arg2);
});
$this->assertSame(false, $listenerCalled);
$this->emitter->emit('foo', ['bar', 'baz']);
$this->assertSame(true, $listenerCalled);
}
public function testEmitWithNoListeners()
{
$this->emitter->emit('foo');
$this->emitter->emit('foo', ['bar']);
$this->emitter->emit('foo', ['bar', 'baz']);
}
public function testEmitWithTwoListeners()
{
$listenersCalled = 0;
$this->emitter->on('foo', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->emitter->on('foo', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->assertSame(2, $listenersCalled);
}
public function testRemoveListenerMatching()
{
$listenersCalled = 0;
$listener = function () use (&$listenersCalled) {
$listenersCalled++;
};
$this->emitter->on('foo', $listener);
$this->emitter->removeListener('foo', $listener);
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->assertSame(0, $listenersCalled);
}
public function testRemoveListenerNotMatching()
{
$listenersCalled = 0;
$listener = function () use (&$listenersCalled) {
$listenersCalled++;
};
$this->emitter->on('foo', $listener);
$this->emitter->removeListener('bar', $listener);
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->assertSame(1, $listenersCalled);
}
public function testRemoveAllListenersMatching()
{
$listenersCalled = 0;
$this->emitter->on('foo', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->emitter->removeAllListeners('foo');
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->assertSame(0, $listenersCalled);
}
public function testRemoveAllListenersNotMatching()
{
$listenersCalled = 0;
$this->emitter->on('foo', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->emitter->removeAllListeners('bar');
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->assertSame(1, $listenersCalled);
}
public function testRemoveAllListenersWithoutArguments()
{
$listenersCalled = 0;
$this->emitter->on('foo', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->emitter->on('bar', function () use (&$listenersCalled) {
$listenersCalled++;
});
$this->emitter->removeAllListeners();
$this->assertSame(0, $listenersCalled);
$this->emitter->emit('foo');
$this->emitter->emit('bar');
$this->assertSame(0, $listenersCalled);
}
}

View File

@@ -1,23 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Evenement\Tests;
class Listener
{
public function onFoo()
{
}
public static function onBar()
{
}
}

View File

@@ -1,13 +0,0 @@
<?php
/*
* This file is part of Evenement.
*
* (c) Igor Wiedler <igor@wiedler.ch>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$loader = require __DIR__.'/../vendor/autoload.php';
$loader->add('Evenement\Tests', __DIR__);

View File

@@ -1,27 +0,0 @@
# Ingore common cruft
.DS_STORE
coverage
.idea
# Ignore binary files
guzzle.phar
guzzle-min.phar
# Ignore potentially sensitive phpunit file
phpunit.xml
# Ignore composer generated files
composer.phar
composer.lock
composer-test.lock
vendor/
# Ignore build files
build/
phing/build.properties
# Ignore subsplit working directory
.subsplit
docs/_build
docs/*.pyc

View File

@@ -1,17 +0,0 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
before_script:
- curl --version
- pecl install uri_template-beta || echo "pecl uri_template not available"
- composer self-update
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
script: composer test

View File

@@ -1,751 +0,0 @@
# CHANGELOG
## 3.9.3 - 2015-03-18
* Ensuring Content-Length is not stripped from a request when it is `0`.
* Added more information to stream wrapper exceptions.
* Message parser will no longer throw warnings for malformed messages.
* Giving a valid cache TTL when max-age is 0.
## 3.9.2 - 2014-09-10
* Retrying "Connection died, retrying a fresh connect" curl errors.
* Automatically extracting the cacert from the phar in client constructor.
* Added EntityBody support for OPTIONS requests.
## 3.9.1 - 2014-05-07
* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop.
* Added a fix to the stream checksum function so that when the first read
returns a falsey value, it still continues to consume the stream until EOF.
## 3.9.0 - 2014-04-23
* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value
with no trailing "=". See dc1d824277.
* No longer performing an MD5 check on the cacert each time the phar is used,
but rather copying the cacert to the temp directory.
* `"0"` can now be added as a URL path
* Deleting cookies that are set to empty
* If-Modified-Since is no longer unnecessarily added to the CachePlugin
* Cookie path matching now follows RFC 6265 s5.1.4
* Updated service descriptions are now added to a service client's composite
factory.
* MockPlugin now throws an exception if the queue is empty.
* Properly parsing URLs that start with "http" but are not absolute
* Added the ability to configure the curl_multi_select timeout setting
* OAuth parameters are now sorted using lexicographical byte value ordering
* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin
## 3.8.1 -2014-01-28
* Bug: Always using GET requests when redirecting from a 303 response
* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
`Guzzle\Http\ClientInterface::setSslVerification()`
* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
* Bug: The body of a request can now be set to `"0"`
* Sending PHP stream requests no longer forces `HTTP/1.0`
* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
each sub-exception
* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
clobbering everything).
* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
* Now properly escaping the regular expression delimiter when matching Cookie domains.
* Network access is now disabled when loading XML documents
## 3.8.0 - 2013-12-05
* Added the ability to define a POST name for a file
* JSON response parsing now properly walks additionalProperties
* cURL error code 18 is now retried automatically in the BackoffPlugin
* Fixed a cURL error when URLs contain fragments
* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
CurlExceptions
* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
* Fixed a bug that was encountered when parsing empty header parameters
* UriTemplate now has a `setRegex()` method to match the docs
* The `debug` request parameter now checks if it is truthy rather than if it exists
* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
* Added the ability to combine URLs using strict RFC 3986 compliance
* Command objects can now return the validation errors encountered by the command
* Various fixes to cache revalidation (#437 and 29797e5)
* Various fixes to the AsyncPlugin
* Cleaned up build scripts
## 3.7.4 - 2013-10-02
* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
(see https://github.com/aws/aws-sdk-php/issues/147)
* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
* Updated the bundled cacert.pem (#419)
* OauthPlugin now supports adding authentication to headers or query string (#425)
## 3.7.3 - 2013-09-08
* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
`CommandTransferException`.
* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
* Schemas are only injected into response models when explicitly configured.
* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
an EntityBody.
* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
* Bug fix: Visiting XML attributes first before visting XML children when serializing requests
* Bug fix: Properly parsing headers that contain commas contained in quotes
* Bug fix: mimetype guessing based on a filename is now case-insensitive
## 3.7.2 - 2013-08-02
* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
See https://github.com/guzzle/guzzle/issues/371
* Bug fix: Cookie domains are now matched correctly according to RFC 6265
See https://github.com/guzzle/guzzle/issues/377
* Bug fix: GET parameters are now used when calculating an OAuth signature
* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
See https://github.com/guzzle/guzzle/issues/379
* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
https://github.com/guzzle/guzzle/pull/380
* cURL multi cleanup and optimizations
## 3.7.1 - 2013-07-05
* Bug fix: Setting default options on a client now works
* Bug fix: Setting options on HEAD requests now works. See #352
* Bug fix: Moving stream factory before send event to before building the stream. See #353
* Bug fix: Cookies no longer match on IP addresses per RFC 6265
* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
* Added `cert` and `ssl_key` as request options
* `Host` header can now diverge from the host part of a URL if the header is set manually
* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
* OAuth parameters are only added via the plugin if they aren't already set
* Exceptions are now thrown when a URL cannot be parsed
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
## 3.7.0 - 2013-06-10
* See UPGRADING.md for more information on how to upgrade.
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
request. You can pass a 'request.options' configuration setting to a client to apply default request options to
every request created by a client (e.g. default query string variables, headers, curl options, etc).
* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
See `Guzzle\Http\StaticClient::mount`.
* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
created by a command (e.g. custom headers, query string variables, timeout settings, etc).
* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
headers of a response
* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
(e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
* ServiceBuilders now support storing and retrieving arbitrary data
* CachePlugin can now purge all resources for a given URI
* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
* CachePlugin now uses the Vary header to determine if a resource is a cache hit
* `Guzzle\Http\Message\Response` now implements `\Serializable`
* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
Symfony users can still use the old version of Monolog.
* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
* Several performance improvements to `Guzzle\Common\Collection`
* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
createRequest, head, delete, put, patch, post, options, prepareRequest
* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
`Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
resource, string, or EntityBody into the $options parameter to specify the download location of the response.
* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
default `array()`
* Added `Guzzle\Stream\StreamInterface::isRepeatable`
* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
* Removed `Guzzle\Http\Message\RequestInterface::canCache`
* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
`Guzzle\Common\Version::$emitWarnings` to true.
* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
`$request->getResponseBody()->isRepeatable()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
`Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
These will work through Guzzle 4.0
* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
* Marked `Guzzle\Common\Collection::inject()` as deprecated.
* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
CacheStorageInterface. These two objects and interface will be removed in a future version.
* Always setting X-cache headers on cached responses
* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
$request, Response $response);`
* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
* Added `CacheStorageInterface::purge($url)`
* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
$plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
CanCacheStrategyInterface $canCache = null)`
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
## 3.6.0 - 2013-05-29
* ServiceDescription now implements ToArrayInterface
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
* Guzzle can now correctly parse incomplete URLs
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
CacheControl header implementation.
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
but are a no-op until removed.
* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
`Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
on a request while the request is still being transferred
* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
instead.
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
* Added the ability to cast Model objects to a string to view debug information.
## 3.5.0 - 2013-05-13
* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
itself from the EventDispatcher)
* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
non-existent key
* Bug: All __call() method arguments are now required (helps with mocking frameworks)
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
to help with refcount based garbage collection of resources created by sending a request
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the
HistoryPlugin for a history.
* Added a `responseBody` alias for the `response_body` location
* Refactored internals to no longer rely on Response::getRequest()
* HistoryPlugin can now be cast to a string
* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
and responses that are sent over the wire
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
## 3.4.3 - 2013-04-30
* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
* Added a check to re-extract the temp cacert bundle from the phar before sending each request
## 3.4.2 - 2013-04-29
* Bug fix: Stream objects now work correctly with "a" and "a+" modes
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
* Bug fix: AsyncPlugin no longer forces HEAD requests
* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
* Setting a response on a request will write to the custom request body from the response body if one is specified
* LogPlugin now writes to php://output when STDERR is undefined
* Added the ability to set multiple POST files for the same key in a single call
* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
* Added the ability to queue CurlExceptions to the MockPlugin
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
* Configuration loading now allows remote files
## 3.4.1 - 2013-04-16
* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
* Exceptions are now properly grouped when sending requests in parallel
* Redirects are now properly aggregated when a multi transaction fails
* Redirects now set the response on the original object even in the event of a failure
* Bug fix: Model names are now properly set even when using $refs
* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
* Added support for oauth_callback in OAuth signatures
* Added support for oauth_verifier in OAuth signatures
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
## 3.4.0 - 2013-04-11
* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
* Bug fix: Added `number` type to service descriptions.
* Bug fix: empty parameters are removed from an OAuth signature
* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
* Bug fix: Fixed "array to string" error when validating a union of types in a service description
* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
the Content-Type can be determined based on the entity body or the path of the request.
* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
* Added support for a PSR-3 LogAdapter.
* Added a `command.after_prepare` event
* Added `oauth_callback` parameter to the OauthPlugin
* Added the ability to create a custom stream class when using a stream factory
* Added a CachingEntityBody decorator
* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
POST fields or files (the latter is only used when emulating a form POST in the browser).
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
## 3.3.1 - 2013-03-10
* Added the ability to create PHP streaming responses from HTTP requests
* Bug fix: Running any filters when parsing response headers with service descriptions
* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
response location visitors.
* Bug fix: Removed the possibility of creating configuration files with circular dependencies
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
## 3.3.0 - 2013-03-03
* A large number of performance optimizations have been made
* Bug fix: Added 'wb' as a valid write mode for streams
* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
* BC: Removed `Guzzle\Http\Utils` class
* BC: Setting a service description on a client will no longer modify the client's command factories.
* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
lowercase
* Operation parameter objects are now lazy loaded internally
* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
* Added support for instantiating responseType=class responseClass classes. Classes must implement
`Guzzle\Service\Command\ResponseClassInterface`
* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
additional properties also support locations and can be used to parse JSON responses where the outermost part of the
JSON is an array
* Added support for nested renaming of JSON models (rename sentAs to name)
* CachePlugin
* Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
* Debug headers can now added to cached response in the CachePlugin
## 3.2.0 - 2013-02-14
* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
* URLs with no path no longer contain a "/" by default
* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
* BadResponseException no longer includes the full request and response message
* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
* xmlEncoding can now be customized for the XML declaration of a XML service description operation
* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
aggregation and no longer uses callbacks
* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
* Bug fix: Filters were not always invoked for array service description parameters
* Bug fix: Redirects now use a target response body rather than a temporary response body
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
## 3.1.2 - 2013-01-27
* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
## 3.1.1 - 2013-01-20
* Adding wildcard support to Guzzle\Common\Collection::getPath()
* Adding alias support to ServiceBuilder configs
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
## 3.1.0 - 2013-01-12
* BC: CurlException now extends from RequestException rather than BadResponseException
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
* Added getData to ServiceDescriptionInterface
* Added context array to RequestInterface::setState()
* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
* Bug: Adding required content-type when JSON request visitor adds JSON to a command
* Bug: Fixing the serialization of a service description with custom data
* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
an array of successful and failed responses
* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
* Added Guzzle\Http\IoEmittingEntityBody
* Moved command filtration from validators to location visitors
* Added `extends` attributes to service description parameters
* Added getModels to ServiceDescriptionInterface
## 3.0.7 - 2012-12-19
* Fixing phar detection when forcing a cacert to system if null or true
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
* Cleaning up `Guzzle\Common\Collection::inject` method
* Adding a response_body location to service descriptions
## 3.0.6 - 2012-12-09
* CurlMulti performance improvements
* Adding setErrorResponses() to Operation
* composer.json tweaks
## 3.0.5 - 2012-11-18
* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
* Bug: Response body can now be a string containing "0"
* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
* Added support for XML attributes in service description responses
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
* Added better mimetype guessing to requests and post files
## 3.0.4 - 2012-11-11
* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
* Bug: Using the system cacert bundle when using the Phar
* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
* Enhanced cookie jar de-duplication
* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
## 3.0.3 - 2012-11-04
* Implementing redirects in PHP rather than cURL
* Added PECL URI template extension and using as default parser if available
* Bug: Fixed Content-Length parsing of Response factory
* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
* Adding ToArrayInterface throughout library
* Fixing OauthPlugin to create unique nonce values per request
## 3.0.2 - 2012-10-25
* Magic methods are enabled by default on clients
* Magic methods return the result of a command
* Service clients no longer require a base_url option in the factory
* Bug: Fixed an issue with URI templates where null template variables were being expanded
## 3.0.1 - 2012-10-22
* Models can now be used like regular collection objects by calling filter, map, etc
* Models no longer require a Parameter structure or initial data in the constructor
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
## 3.0.0 - 2012-10-15
* Rewrote service description format to be based on Swagger
* Now based on JSON schema
* Added nested input structures and nested response models
* Support for JSON and XML input and output models
* Renamed `commands` to `operations`
* Removed dot class notation
* Removed custom types
* Broke the project into smaller top-level namespaces to be more component friendly
* Removed support for XML configs and descriptions. Use arrays or JSON files.
* Removed the Validation component and Inspector
* Moved all cookie code to Guzzle\Plugin\Cookie
* Magic methods on a Guzzle\Service\Client now return the command un-executed.
* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
* Now shipping with cURL's CA certs and using it by default
* Added previousResponse() method to response objects
* No longer sending Accept and Accept-Encoding headers on every request
* Only sending an Expect header by default when a payload is greater than 1MB
* Added/moved client options:
* curl.blacklist to curl.option.blacklist
* Added ssl.certificate_authority
* Added a Guzzle\Iterator component
* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
* Added a more robust caching plugin
* Added setBody to response objects
* Updating LogPlugin to use a more flexible MessageFormatter
* Added a completely revamped build process
* Cleaning up Collection class and removing default values from the get method
* Fixed ZF2 cache adapters
## 2.8.8 - 2012-10-15
* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
## 2.8.7 - 2012-09-30
* Bug: Fixed config file aliases for JSON includes
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
* Bug: Hardening request and response parsing to account for missing parts
* Bug: Fixed PEAR packaging
* Bug: Fixed Request::getInfo
* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
* Adding the ability for the namespace Iterator factory to look in multiple directories
* Added more getters/setters/removers from service descriptions
* Added the ability to remove POST fields from OAuth signatures
* OAuth plugin now supports 2-legged OAuth
## 2.8.6 - 2012-09-05
* Added the ability to modify and build service descriptions
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
* Added a `json` parameter location
* Now allowing dot notation for classes in the CacheAdapterFactory
* Using the union of two arrays rather than an array_merge when extending service builder services and service params
* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
in service builder config files.
* Services defined in two different config files that include one another will by default replace the previously
defined service, but you can now create services that extend themselves and merge their settings over the previous
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
'_default' with a default JSON configuration file.
## 2.8.5 - 2012-08-29
* Bug: Suppressed empty arrays from URI templates
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
* Added support for HTTP responses that do not contain a reason phrase in the start-line
* AbstractCommand commands are now invokable
* Added a way to get the data used when signing an Oauth request before a request is sent
## 2.8.4 - 2012-08-15
* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
* Added additional response status codes
* Removed SSL information from the default User-Agent header
* DELETE requests can now send an entity body
* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
* Added the ability of the MockPlugin to consume mocked request bodies
* LogPlugin now exposes request and response objects in the extras array
## 2.8.3 - 2012-07-30
* Bug: Fixed a case where empty POST requests were sent as GET requests
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
* Added multiple inheritance to service description commands
* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()``
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
## 2.8.2 - 2012-07-24
* Bug: Query string values set to 0 are no longer dropped from the query string
* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
* Bug: ``+`` is now treated as an encoded space when parsing query strings
* QueryString and Collection performance improvements
* Allowing dot notation for class paths in filters attribute of a service descriptions
## 2.8.1 - 2012-07-16
* Loosening Event Dispatcher dependency
* POST redirects can now be customized using CURLOPT_POSTREDIR
## 2.8.0 - 2012-07-15
* BC: Guzzle\Http\Query
* Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
* Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
* Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
* Changed the aggregation functions of QueryString to be static methods
* Can now use fromString() with querystrings that have a leading ?
* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters
* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
* Cookies are no longer URL decoded by default
* Bug: URI template variables set to null are no longer expanded
## 2.7.2 - 2012-07-02
* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
* CachePlugin now allows for a custom request parameter function to check if a request can be cached
* Bug fix: CachePlugin now only caches GET and HEAD requests by default
* Bug fix: Using header glue when transferring headers over the wire
* Allowing deeply nested arrays for composite variables in URI templates
* Batch divisors can now return iterators or arrays
## 2.7.1 - 2012-06-26
* Minor patch to update version number in UA string
* Updating build process
## 2.7.0 - 2012-06-25
* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
* BC: Removed magic setX methods from commands
* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
* Added the ability to set POST fields and files in a service description
* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
* Adding a command.before_prepare event to clients
* Added BatchClosureTransfer and BatchClosureDivisor
* BatchTransferException now includes references to the batch divisor and transfer strategies
* Fixed some tests so that they pass more reliably
* Added Guzzle\Common\Log\ArrayLogAdapter
## 2.6.6 - 2012-06-10
* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
* BC: Removing Guzzle\Service\Command\CommandSet
* Adding generic batching system (replaces the batch queue plugin and command set)
* Updating ZF cache and log adapters and now using ZF's composer repository
* Bug: Setting the name of each ApiParam when creating through an ApiCommand
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
* Bug: Changed the default cookie header casing back to 'Cookie'
## 2.6.5 - 2012-06-03
* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
* BC: Renaming methods in the CookieJarInterface
* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
* Making the default glue for HTTP headers ';' instead of ','
* Adding a removeValue to Guzzle\Http\Message\Header
* Adding getCookies() to request interface.
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
## 2.6.4 - 2012-05-30
* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
* Bug: Fixing magic method command calls on clients
* Bug: Email constraint only validates strings
* Bug: Aggregate POST fields when POST files are present in curl handle
* Bug: Fixing default User-Agent header
* Bug: Only appending or prepending parameters in commands if they are specified
* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
* Allowing the use of dot notation for class namespaces when using instance_of constraint
* Added any_match validation constraint
* Added an AsyncPlugin
* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
* Allowing the result of a command object to be changed
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
## 2.6.3 - 2012-05-23
* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
* You can now use an array of data when creating PUT request bodies in the request factory.
* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
* [Http] Adding support for Content-Type in multipart POST uploads per upload
* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
* Adding more POST data operations for easier manipulation of POST data.
* You can now set empty POST fields.
* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
* CS updates
## 2.6.2 - 2012-05-19
* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
## 2.6.1 - 2012-05-19
* [BC] Removing 'path' support in service descriptions. Use 'uri'.
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
* [BC] Removing Guzzle\Common\XmlElement.
* All commands, both dynamic and concrete, have ApiCommand objects.
* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
## 2.6.0 - 2012-05-15
* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
* [BC] Executing a Command returns the result of the command rather than the command
* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
* [BC] Guzzle\Guzzle is now deprecated
* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
* Adding Guzzle\Version class to give version information about Guzzle
* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
* ServiceDescription and ServiceBuilder are now cacheable using similar configs
* Changing the format of XML and JSON service builder configs. Backwards compatible.
* Cleaned up Cookie parsing
* Trimming the default Guzzle User-Agent header
* Adding a setOnComplete() method to Commands that is called when a command completes
* Keeping track of requests that were mocked in the MockPlugin
* Fixed a caching bug in the CacheAdapterFactory
* Inspector objects can be injected into a Command object
* Refactoring a lot of code and tests to be case insensitive when dealing with headers
* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
* Adding the ability to set global option overrides to service builder configs
* Adding the ability to include other service builder config files from within XML and JSON files
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
## 2.5.0 - 2012-05-08
* Major performance improvements
* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
* Added the ability to passed parameters to all requests created by a client
* Added callback functionality to the ExponentialBackoffPlugin
* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
* Rewinding request stream bodies when retrying requests
* Exception is thrown when JSON response body cannot be decoded
* Added configurable magic method calls to clients and commands. This is off by default.
* Fixed a defect that added a hash to every parsed URL part
* Fixed duplicate none generation for OauthPlugin.
* Emitting an event each time a client is generated by a ServiceBuilder
* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
* cache.* request parameters should be renamed to params.cache.*
* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc). See CurlHandle.
* Added the ability to disable type validation of service descriptions
* ServiceDescriptions and ServiceBuilders are now Serializable

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