public static function newFromDictionary(HarbormasterBuildTarget $build_target, array $dict) { $obj = self::initializeNewUnitMessage($build_target); $spec = self::getParameterSpec(); $spec = ipull($spec, 'type'); // We're just going to ignore extra keys for now, to make it easier to // add stuff here later on. $dict = array_select_keys($dict, array_keys($spec)); PhutilTypeSpec::checkMap($dict, $spec); $obj->setEngine(idx($dict, 'engine', '')); $obj->setNamespace(idx($dict, 'namespace', '')); $obj->setName($dict['name']); $obj->setResult($dict['result']); $obj->setDuration((double) idx($dict, 'duration')); $path = idx($dict, 'path'); if (strlen($path)) { $obj->setProperty('path', $path); } $coverage = idx($dict, 'coverage'); if ($coverage) { $obj->setProperty('coverage', $coverage); } $details = idx($dict, 'details'); if ($details) { $obj->setProperty('details', $details); } return $obj; }
protected function executeAcquireLease(DrydockResource $resource, DrydockLease $lease) { // Because preallocated resources are manually created, we should verify // we have all the information we need. PhutilTypeSpec::checkMap($resource->getAttributesForTypeSpec(array('platform', 'host', 'port', 'credential', 'path')), array('platform' => 'string', 'host' => 'string', 'port' => 'string', 'credential' => 'string', 'path' => 'string')); $v_platform = $resource->getAttribute('platform'); $v_path = $resource->getAttribute('path'); // Similar to DrydockLocalHostBlueprint, we create a folder // on the remote host that the lease can use. $lease_id = $lease->getID(); // Can't use DIRECTORY_SEPERATOR here because that is relevant to // the platform we're currently running on, not the platform we are // remoting to. $separator = '/'; if ($v_platform === 'windows') { $separator = '\\'; } // Clean up the directory path a little. $base_path = rtrim($v_path, '/'); $base_path = rtrim($base_path, '\\'); $full_path = $base_path . $separator . $lease_id; $cmd = $lease->getInterface('command'); $cmd->execx('mkdir %s', $full_path); $lease->setAttribute('path', $full_path); }
protected function readBookConfiguration($book_path) { if ($book_path === null) { throw new PhutilArgumentUsageException('Specify a Diviner book configuration file with --book.'); } $book_data = Filesystem::readFile($book_path); $book = json_decode($book_data, true); if (!is_array($book)) { throw new PhutilArgumentUsageException("Book configuration '{$book_path}' is not in JSON format."); } PhutilTypeSpec::checkMap($book, array('name' => 'string', 'title' => 'optional string', 'short' => 'optional string', 'preface' => 'optional string', 'root' => 'optional string', 'uri.source' => 'optional string', 'rules' => 'optional map<regex, string>', 'exclude' => 'optional regex|list<regex>', 'groups' => 'optional map<string, map<string, wild>>')); // If the book specifies a "root", resolve it; otherwise, use the directory // the book configuration file lives in. $full_path = dirname(Filesystem::resolvePath($book_path)); if (empty($book['root'])) { $book['root'] = '.'; } $book['root'] = Filesystem::resolvePath($book['root'], $full_path); if (!preg_match('/^[a-z][a-z-]*\\z/', $book['name'])) { $name = $book['name']; throw new PhutilArgumentUsageException("Book configuration '{$book_path}' has name '{$name}', but book names " . "must include only lowercase letters and hyphens."); } foreach (idx($book, 'groups', array()) as $group) { PhutilTypeSpec::checkmap($group, array('name' => 'string', 'include' => 'optional regex|list<regex>')); } $this->bookConfigPath = $book_path; $this->config = $book; }
public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception(pht('Logo configuration is not valid: value must be a dictionary.')); } PhutilTypeSpec::checkMap($value, array('logoImagePHID' => 'optional string|null', 'wordmarkText' => 'optional string|null')); }
public static function validateConfiguration($config) { if (!is_array($config)) { throw new Exception(pht('Configuration is not valid. Maniphest points configuration must ' . 'be a dictionary.')); } PhutilTypeSpec::checkMap($config, array('enabled' => 'optional bool', 'label' => 'optional string', 'action' => 'optional string')); }
/** * @task apps */ public static function buildFieldList($base_class, array $spec, $object, array $options = array()) { PhutilTypeSpec::checkMap($options, array('withDisabled' => 'optional bool')); $field_objects = id(new PhutilSymbolLoader())->setAncestorClass($base_class)->loadObjects(); $fields = array(); $from_map = array(); foreach ($field_objects as $field_object) { $current_class = get_class($field_object); foreach ($field_object->createFields($object) as $field) { $key = $field->getFieldKey(); if (isset($fields[$key])) { throw new Exception(pht("Both '%s' and '%s' define a custom field with " . "field key '%s'. Field keys must be unique.", $from_map[$key], $current_class, $key)); } $from_map[$key] = $current_class; $fields[$key] = $field; } } foreach ($fields as $key => $field) { if (!$field->isFieldEnabled()) { unset($fields[$key]); } } $fields = array_select_keys($fields, array_keys($spec)) + $fields; if (empty($options['withDisabled'])) { foreach ($fields as $key => $field) { $config = idx($spec, $key, array()) + array('disabled' => $field->shouldDisableByDefault()); if (!empty($config['disabled'])) { if ($field->canDisableField()) { unset($fields[$key]); } } } } return $fields; }
public function loadDictionary($path) { $root = $this->getProjectRoot(); $path = Filesystem::resolvePath($path, $root); $dict = phutil_json_decode(Filesystem::readFile($path)); PhutilTypeSpec::checkMap($dict, array('rules' => 'map<string, map<string, string>>')); $rules = $dict['rules']; $this->dictionaries[] = $path; $this->exactWordRules = array_merge($this->exactWordRules, idx($rules, 'exact', array())); $this->partialWordRules = array_merge($this->partialWordRules, idx($rules, 'partial', array())); }
public function buildTestEngines() { $working_copy = $this->getWorkingCopy(); $config_path = $working_copy->getProjectPath('.arcunit'); if (!Filesystem::pathExists($config_path)) { throw new ArcanistUsageException(pht("Unable to find '%s' file to configure test engines. Create an " . "'%s' file in the root directory of the working copy.", '.arcunit', '.arcunit')); } $data = Filesystem::readFile($config_path); $config = null; try { $config = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht("Expected '%s' file to be a valid JSON file, but " . "failed to decode '%s'.", '.arcunit', $config_path), $ex); } $test_engines = $this->loadAvailableTestEngines(); try { PhutilTypeSpec::checkMap($config, array('engines' => 'map<string, map<string, wild>>')); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException(pht("Error in parsing '%s' file.", $config_path), $ex); } $built_test_engines = array(); $all_paths = $this->getPaths(); foreach ($config['engines'] as $name => $spec) { $type = idx($spec, 'type'); if ($type !== null) { if (empty($test_engines[$type])) { throw new ArcanistUsageException(pht("Test engine '%s' specifies invalid type '%s'. " . "Available test engines are: %s.", $name, $type, implode(', ', array_keys($test_engines)))); } $test_engine = clone $test_engines[$type]; } else { // We'll raise an error below about the invalid "type" key. // TODO: Can we just do the type check first, and simplify this a bit? $test_engine = null; } try { PhutilTypeSpec::checkMap($spec, array('type' => 'string', 'include' => 'optional regex | list<regex>', 'exclude' => 'optional regex | list<regex>')); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException(pht("Error in parsing '%s' file, for test engine '%s'.", '.arcunit', $name), $ex); } if ($all_paths) { $include = (array) idx($spec, 'include', array()); $exclude = (array) idx($spec, 'exclude', array()); $paths = $this->matchPaths($all_paths, $include, $exclude); $test_engine->setPaths($paths); } $built_test_engines[] = $test_engine; } return $built_test_engines; }
public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception(pht('Keyring configuration is not valid: value must be a ' . 'list of encryption keys.')); } foreach ($value as $index => $spec) { if (!is_array($spec)) { throw new Exception(pht('Keyring configuration is not valid: each entry in the list must ' . 'be a dictionary describing an encryption key, but the value ' . 'with index "%s" is not a dictionary.', $index)); } } $map = array(); $defaults = array(); foreach ($value as $index => $spec) { try { PhutilTypeSpec::checkMap($spec, array('name' => 'string', 'type' => 'string', 'material.base64' => 'string', 'default' => 'optional bool')); } catch (Exception $ex) { throw new Exception(pht('Keyring configuration has an invalid key specification (at ' . 'index "%s"): %s.', $index, $ex->getMessage())); } $name = $spec['name']; if (isset($map[$name])) { throw new Exception(pht('Keyring configuration is invalid: it describes multiple keys ' . 'with the same name ("%s"). Each key must have a unique name.', $name)); } $map[$name] = true; if (idx($spec, 'default')) { $defaults[] = $name; } $type = $spec['type']; switch ($type) { case 'aes-256-cbc': if (!function_exists('openssl_encrypt')) { throw new Exception(pht('Keyring is configured with a "%s" key, but the PHP OpenSSL ' . 'extension is not installed. Install the OpenSSL extension ' . 'to enable encryption.', $type)); } $material = $spec['material.base64']; $material = base64_decode($material, true); if ($material === false) { throw new Exception(pht('Keyring specifies an invalid key ("%s"): key material ' . 'should be base64 encoded.', $name)); } if (strlen($material) != 32) { throw new Exception(pht('Keyring specifies an invalid key ("%s"): key material ' . 'should be 32 bytes (256 bits) but has length %s.', $name, new PhutilNumber(strlen($material)))); } break; default: throw new Exception(pht('Keyring configuration is invalid: it describes a key with ' . 'type "%s", but this type is unknown.', $type)); } } if (count($defaults) > 1) { throw new Exception(pht('Keyring configuration is invalid: it describes multiple default ' . 'encryption keys. No more than one key may be the default key. ' . 'Keys currently configured as defaults: %s.', implode(', ', $defaults))); } }
public function validateTransactions($object, array $xactions) { $errors = array(); if (!$xactions) { return $errors; } $old = mpull($object->getPaths(), 'getRef'); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); // Check that we have a list of paths. if (!is_array($new)) { $errors[] = $this->newInvalidError(pht('Path specification must be a list of paths.'), $xaction); continue; } // Check that each item in the list is formatted properly. $type_exception = null; foreach ($new as $key => $value) { try { PhutilTypeSpec::checkMap($value, array('repositoryPHID' => 'string', 'path' => 'string', 'excluded' => 'optional wild')); } catch (PhutilTypeCheckException $ex) { $errors[] = $this->newInvalidError(pht('Path specification list contains invalid value ' . 'in key "%s": %s.', $key, $ex->getMessage()), $xaction); $type_exception = $ex; } } if ($type_exception) { continue; } // Check that any new paths reference legitimate repositories which // the viewer has permission to see. list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); if ($add) { $repository_phids = ipull($add, 'repositoryPHID'); $repositories = id(new PhabricatorRepositoryQuery())->setViewer($this->getActor())->withPHIDs($repository_phids)->execute(); $repositories = mpull($repositories, null, 'getPHID'); foreach ($add as $ref) { $repository_phid = $ref['repositoryPHID']; if (isset($repositories[$repository_phid])) { continue; } $errors[] = $this->newInvalidError(pht('Path specification list references repository PHID "%s", ' . 'but that is not a valid, visible repository.', $repository_phid)); } } } return $errors; }
public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception(pht('Database cluster configuration is not valid: value must be a ' . 'list of database hosts.')); } foreach ($value as $index => $spec) { if (!is_array($spec)) { throw new Exception(pht('Database cluster configuration is not valid: each entry in the ' . 'list must be a dictionary describing a database host, but ' . 'the value with index "%s" is not a dictionary.', $index)); } } $masters = array(); $map = array(); foreach ($value as $index => $spec) { try { PhutilTypeSpec::checkMap($spec, array('host' => 'string', 'role' => 'string', 'port' => 'optional int', 'user' => 'optional string', 'pass' => 'optional string', 'disabled' => 'optional bool')); } catch (Exception $ex) { throw new Exception(pht('Database cluster configuration has an invalid host ' . 'specification (at index "%s"): %s.', $index, $ex->getMessage())); } $role = $spec['role']; $host = $spec['host']; $port = idx($spec, 'port'); switch ($role) { case 'master': case 'replica': break; default: throw new Exception(pht('Database cluster configuration describes an invalid ' . 'host ("%s", at index "%s") with an unrecognized role ("%s"). ' . 'Valid roles are "%s" or "%s".', $spec['host'], $index, $spec['role'], 'master', 'replica')); } if ($role === 'master') { $masters[] = $host; } // We can't guarantee that you didn't just give the same host two // different names in DNS, but this check can catch silly copy/paste // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { throw new Exception(pht('Database cluster configuration is invalid: it describes the ' . 'same host ("%s") multiple times. Each host should appear only ' . 'once in the list.', $host)); } $map[$key] = true; } if (count($masters) > 1) { throw new Exception(pht('Database cluster configuration is invalid: it describes multiple ' . 'masters. No more than one host may be a master. Hosts currently ' . 'configured as masters: %s.', implode(', ', $masters))); } }
public static function newFromDictionary(HarbormasterBuildTarget $build_target, array $dict) { $obj = self::initializeNewLintMessage($build_target); $spec = array('path' => 'string', 'line' => 'optional int', 'char' => 'optional int', 'code' => 'string', 'severity' => 'string', 'name' => 'string', 'description' => 'optional string'); // We're just going to ignore extra keys for now, to make it easier to // add stuff here later on. $dict = array_select_keys($dict, array_keys($spec)); PhutilTypeSpec::checkMap($dict, $spec); $obj->setPath($dict['path']); $obj->setLine(idx($dict, 'line')); $obj->setCharacterOffset(idx($dict, 'char')); $obj->setCode($dict['code']); $obj->setSeverity($dict['severity']); $obj->setName($dict['name']); $description = idx($dict, 'description'); if (strlen($description)) { $obj->setProperty('description', $description); } return $obj; }
public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); echo tsprintf("%s\n", pht('Reading list of hints from stdin...')); $hints = file_get_contents('php://stdin'); if ($hints === false) { throw new PhutilArgumentUsageException(pht('Failed to read stdin.')); } try { $hints = phutil_json_decode($hints); } catch (Exception $ex) { throw new PhutilArgumentUsageException(pht('Expected a list of hints in JSON format: %s', $ex->getMessage())); } $repositories = array(); foreach ($hints as $idx => $hint) { if (!is_array($hint)) { throw new PhutilArgumentUsageException(pht('Each item in the list of hints should be a JSON object, but ' . 'the item at index "%s" is not.', $idx)); } try { PhutilTypeSpec::checkMap($hint, array('repository' => 'string|int', 'old' => 'string', 'new' => 'optional string|null', 'hint' => 'string')); } catch (Exception $ex) { throw new PhutilArgumentUsageException(pht('Unexpected hint format at index "%s": %s', $idx, $ex->getMessage())); } $repository_identifier = $hint['repository']; $repository = idx($repositories, $repository_identifier); if (!$repository) { $repository = id(new PhabricatorRepositoryQuery())->setViewer($viewer)->withIdentifiers(array($repository_identifier))->executeOne(); if (!$repository) { throw new PhutilArgumentUsageException(pht('Repository identifier "%s" (in hint at index "%s") does not ' . 'identify a valid repository.', $repository_identifier, $idx)); } $repositories[$repository_identifier] = $repository; } PhabricatorRepositoryCommitHint::updateHint($repository->getPHID(), $hint['old'], idx($hint, 'new'), $hint['hint']); echo tsprintf("%s\n", pht('Updated hint for "%s".', $hint['old'])); } }
public function validateProperties(array $properties) { PhutilTypeSpec::checkMap($properties, array('epoch' => 'int')); }
public function addColumn($key, array $column) { PhutilTypeSpec::checkMap($column, array('title' => 'string', 'align' => 'optional string')); $this->columns[$key] = $column; return $this; }
private function buildMoveTransaction(PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $new = $xaction->getNewValue(); if (!is_array($new)) { $this->validateColumnPHID($new); $new = array($new); } $nearby_phids = array(); foreach ($new as $key => $value) { if (!is_array($value)) { $this->validateColumnPHID($value); $value = array('columnPHID' => $value); } PhutilTypeSpec::checkMap($value, array('columnPHID' => 'string', 'beforePHID' => 'optional string', 'afterPHID' => 'optional string')); $new[$key] = $value; if (!empty($value['beforePHID'])) { $nearby_phids[] = $value['beforePHID']; } if (!empty($value['afterPHID'])) { $nearby_phids[] = $value['afterPHID']; } } if ($nearby_phids) { $nearby_objects = id(new PhabricatorObjectQuery())->setViewer($this->getActor())->withPHIDs($nearby_phids)->execute(); $nearby_objects = mpull($nearby_objects, null, 'getPHID'); } else { $nearby_objects = array(); } $column_phids = ipull($new, 'columnPHID'); if ($column_phids) { $columns = id(new PhabricatorProjectColumnQuery())->setViewer($this->getActor())->withPHIDs($column_phids)->execute(); $columns = mpull($columns, null, 'getPHID'); } else { $columns = array(); } $board_phids = mpull($columns, 'getProjectPHID'); $object_phid = $object->getPHID(); $object_phids = $nearby_phids; // Note that we may not have an object PHID if we're creating a new // object. if ($object_phid) { $object_phids[] = $object_phid; } if ($object_phids) { $layout_engine = id(new PhabricatorBoardLayoutEngine())->setViewer($this->getActor())->setBoardPHIDs($board_phids)->setObjectPHIDs($object_phids)->setFetchAllBoards(true)->executeLayout(); } foreach ($new as $key => $spec) { $column_phid = $spec['columnPHID']; $column = idx($columns, $column_phid); if (!$column) { throw new Exception(pht('Column move transaction specifies column PHID "%s", but there ' . 'is no corresponding column with this PHID.', $column_phid)); } $board_phid = $column->getProjectPHID(); $nearby = array(); if (!empty($spec['beforePHID'])) { $nearby['beforePHID'] = $spec['beforePHID']; } if (!empty($spec['afterPHID'])) { $nearby['afterPHID'] = $spec['afterPHID']; } if (count($nearby) > 1) { throw new Exception(pht('Column move transaction moves object to multiple positions. ' . 'Specify only "beforePHID" or "afterPHID", not both.')); } foreach ($nearby as $where => $nearby_phid) { if (empty($nearby_objects[$nearby_phid])) { throw new Exception(pht('Column move transaction specifies object "%s" as "%s", but ' . 'there is no corresponding object with this PHID.', $object_phid, $where)); } $nearby_columns = $layout_engine->getObjectColumns($board_phid, $nearby_phid); $nearby_columns = mpull($nearby_columns, null, 'getPHID'); if (empty($nearby_columns[$column_phid])) { throw new Exception(pht('Column move transaction specifies object "%s" as "%s" in ' . 'column "%s", but this object is not in that column!', $nearby_phid, $where, $column_phid)); } } if ($object_phid) { $old_columns = $layout_engine->getObjectColumns($board_phid, $object_phid); $old_column_phids = mpull($old_columns, 'getPHID'); } else { $old_column_phids = array(); } $spec += array('boardPHID' => $board_phid, 'fromColumnPHIDs' => $old_column_phids); // Check if the object is already in this column, and isn't being moved. // We can just drop this column change if it has no effect. $from_map = array_fuse($spec['fromColumnPHIDs']); $already_here = isset($from_map[$column_phid]); $is_reordering = (bool) $nearby; if ($already_here && !$is_reordering) { unset($new[$key]); } else { $new[$key] = $spec; } } $new = array_values($new); $xaction->setNewValue($new); $more = array(); // If we're moving the object into a column and it does not already belong // in the column, add the appropriate board. For normal columns, this // is the board PHID. For proxy columns, it is the proxy PHID, unless the // object is already a member of some descendant of the proxy PHID. // The major case where this can happen is moves via the API, but it also // happens when a user drags a task from the "Backlog" to a milestone // column. if ($object_phid) { $current_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($object_phid, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $current_phids = array_fuse($current_phids); } else { $current_phids = array(); } $add_boards = array(); foreach ($new as $move) { $column_phid = $move['columnPHID']; $board_phid = $move['boardPHID']; $column = $columns[$column_phid]; $proxy_phid = $column->getProxyPHID(); // If this is a normal column, add the board if the object isn't already // associated. if (!$proxy_phid) { if (!isset($current_phids[$board_phid])) { $add_boards[] = $board_phid; } continue; } // If this is a proxy column but the object is already associated with // the proxy board, we don't need to do anything. if (isset($current_phids[$proxy_phid])) { continue; } // If this a proxy column and the object is already associated with some // descendant of the proxy board, we also don't need to do anything. $descendants = id(new PhabricatorProjectQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withAncestorProjectPHIDs(array($proxy_phid))->execute(); $found_descendant = false; foreach ($descendants as $descendant) { if (isset($current_phids[$descendant->getPHID()])) { $found_descendant = true; break; } } if ($found_descendant) { continue; } // Otherwise, we're moving the object to a proxy column which it is not // a member of yet, so add an association to the column's proxy board. $add_boards[] = $proxy_phid; } if ($add_boards) { $more[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)->setIgnoreOnNoEffect(true)->setNewValue(array('+' => array_fuse($add_boards))); } return $more; }
/** * Simplifies the task of constructing a paging clause across multiple * columns. In the general case, this looks like: * * A > a OR (A = a AND B > b) OR (A = a AND B = b AND C > c) * * To build a clause, specify the name, type, and value of each column * to include: * * $this->buildPagingClauseFromMultipleColumns( * $conn_r, * array( * array( * 'table' => 't', * 'column' => 'title', * 'type' => 'string', * 'value' => $cursor->getTitle(), * 'reverse' => true, * ), * array( * 'table' => 't', * 'column' => 'id', * 'type' => 'int', * 'value' => $cursor->getID(), * ), * ), * array( * 'reversed' => $is_reversed, * )); * * This method will then return a composable clause for inclusion in WHERE. * * @param AphrontDatabaseConnection Connection query will execute on. * @param list<map> Column description dictionaries. * @param map Additional constuction options. * @return string Query clause. * @task paging */ protected final function buildPagingClauseFromMultipleColumns(AphrontDatabaseConnection $conn, array $columns, array $options) { foreach ($columns as $column) { PhutilTypeSpec::checkMap($column, array('table' => 'optional string|null', 'column' => 'string', 'value' => 'wild', 'type' => 'string', 'reverse' => 'optional bool', 'unique' => 'optional bool', 'null' => 'optional string|null')); } PhutilTypeSpec::checkMap($options, array('reversed' => 'optional bool')); $is_query_reversed = idx($options, 'reversed', false); $clauses = array(); $accumulated = array(); $last_key = last_key($columns); foreach ($columns as $key => $column) { $type = $column['type']; $null = idx($column, 'null'); if ($column['value'] === null) { if ($null) { $value = null; } else { throw new Exception(pht('Column "%s" has null value, but does not specify a null ' . 'behavior.', $key)); } } else { switch ($type) { case 'int': $value = qsprintf($conn, '%d', $column['value']); break; case 'float': $value = qsprintf($conn, '%f', $column['value']); break; case 'string': $value = qsprintf($conn, '%s', $column['value']); break; default: throw new Exception(pht('Column "%s" has unknown column type "%s".', $column['column'], $type)); } } $is_column_reversed = idx($column, 'reverse', false); $reverse = ($is_query_reversed xor $is_column_reversed); $clause = $accumulated; $table_name = idx($column, 'table'); $column_name = $column['column']; if ($table_name !== null) { $field = qsprintf($conn, '%T.%T', $table_name, $column_name); } else { $field = qsprintf($conn, '%T', $column_name); } $parts = array(); if ($null) { $can_page_if_null = $null === 'head'; $can_page_if_nonnull = $null === 'tail'; if ($reverse) { $can_page_if_null = !$can_page_if_null; $can_page_if_nonnull = !$can_page_if_nonnull; } $subclause = null; if ($can_page_if_null && $value === null) { $parts[] = qsprintf($conn, '(%Q IS NOT NULL)', $field); } else { if ($can_page_if_nonnull && $value !== null) { $parts[] = qsprintf($conn, '(%Q IS NULL)', $field); } } } if ($value !== null) { $parts[] = qsprintf($conn, '%Q %Q %Q', $field, $reverse ? '>' : '<', $value); } if ($parts) { if (count($parts) > 1) { $clause[] = '(' . implode(') OR (', $parts) . ')'; } else { $clause[] = head($parts); } } if ($clause) { if (count($clause) > 1) { $clauses[] = '(' . implode(') AND (', $clause) . ')'; } else { $clauses[] = head($clause); } } if ($value === null) { $accumulated[] = qsprintf($conn, '%Q IS NULL', $field); } else { $accumulated[] = qsprintf($conn, '%Q = %Q', $field, $value); } } return '(' . implode(') OR (', $clauses) . ')'; }
$args->setTagline(pht('daemon executor')); $args->setSynopsis(<<<EOHELP **exec_daemon.php** [__options__] __daemon__ ... Run an instance of __daemon__. EOHELP ); $args->parse(array(array('name' => 'trace', 'help' => pht('Enable debug tracing.')), array('name' => 'trace-memory', 'help' => pht('Enable debug memory tracing.')), array('name' => 'verbose', 'help' => pht('Enable verbose activity logging.')), array('name' => 'label', 'short' => 'l', 'param' => 'label', 'help' => pht('Optional process label. Makes "%s" nicer, no behavioral effects.', 'ps')), array('name' => 'daemon', 'wildcard' => true))); $trace_memory = $args->getArg('trace-memory'); $trace_mode = $args->getArg('trace') || $trace_memory; $verbose = $args->getArg('verbose'); if (function_exists('posix_isatty') && posix_isatty(STDIN)) { fprintf(STDERR, pht('Reading daemon configuration from stdin...') . "\n"); } $config = @file_get_contents('php://stdin'); $config = id(new PhutilJSONParser())->parse($config); PhutilTypeSpec::checkMap($config, array('log' => 'optional string|null', 'argv' => 'optional list<wild>', 'load' => 'optional list<string>', 'autoscale' => 'optional wild')); $log = idx($config, 'log'); if ($log) { ini_set('error_log', $log); PhutilErrorHandler::setErrorListener(array('PhutilDaemon', 'errorListener')); } $load = idx($config, 'load', array()); foreach ($load as $library) { $library = Filesystem::resolvePath($library); phutil_load_library($library); } PhutilErrorHandler::initialize(); $daemon = $args->getArg('daemon'); if (!$daemon) { throw new PhutilArgumentUsageException(pht('Specify which class of daemon to start.')); } else {
public function testMixedVector() { // This is a test case for an issue where we would not infer the type // of a vector containing a mixture of scalar and nonscalar elements // correctly. $caught = null; try { PhutilTypeSpec::checkMap(array('key' => array('!', (object) array())), array('key' => 'list<X>')); } catch (PhutilTypeCheckException $ex) { $caught = $ex; } $this->assertTrue($caught instanceof PhutilTypeCheckException); }
public function buildLinters() { $working_copy = $this->getWorkingCopy(); $config_path = $working_copy->getProjectPath('.arclint'); if (!Filesystem::pathExists($config_path)) { throw new ArcanistUsageException(pht("Unable to find '%s' file to configure linters. Create an " . "'%s' file in the root directory of the working copy.", '.arclint', '.arclint')); } $data = Filesystem::readFile($config_path); $config = null; try { $config = phutil_json_decode($data); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException(pht("Expected '%s' file to be a valid JSON file, but " . "failed to decode '%s'.", '.arclint', $config_path), $ex); } $linters = $this->loadAvailableLinters(); try { PhutilTypeSpec::checkMap($config, array('exclude' => 'optional regex | list<regex>', 'linters' => 'map<string, map<string, wild>>')); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException(pht("Error in parsing '%s' file.", $config_path), $ex); } $global_exclude = (array) idx($config, 'exclude', array()); $built_linters = array(); $all_paths = $this->getPaths(); foreach ($config['linters'] as $name => $spec) { $type = idx($spec, 'type'); if ($type !== null) { if (empty($linters[$type])) { throw new ArcanistUsageException(pht("Linter '%s' specifies invalid type '%s'. " . "Available linters are: %s.", $name, $type, implode(', ', array_keys($linters)))); } $linter = clone $linters[$type]; $linter->setEngine($this); $more = $linter->getLinterConfigurationOptions(); foreach ($more as $key => $option_spec) { PhutilTypeSpec::checkMap($option_spec, array('type' => 'string', 'help' => 'string')); $more[$key] = $option_spec['type']; } } else { // We'll raise an error below about the invalid "type" key. $linter = null; $more = array(); } try { PhutilTypeSpec::checkMap($spec, array('type' => 'string', 'include' => 'optional regex | list<regex>', 'exclude' => 'optional regex | list<regex>') + $more); } catch (PhutilTypeCheckException $ex) { throw new PhutilProxyException(pht("Error in parsing '%s' file, for linter '%s'.", '.arclint', $name), $ex); } foreach ($more as $key => $value) { if (array_key_exists($key, $spec)) { try { $linter->setLinterConfigurationValue($key, $spec[$key]); } catch (Exception $ex) { throw new PhutilProxyException(pht("Error in parsing '%s' file, in key '%s' for linter '%s'.", '.arclint', $key, $name), $ex); } } } $include = (array) idx($spec, 'include', array()); $exclude = (array) idx($spec, 'exclude', array()); $console = PhutilConsole::getConsole(); $console->writeLog("%s\n", pht("Examining paths for linter '%s'.", $name)); $paths = $this->matchPaths($all_paths, $include, $exclude, $global_exclude); $console->writeLog("%s\n", pht("Found %d matching paths for linter '%s'.", count($paths), $name)); $linter->setPaths($paths); $built_linters[] = $linter; } return $built_linters; }
/** * @task validate */ public static function validateConfiguration(array $config) { foreach ($config as $key => $value) { if (!self::isValidStatusConstant($key)) { throw new Exception(pht('Key "%s" is not a valid status constant. Status constants must ' . 'be 1-12 characters long and contain only lowercase letters (a-z) ' . 'and digits (0-9). For example, "%s" or "%s" are reasonable ' . 'choices.', $key, 'open', 'closed')); } if (!is_array($value)) { throw new Exception(pht('Value for key "%s" should be a dictionary.', $key)); } PhutilTypeSpec::checkMap($value, array('name' => 'string', 'name.full' => 'optional string', 'name.action' => 'optional string', 'closed' => 'optional bool', 'special' => 'optional string', 'transaction.icon' => 'optional string', 'transaction.color' => 'optional string', 'silly' => 'optional bool', 'prefixes' => 'optional list<string>', 'suffixes' => 'optional list<string>', 'keywords' => 'optional list<string>', 'disabled' => 'optional bool', 'claim' => 'optional bool')); } $special_map = array(); foreach ($config as $key => $value) { $special = idx($value, 'special'); if (!$special) { continue; } if (isset($special_map[$special])) { throw new Exception(pht('Configuration has two statuses both marked with the special ' . 'attribute "%s" ("%s" and "%s"). There should be only one.', $special, $special_map[$special], $key)); } switch ($special) { case self::SPECIAL_DEFAULT: if (!empty($value['closed'])) { throw new Exception(pht('Status "%s" is marked as default, but it is a closed ' . 'status. The default status should be an open status.', $key)); } break; case self::SPECIAL_CLOSED: if (empty($value['closed'])) { throw new Exception(pht('Status "%s" is marked as the default status for closing ' . 'tasks, but is not a closed status. It should be a closed ' . 'status.', $key)); } break; case self::SPECIAL_DUPLICATE: if (empty($value['closed'])) { throw new Exception(pht('Status "%s" is marked as the status for closing tasks as ' . 'duplicates, but it is not a closed status. It should ' . 'be a closed status.', $key)); } break; } $special_map[$special] = $key; } // NOTE: We're not explicitly validating that we have at least one open // and one closed status, because the DEFAULT and CLOSED specials imply // that to be true. If those change in the future, that might become a // reasonable thing to validate. $required = array(self::SPECIAL_DEFAULT, self::SPECIAL_CLOSED, self::SPECIAL_DUPLICATE); foreach ($required as $required_special) { if (!isset($special_map[$required_special])) { throw new Exception(pht('Configuration defines no task status with special attribute ' . '"%s", but you must specify a status which fills this special ' . 'role.', $required_special)); } } }
/** * Render a standard login/register button element. * * The `$attributes` parameter takes these keys: * * - `uri`: URI the button should take the user to when clicked. * - `method`: Optional HTTP method the button should use, defaults to GET. * * @param AphrontRequest HTTP request. * @param string Request mode string. * @param map Additional parameters, see above. * @return wild Login button. */ protected function renderStandardLoginButton(AphrontRequest $request, $mode, array $attributes = array()) { PhutilTypeSpec::checkMap($attributes, array('method' => 'optional string', 'uri' => 'string', 'sigil' => 'optional string')); $viewer = $request->getUser(); $adapter = $this->getAdapter(); if ($mode == 'link') { $button_text = pht('Link External Account'); } else { if ($mode == 'refresh') { $button_text = pht('Refresh Account Link'); } else { if ($mode == 'invite') { $button_text = pht('Register Account'); } else { if ($this->shouldAllowRegistration()) { $button_text = pht('Login or Register'); } else { $button_text = pht('Login'); } } } } $icon = id(new PHUIIconView())->setSpriteSheet(PHUIIconView::SPRITE_LOGIN)->setSpriteIcon($this->getLoginIcon()); $button = id(new PHUIButtonView())->setSize(PHUIButtonView::BIG)->setColor(PHUIButtonView::GREY)->setIcon($icon)->setText($button_text)->setSubtext($this->getProviderName()); $uri = $attributes['uri']; $uri = new PhutilURI($uri); $params = $uri->getQueryParams(); $uri->setQueryParams(array()); $content = array($button); foreach ($params as $key => $value) { $content[] = phutil_tag('input', array('type' => 'hidden', 'name' => $key, 'value' => $value)); } return phabricator_form($viewer, array('method' => idx($attributes, 'method', 'GET'), 'action' => (string) $uri, 'sigil' => idx($attributes, 'sigil')), $content); }
private function lintFile($file, ArcanistLinter $linter) { $linter = clone $linter; $contents = Filesystem::readFile($file); $contents = preg_split('/^~{4,}\\n/m', $contents); if (count($contents) < 2) { throw new Exception(pht("Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect, $xform, $config) = array_merge($contents, array(null, null)); $basename = basename($file); if ($config) { $config = phutil_json_decode($config); } else { $config = array(); } PhutilTypeSpec::checkMap($config, array('config' => 'optional map<string, wild>', 'path' => 'optional string', 'mode' => 'optional string', 'stopped' => 'optional bool')); $exception = null; $after_lint = null; $messages = null; $exception_message = false; $caught_exception = false; try { $tmp = new TempFile($basename); Filesystem::writeFile($tmp, $data); $full_path = (string) $tmp; $mode = idx($config, 'mode'); if ($mode) { Filesystem::changePermissions($tmp, octdec($mode)); } $dir = dirname($full_path); $path = basename($full_path); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($dir, null, pht('Unit Test')); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $engine = new ArcanistUnitTestableLintEngine(); $engine->setWorkingCopy($working_copy); $engine->setConfigurationManager($configuration_manager); $path_name = idx($config, 'path', $path); $engine->setPaths(array($path_name)); $linter->addPath($path_name); $linter->addData($path_name, $data); foreach (idx($config, 'config', array()) as $key => $value) { $linter->setLinterConfigurationValue($key, $value); } $engine->addLinter($linter); $engine->addFileData($path_name, $data); $results = $engine->run(); $this->assertEqual(1, count($results), pht('Expect one result returned by linter.')); $assert_stopped = idx($config, 'stopped'); if ($assert_stopped !== null) { $this->assertEqual($assert_stopped, $linter->didStopAllLinters(), $assert_stopped ? pht('Expect linter to be stopped.') : pht('Expect linter to not be stopped.')); } $result = reset($results); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $after_lint = $patcher->getModifiedFileContent(); } catch (PhutilTestTerminatedException $ex) { throw $ex; } catch (Exception $exception) { $caught_exception = true; if ($exception instanceof PhutilAggregateException) { $caught_exception = false; foreach ($exception->getExceptions() as $ex) { if ($ex instanceof ArcanistUsageException || $ex instanceof ArcanistMissingLinterException) { $this->assertSkipped($ex->getMessage()); } else { $caught_exception = true; } } } else { if ($exception instanceof ArcanistUsageException || $exception instanceof ArcanistMissingLinterException) { $this->assertSkipped($exception->getMessage()); } } $exception_message = $exception->getMessage() . "\n\n" . $exception->getTraceAsString(); } $this->assertEqual(false, $caught_exception, $exception_message); $this->compareLint($basename, $expect, $result); $this->compareTransform($xform, $after_lint); }
public static function validateConfiguration($config) { if (!is_array($config)) { throw new Exception(pht('Configuration is not valid. Maniphest priority configurations ' . 'must be dictionaries.', $config)); } foreach ($config as $key => $value) { if (!ctype_digit((string) $key)) { throw new Exception(pht('Key "%s" is not a valid priority constant. Priority constants ' . 'must be nonnegative integers.', $key)); } if (!is_array($value)) { throw new Exception(pht('Value for key "%s" should be a dictionary.', $key)); } PhutilTypeSpec::checkMap($value, array('name' => 'string', 'short' => 'optional string', 'color' => 'optional string', 'keywords' => 'optional list<string>', 'disabled' => 'optional bool')); } }
protected final function executeStartCommand(array $options) { PhutilTypeSpec::checkMap($options, array('keep-leases' => 'optional bool', 'force' => 'optional bool', 'reserve' => 'optional float')); $console = PhutilConsole::getConsole(); if (!idx($options, 'force')) { $running = $this->loadRunningDaemons(); // This may include daemons which were launched but which are no longer // running; check that we actually have active daemons before failing. foreach ($running as $daemon) { if ($daemon->isRunning()) { $message = pht("phd start: Unable to start daemons because daemons are already " . "running.\n\n" . "You can view running daemons with '%s'.\n" . "You can stop running daemons with '%s'.\n" . "You can use '%s' to stop all daemons before starting " . "new daemons.\n" . "You can force daemons to start anyway with %s.", 'phd status', 'phd stop', 'phd restart', '--force'); $console->writeErr("%s\n", $message); exit(1); } } } if (idx($options, 'keep-leases')) { $console->writeErr("%s\n", pht('Not touching active task queue leases.')); } else { $console->writeErr("%s\n", pht('Freeing active task leases...')); $count = $this->freeActiveLeases(); $console->writeErr("%s\n", pht('Freed %s task lease(s).', new PhutilNumber($count))); } $daemons = array(array('class' => 'PhabricatorRepositoryPullLocalDaemon'), array('class' => 'PhabricatorTriggerDaemon'), array('class' => 'PhabricatorTaskmasterDaemon', 'autoscale' => array('group' => 'task', 'pool' => PhabricatorEnv::getEnvConfig('phd.taskmasters'), 'reserve' => idx($options, 'reserve', 0)))); $this->launchDaemons($daemons, $is_debug = false); $console->writeErr("%s\n", pht('Done.')); return 0; }
public function validateArtifactData(array $artifact_data) { $artifact_spec = $this->getArtifactParameterSpecification(); PhutilTypeSpec::checkMap($artifact_data, $artifact_spec); }
public static final function scheduleTask($task_class, $data, $options = array()) { PhutilTypeSpec::checkMap($options, array('priority' => 'optional int|null', 'objectPHID' => 'optional string|null', 'delayUntil' => 'optional int|null')); $priority = idx($options, 'priority'); if ($priority === null) { $priority = self::PRIORITY_DEFAULT; } $object_phid = idx($options, 'objectPHID'); $task = id(new PhabricatorWorkerActiveTask())->setTaskClass($task_class)->setData($data)->setPriority($priority)->setObjectPHID($object_phid); $delay = idx($options, 'delayUntil'); if ($delay) { $task->setLeaseExpires($delay); } if (self::$runAllTasksInProcess) { // Do the work in-process. $worker = newv($task_class, array($data)); while (true) { try { $worker->doWork(); foreach ($worker->getQueuedTasks() as $queued_task) { list($queued_class, $queued_data, $queued_priority) = $queued_task; $queued_options = array('priority' => $queued_priority); self::scheduleTask($queued_class, $queued_data, $queued_options); } break; } catch (PhabricatorWorkerYieldException $ex) { phlog(pht('In-process task "%s" yielded for %s seconds, sleeping...', $task_class, $ex->getDuration())); sleep($ex->getDuration()); } } // Now, save a task row and immediately archive it so we can return an // object with a valid ID. $task->openTransaction(); $task->save(); $archived = $task->archiveTask(PhabricatorWorkerArchiveTask::RESULT_SUCCESS, 0); $task->saveTransaction(); return $archived; } else { $task->save(); return $task; } }
public function validateProperties(array $properties) { PhutilTypeSpec::checkMap($properties, array('message' => 'string')); }
public function testScalarOrListRegexp() { PhutilTypeSpec::checkMap(array('regex' => '/.*/'), array('regex' => 'regex | list<regex>')); PhutilTypeSpec::checkMap(array('regex' => array('/.*/')), array('regex' => 'regex | list<regex>')); PhutilTypeSpec::checkMap(array('regex' => '/.*/'), array('regex' => 'list<regex> | regex')); PhutilTypeSpec::checkMap(array('regex' => array('/.*/')), array('regex' => 'list<regex> | regex')); $this->assertTrue(true); }
/** * Configure autoscaling for this daemon. * * @param map<string, wild> Map of autoscale properties. * @return this * @task autoscale */ public function setAutoscaleProperties(array $autoscale_properties) { PhutilTypeSpec::checkMap($autoscale_properties, array('group' => 'optional string', 'up' => 'optional int', 'down' => 'optional int', 'pool' => 'optional int', 'clone' => 'optional bool', 'reserve' => 'optional int|float')); $this->autoscaleProperties = $autoscale_properties; return $this; }