diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -150,6 +150,8 @@ 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEditDistanceMatrix' => 'utils/PhutilEditDistanceMatrix.php', 'PhutilEditDistanceMatrixTestCase' => 'utils/__tests__/PhutilEditDistanceMatrixTestCase.php', + 'PhutilEditorConfig' => 'parser/PhutilEditorConfig.php', + 'PhutilEditorConfigTestCase' => 'parser/__tests__/PhutilEditorConfigTestCase.php', 'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php', 'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php', 'PhutilEmptyAuthAdapter' => 'auth/PhutilEmptyAuthAdapter.php', @@ -587,6 +589,7 @@ 'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEditDistanceMatrixTestCase' => 'PhutilTestCase', + 'PhutilEditorConfigTestCase' => 'PhutilTestCase', 'PhutilEmailAddressTestCase' => 'PhutilTestCase', 'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', diff --git a/src/parser/PhutilEditorConfig.php b/src/parser/PhutilEditorConfig.php new file mode 100644 --- /dev/null +++ b/src/parser/PhutilEditorConfig.php @@ -0,0 +1,131 @@ + array('latin1', 'utf-8', 'utf-8-bom', 'utf-16be', 'utf-16le'), + 'end_of_line' => array('lf', 'cr', 'lfcr'), + 'indent_size' => 'int|string', + 'indent_style' => array('space', 'tab'), + 'insert_final_newline' => 'bool', + 'tab_width' => 'int', + 'trim_trailing_whitespace' => 'bool', + ); + + /** + * Constructor. + * + * @param string The root directory. + */ + public function __construct($root) { + $this->root = $root; + } + + public function getConfig($path, $key) { + $configs = $this->getConfigsAffectingPath($path); + + switch ($key) { + case 'indent_size': + if (idx($configs, 'indent_size') === null && + idx($configs, 'indent_style') === 'tab') { + return 'tab'; + } else if (idx($configs, 'indent_size') == 'tab' && + idx($configs, 'tab_size') !== null) { + return idx($configs, 'tab_size'); + } + break; + + case 'tab_width': + if (idx($configs, 'indent_size') !== null && + idx($configs, 'tab_width') === null && + idx($configs, 'indent_size') !== 'tab') { + return idx($configs, 'indent_size'); + } + break; + } + + return idx($configs, $key); + } + + private function getConfigsAffectingPath($path) { + $editorconfigs = $this->getEditorConfigs($path); + $matches = array(); + + foreach ($editorconfigs as $editorconfig) { + list($path_prefix, $config) = $editorconfig; + $value = null; + + foreach ($config as $glob => $options) { + if (!$glob) { + continue; + } + + switch (strpos($glob, '/')) { + case false: + $glob = '**/'.$glob; + break; + + case 0: + $glob = substr($glob, 1); + break; + } + + $glob = $path_prefix.'/'.$glob; + $regex = phutil_glob_to_regex($glob); + + if (!preg_match($regex, $path)) { + continue; + } + + foreach ($options as $option => $value) { + $option = strtolower($option); + + if (idx(self::$knownProperties, $option) === null) { + continue; + } + + // TODO: What if it really is `null`? + $value = strtolower($value); + if (json_decode($value, true) !== null) { + $value = json_decode($value, true); + } + $matches[$option] = $value; + } + } + } + + return $matches; + } + + /** + * Returns the EditorConfig files which affect the specified path. + * + * Find and parse all `.editorconfig` files between the specified path and + * the root directory. + * + * return list> + */ + private function getEditorConfigs($path) { + $configs = array(); + + do { + $path = dirname($path); + $file = $path.'/.editorconfig'; + + if (!Filesystem::pathExists($file)) { + continue; + } + + $config = phutil_ini_decode(Filesystem::readFile($file)); + array_unshift($configs, array($path, $config)); + } while ($path != $this->root); + + return $configs; + } + +} diff --git a/src/parser/__tests__/PhutilEditorConfigTestCase.php b/src/parser/__tests__/PhutilEditorConfigTestCase.php new file mode 100644 --- /dev/null +++ b/src/parser/__tests__/PhutilEditorConfigTestCase.php @@ -0,0 +1,35 @@ + array( + 'indent_style' => 'tab', + 'indent_size' => 4, + 'charset' => 'utf-8', + 'trim_trailing_whitespace' => false, + 'insert_final_newline' => false, + ), + 'indent.txt' => array( + 'indent_style' => 'space', + 'indent_size' => 2, + ), + ); + + foreach ($tests as $path => $config) { + foreach ($config as $key => $value) { + $this->assertEqual( + $value, + $parser->getConfig($this->getTestFile($path), $key)); + } + } + } + + private function getTestFile($path) { + return dirname(__FILE__).'/editorconfig/'.$path; + } + +} diff --git a/src/parser/__tests__/editorconfig/.editorconfig b/src/parser/__tests__/editorconfig/.editorconfig new file mode 100644 --- /dev/null +++ b/src/parser/__tests__/editorconfig/.editorconfig @@ -0,0 +1,10 @@ +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false + +[indent.txt] +indent_style = space +indent_size = 2