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 @@ -64,7 +64,6 @@ 'PhagePlanAction' => 'phage/action/PhagePlanAction.php', 'Phobject' => 'object/Phobject.php', 'PhobjectTestCase' => 'object/__tests__/PhobjectTestCase.php', - 'PhutilAPCKeyValueCache' => 'cache/PhutilAPCKeyValueCache.php', 'PhutilAWSCloudFormationFuture' => 'future/aws/PhutilAWSCloudFormationFuture.php', 'PhutilAWSCloudWatchFuture' => 'future/aws/PhutilAWSCloudWatchFuture.php', 'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php', @@ -133,12 +132,10 @@ 'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php', 'PhutilConsoleSkip' => 'console/view/PhutilConsoleSkip.php', 'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php', - 'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php', 'PhutilConsoleTable' => 'console/view/PhutilConsoleTable.php', 'PhutilConsoleView' => 'console/view/PhutilConsoleView.php', 'PhutilConsoleWarning' => 'console/view/PhutilConsoleWarning.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', - 'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php', 'PhutilCowsay' => 'utils/PhutilCowsay.php', 'PhutilCowsayTestCase' => 'utils/__tests__/PhutilCowsayTestCase.php', 'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php', @@ -149,17 +146,11 @@ 'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php', 'PhutilDaemonOverseerModule' => 'daemon/PhutilDaemonOverseerModule.php', 'PhutilDaemonPool' => 'daemon/PhutilDaemonPool.php', - 'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php', - 'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php', - 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php', - 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php', 'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php', 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', 'PhutilDiffBinaryAnalyzer' => 'filesystem/binary/PhutilDiffBinaryAnalyzer.php', 'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', - 'PhutilDirectoryKeyValueCache' => 'cache/PhutilDirectoryKeyValueCache.php', - 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', 'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php', 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEditDistanceMatrix' => 'utils/PhutilEditDistanceMatrix.php', @@ -213,16 +204,13 @@ 'PhutilIPAddressTestCase' => 'ip/__tests__/PhutilIPAddressTestCase.php', 'PhutilIPv4Address' => 'ip/PhutilIPv4Address.php', 'PhutilIPv6Address' => 'ip/PhutilIPv6Address.php', - 'PhutilInRequestKeyValueCache' => 'cache/PhutilInRequestKeyValueCache.php', 'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php', 'PhutilInvalidRuleParserGeneratorException' => 'parser/generator/exception/PhutilInvalidRuleParserGeneratorException.php', 'PhutilInvalidStateException' => 'exception/PhutilInvalidStateException.php', 'PhutilInvalidStateExceptionTestCase' => 'exception/__tests__/PhutilInvalidStateExceptionTestCase.php', - 'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php', 'PhutilIrreducibleRuleParserGeneratorException' => 'parser/generator/exception/PhutilIrreducibleRuleParserGeneratorException.php', 'PhutilJSON' => 'parser/PhutilJSON.php', 'PhutilJSONFragmentLexer' => 'lexer/PhutilJSONFragmentLexer.php', - 'PhutilJSONFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilJSONFragmentLexerHighlighterTestCase.php', 'PhutilJSONParser' => 'parser/PhutilJSONParser.php', 'PhutilJSONParserException' => 'parser/exception/PhutilJSONParserException.php', 'PhutilJSONParserTestCase' => 'parser/__tests__/PhutilJSONParserTestCase.php', @@ -230,17 +218,10 @@ 'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php', 'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php', 'PhutilJavaFragmentLexer' => 'lexer/PhutilJavaFragmentLexer.php', - 'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php', - 'PhutilKeyValueCacheNamespace' => 'cache/PhutilKeyValueCacheNamespace.php', - 'PhutilKeyValueCacheProfiler' => 'cache/PhutilKeyValueCacheProfiler.php', - 'PhutilKeyValueCacheProxy' => 'cache/PhutilKeyValueCacheProxy.php', - 'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php', - 'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php', 'PhutilKoreanLocale' => 'internationalization/locales/PhutilKoreanLocale.php', 'PhutilLanguageGuesser' => 'parser/PhutilLanguageGuesser.php', 'PhutilLanguageGuesserTestCase' => 'parser/__tests__/PhutilLanguageGuesserTestCase.php', 'PhutilLexer' => 'lexer/PhutilLexer.php', - 'PhutilLexerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php', 'PhutilLibraryConflictException' => 'moduleutils/PhutilLibraryConflictException.php', 'PhutilLibraryMapBuilder' => 'moduleutils/PhutilLibraryMapBuilder.php', 'PhutilLibraryTestCase' => '__tests__/PhutilLibraryTestCase.php', @@ -251,9 +232,6 @@ 'PhutilLogFileChannel' => 'channel/PhutilLogFileChannel.php', 'PhutilLunarPhase' => 'utils/PhutilLunarPhase.php', 'PhutilLunarPhaseTestCase' => 'utils/__tests__/PhutilLunarPhaseTestCase.php', - 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', - 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', - 'PhutilMemcacheKeyValueCache' => 'cache/PhutilMemcacheKeyValueCache.php', 'PhutilMercurialBinaryAnalyzer' => 'filesystem/binary/PhutilMercurialBinaryAnalyzer.php', 'PhutilMethodNotImplementedException' => 'error/PhutilMethodNotImplementedException.php', 'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php', @@ -263,12 +241,10 @@ 'PhutilNumber' => 'internationalization/PhutilNumber.php', 'PhutilOAuth1Future' => 'future/oauth/PhutilOAuth1Future.php', 'PhutilOAuth1FutureTestCase' => 'future/oauth/__tests__/PhutilOAuth1FutureTestCase.php', - 'PhutilOnDiskKeyValueCache' => 'cache/PhutilOnDiskKeyValueCache.php', 'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php', 'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php', 'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php', 'PhutilPHPFragmentLexer' => 'lexer/PhutilPHPFragmentLexer.php', - 'PhutilPHPFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php', 'PhutilPHPFragmentLexerTestCase' => 'lexer/__tests__/PhutilPHPFragmentLexerTestCase.php', 'PhutilPHPObjectProtocolChannel' => 'channel/PhutilPHPObjectProtocolChannel.php', 'PhutilPHPObjectProtocolChannelTestCase' => 'channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php', @@ -293,21 +269,14 @@ 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilProxyIterator' => 'utils/PhutilProxyIterator.php', 'PhutilPygmentizeBinaryAnalyzer' => 'filesystem/binary/PhutilPygmentizeBinaryAnalyzer.php', - 'PhutilPygmentizeParser' => 'parser/PhutilPygmentizeParser.php', - 'PhutilPygmentizeParserTestCase' => 'parser/__tests__/PhutilPygmentizeParserTestCase.php', - 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php', 'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php', 'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php', - 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', 'PhutilRawEnglishLocale' => 'internationalization/locales/PhutilRawEnglishLocale.php', 'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php', 'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php', 'PhutilRope' => 'utils/PhutilRope.php', 'PhutilRopeTestCase' => 'utils/__tests__/PhutilRopeTestCase.php', - 'PhutilSafeHTML' => 'markup/PhutilSafeHTML.php', - 'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php', - 'PhutilSafeHTMLTestCase' => 'markup/__tests__/PhutilSafeHTMLTestCase.php', 'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php', 'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php', 'PhutilShellLexer' => 'lexer/PhutilShellLexer.php', @@ -323,14 +292,9 @@ 'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php', 'PhutilSortVector' => 'utils/PhutilSortVector.php', 'PhutilSpanishSpainLocale' => 'internationalization/locales/PhutilSpanishSpainLocale.php', - 'PhutilSprite' => 'sprites/PhutilSprite.php', - 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilStreamIterator' => 'utils/PhutilStreamIterator.php', 'PhutilSubversionBinaryAnalyzer' => 'filesystem/binary/PhutilSubversionBinaryAnalyzer.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', - 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', - 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', - 'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php', 'PhutilSystem' => 'utils/PhutilSystem.php', 'PhutilSystemTestCase' => 'utils/__tests__/PhutilSystemTestCase.php', 'PhutilTerminalString' => 'xsprintf/PhutilTerminalString.php', @@ -362,9 +326,6 @@ 'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php', 'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php', 'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php', - 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', - 'PhutilXHPASTSyntaxHighlighterFuture' => 'markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php', - 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'TempFile' => 'filesystem/TempFile.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', @@ -390,7 +351,6 @@ 'head' => 'utils/utils.php', 'head_key' => 'utils/utils.php', 'hgsprintf' => 'xsprintf/hgsprintf.php', - 'hsprintf' => 'markup/render.php', 'id' => 'utils/utils.php', 'idx' => 'utils/utils.php', 'idxv' => 'utils/utils.php', @@ -427,10 +387,8 @@ 'phutil_deprecated' => 'moduleutils/moduleutils.php', 'phutil_describe_type' => 'utils/utils.php', 'phutil_error_listener_example' => 'error/phlog.php', - 'phutil_escape_html' => 'markup/render.php', - 'phutil_escape_html_newlines' => 'markup/render.php', - 'phutil_escape_uri' => 'markup/render.php', - 'phutil_escape_uri_path_component' => 'markup/render.php', + 'phutil_escape_uri' => 'utils/utils.php', + 'phutil_escape_uri_path_component' => 'utils/utils.php', 'phutil_fnmatch' => 'utils/utils.php', 'phutil_format_bytes' => 'utils/viewutils.php', 'phutil_format_relative_time' => 'utils/viewutils.php', @@ -445,7 +403,6 @@ 'phutil_get_system_locale' => 'utils/utf8.php', 'phutil_hashes_are_identical' => 'utils/utils.php', 'phutil_http_parameter_pair' => 'utils/utils.php', - 'phutil_implode_html' => 'markup/render.php', 'phutil_ini_decode' => 'utils/utils.php', 'phutil_is_hiphop_runtime' => 'utils/utils.php', 'phutil_is_natural_list' => 'utils/utils.php', @@ -464,13 +421,10 @@ 'phutil_person' => 'internationalization/pht.php', 'phutil_register_library' => 'moduleutils/core.php', 'phutil_register_library_map' => 'moduleutils/core.php', - 'phutil_safe_html' => 'markup/render.php', 'phutil_set_system_locale' => 'utils/utf8.php', 'phutil_split_lines' => 'utils/utils.php', 'phutil_string_cast' => 'utils/utils.php', - 'phutil_tag' => 'markup/render.php', - 'phutil_tag_div' => 'markup/render.php', - 'phutil_unescape_uri_path_component' => 'markup/render.php', + 'phutil_unescape_uri_path_component' => 'utils/utils.php', 'phutil_units' => 'utils/utils.php', 'phutil_utf8_console_strlen' => 'utils/utf8.php', 'phutil_utf8_convert' => 'utils/utf8.php', @@ -576,7 +530,6 @@ 'PhagePlanAction' => 'PhageAction', 'Phobject' => 'Iterator', 'PhobjectTestCase' => 'PhutilTestCase', - 'PhutilAPCKeyValueCache' => 'PhutilKeyValueCache', 'PhutilAWSCloudFormationFuture' => 'PhutilAWSFuture', 'PhutilAWSCloudWatchFuture' => 'PhutilAWSFuture', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', @@ -655,12 +608,10 @@ 'PhutilConsoleServerChannel' => 'PhutilChannelChannel', 'PhutilConsoleSkip' => 'PhutilConsoleLogLine', 'PhutilConsoleStdinNotInteractiveException' => 'Exception', - 'PhutilConsoleSyntaxHighlighter' => 'Phobject', 'PhutilConsoleTable' => 'PhutilConsoleView', 'PhutilConsoleView' => 'Phobject', 'PhutilConsoleWarning' => 'PhutilConsoleLogLine', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', - 'PhutilContextFreeGrammar' => 'Phobject', 'PhutilCowsay' => 'Phobject', 'PhutilCowsayTestCase' => 'PhutilTestCase', 'PhutilCsprintfTestCase' => 'PhutilTestCase', @@ -671,17 +622,11 @@ 'PhutilDaemonOverseer' => 'Phobject', 'PhutilDaemonOverseerModule' => 'Phobject', 'PhutilDaemonPool' => 'Phobject', - 'PhutilDefaultSyntaxHighlighter' => 'Phobject', - 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', - 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy', - 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLog' => 'Phobject', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDiffBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph', 'PhutilDirectoryFixture' => 'Phobject', - 'PhutilDirectoryKeyValueCache' => 'PhutilKeyValueCache', - 'PhutilDivinerSyntaxHighlighter' => 'Phobject', 'PhutilDocblockParser' => 'Phobject', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEditDistanceMatrix' => 'Phobject', @@ -738,16 +683,13 @@ 'PhutilIPAddressTestCase' => 'PhutilTestCase', 'PhutilIPv4Address' => 'PhutilIPAddress', 'PhutilIPv6Address' => 'PhutilIPAddress', - 'PhutilInRequestKeyValueCache' => 'PhutilKeyValueCache', 'PhutilInteractiveEditor' => 'Phobject', 'PhutilInvalidRuleParserGeneratorException' => 'PhutilParserGeneratorException', 'PhutilInvalidStateException' => 'Exception', 'PhutilInvalidStateExceptionTestCase' => 'PhutilTestCase', - 'PhutilInvisibleSyntaxHighlighter' => 'Phobject', 'PhutilIrreducibleRuleParserGeneratorException' => 'PhutilParserGeneratorException', 'PhutilJSON' => 'Phobject', 'PhutilJSONFragmentLexer' => 'PhutilLexer', - 'PhutilJSONFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilJSONParser' => 'Phobject', 'PhutilJSONParserException' => 'Exception', 'PhutilJSONParserTestCase' => 'PhutilTestCase', @@ -755,17 +697,10 @@ 'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilJSONTestCase' => 'PhutilTestCase', 'PhutilJavaFragmentLexer' => 'PhutilLexer', - 'PhutilKeyValueCache' => 'Phobject', - 'PhutilKeyValueCacheNamespace' => 'PhutilKeyValueCacheProxy', - 'PhutilKeyValueCacheProfiler' => 'PhutilKeyValueCacheProxy', - 'PhutilKeyValueCacheProxy' => 'PhutilKeyValueCache', - 'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache', - 'PhutilKeyValueCacheTestCase' => 'PhutilTestCase', 'PhutilKoreanLocale' => 'PhutilLocale', 'PhutilLanguageGuesser' => 'Phobject', 'PhutilLanguageGuesserTestCase' => 'PhutilTestCase', 'PhutilLexer' => 'Phobject', - 'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter', 'PhutilLibraryConflictException' => 'Exception', 'PhutilLibraryMapBuilder' => 'Phobject', 'PhutilLibraryTestCase' => 'PhutilTestCase', @@ -776,9 +711,6 @@ 'PhutilLogFileChannel' => 'PhutilChannelChannel', 'PhutilLunarPhase' => 'Phobject', 'PhutilLunarPhaseTestCase' => 'PhutilTestCase', - 'PhutilMarkupEngine' => 'Phobject', - 'PhutilMarkupTestCase' => 'PhutilTestCase', - 'PhutilMemcacheKeyValueCache' => 'PhutilKeyValueCache', 'PhutilMercurialBinaryAnalyzer' => 'PhutilBinaryAnalyzer', 'PhutilMethodNotImplementedException' => 'Exception', 'PhutilMetricsChannel' => 'PhutilChannelChannel', @@ -788,12 +720,10 @@ 'PhutilNumber' => 'Phobject', 'PhutilOAuth1Future' => 'FutureProxy', 'PhutilOAuth1FutureTestCase' => 'PhutilTestCase', - 'PhutilOnDiskKeyValueCache' => 'PhutilKeyValueCache', 'PhutilOpaqueEnvelope' => 'Phobject', 'PhutilOpaqueEnvelopeKey' => 'Phobject', 'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexer' => 'PhutilLexer', - 'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase', 'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel', 'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase', @@ -823,20 +753,14 @@ 'Iterator', ), 'PhutilPygmentizeBinaryAnalyzer' => 'PhutilBinaryAnalyzer', - 'PhutilPygmentizeParser' => 'Phobject', - 'PhutilPygmentizeParserTestCase' => 'PhutilTestCase', - 'PhutilPygmentsSyntaxHighlighter' => 'Phobject', 'PhutilPythonFragmentLexer' => 'PhutilLexer', 'PhutilQueryStringParser' => 'Phobject', 'PhutilQueryStringParserTestCase' => 'PhutilTestCase', - 'PhutilRainbowSyntaxHighlighter' => 'Phobject', 'PhutilRawEnglishLocale' => 'PhutilLocale', 'PhutilReadableSerializer' => 'Phobject', 'PhutilReadableSerializerTestCase' => 'PhutilTestCase', 'PhutilRope' => 'Phobject', 'PhutilRopeTestCase' => 'PhutilTestCase', - 'PhutilSafeHTML' => 'Phobject', - 'PhutilSafeHTMLTestCase' => 'PhutilTestCase', 'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon', 'PhutilServiceProfiler' => 'Phobject', 'PhutilShellLexer' => 'PhutilLexer', @@ -852,16 +776,11 @@ 'PhutilSocketChannel' => 'PhutilChannel', 'PhutilSortVector' => 'Phobject', 'PhutilSpanishSpainLocale' => 'PhutilLocale', - 'PhutilSprite' => 'Phobject', - 'PhutilSpriteSheet' => 'Phobject', 'PhutilStreamIterator' => array( 'Phobject', 'Iterator', ), 'PhutilSubversionBinaryAnalyzer' => 'PhutilBinaryAnalyzer', - 'PhutilSyntaxHighlighter' => 'Phobject', - 'PhutilSyntaxHighlighterEngine' => 'Phobject', - 'PhutilSyntaxHighlighterException' => 'Exception', 'PhutilSystem' => 'Phobject', 'PhutilSystemTestCase' => 'PhutilTestCase', 'PhutilTerminalString' => 'Phobject', @@ -893,9 +812,6 @@ 'PhutilVeryWowEnglishLocale' => 'PhutilLocale', 'PhutilWordPressFuture' => 'FutureProxy', 'PhutilXHPASTBinary' => 'Phobject', - 'PhutilXHPASTSyntaxHighlighter' => 'Phobject', - 'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy', - 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'TempFile' => 'Phobject', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', diff --git a/src/cache/PhutilAPCKeyValueCache.php b/src/cache/PhutilAPCKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilAPCKeyValueCache.php +++ /dev/null @@ -1,97 +0,0 @@ - $value) { - if ($is_apcu) { - apcu_store($key, $value, $ttl); - } else { - apc_store($key, $value, $ttl); - } - } - - return $this; - } - - public function deleteKeys(array $keys) { - static $is_apcu; - if ($is_apcu === null) { - $is_apcu = self::isAPCu(); - } - - foreach ($keys as $key) { - if ($is_apcu) { - apcu_delete($key); - } else { - apc_delete($key); - } - } - - return $this; - } - - public function destroyCache() { - static $is_apcu; - if ($is_apcu === null) { - $is_apcu = self::isAPCu(); - } - - if ($is_apcu) { - apcu_clear_cache(); - } else { - apc_clear_cache('user'); - } - - return $this; - } - - private static function isAPCu() { - return function_exists('apcu_fetch'); - } - -} diff --git a/src/cache/PhutilDirectoryKeyValueCache.php b/src/cache/PhutilDirectoryKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilDirectoryKeyValueCache.php +++ /dev/null @@ -1,244 +0,0 @@ -validateKeys($keys); - - try { - $this->lockCache(); - } catch (PhutilLockException $ex) { - return array(); - } - - $now = time(); - - $results = array(); - foreach ($keys as $key) { - $key_file = $this->getKeyFile($key); - try { - $data = Filesystem::readFile($key_file); - } catch (FilesystemException $ex) { - continue; - } - - $data = unserialize($data); - if (!$data) { - continue; - } - - if (isset($data['ttl']) && $data['ttl'] < $now) { - continue; - } - - $results[$key] = $data['value']; - } - - $this->unlockCache(); - - return $results; - } - - - public function setKeys(array $keys, $ttl = null) { - $this->validateKeys(array_keys($keys)); - - $this->lockCache(15); - - if ($ttl) { - $ttl_epoch = time() + $ttl; - } else { - $ttl_epoch = null; - } - - foreach ($keys as $key => $value) { - $dict = array( - 'value' => $value, - ); - if ($ttl_epoch) { - $dict['ttl'] = $ttl_epoch; - } - - try { - $key_file = $this->getKeyFile($key); - $key_dir = dirname($key_file); - if (!Filesystem::pathExists($key_dir)) { - Filesystem::createDirectory( - $key_dir, - $mask = 0755, - $recursive = true); - } - - $new_file = $key_file.'.new'; - Filesystem::writeFile($new_file, serialize($dict)); - Filesystem::rename($new_file, $key_file); - } catch (FilesystemException $ex) { - phlog($ex); - } - } - - $this->unlockCache(); - - return $this; - } - - - public function deleteKeys(array $keys) { - $this->validateKeys($keys); - - $this->lockCache(15); - - foreach ($keys as $key) { - $path = $this->getKeyFile($key); - Filesystem::remove($path); - - // If removing this key leaves the directory empty, clean it up. Then - // clean up any empty parent directories. - $path = dirname($path); - do { - if (!Filesystem::isDescendant($path, $this->getCacheDirectory())) { - break; - } - if (Filesystem::listDirectory($path, true)) { - break; - } - Filesystem::remove($path); - $path = dirname($path); - } while (true); - } - - $this->unlockCache(); - - return $this; - } - - - public function destroyCache() { - Filesystem::remove($this->getCacheDirectory()); - return $this; - } - - -/* -( Cache Storage )------------------------------------------------------ */ - - - /** - * @task storage - */ - public function setCacheDirectory($directory) { - $this->cacheDirectory = rtrim($directory, '/').'/'; - return $this; - } - - - /** - * @task storage - */ - private function getCacheDirectory() { - if (!$this->cacheDirectory) { - throw new PhutilInvalidStateException('setCacheDirectory'); - } - return $this->cacheDirectory; - } - - - /** - * @task storage - */ - private function getKeyFile($key) { - // Colon is a drive separator on Windows. - $key = str_replace(':', '_', $key); - - // NOTE: We add ".cache" to each file so we don't get a collision if you - // set the keys "a" and "a/b". Without ".cache", the file "a" would need - // to be both a file and a directory. - return $this->getCacheDirectory().$key.'.cache'; - } - - - /** - * @task storage - */ - private function validateKeys(array $keys) { - foreach ($keys as $key) { - // NOTE: Use of "." is reserved for ".lock", "key.new" and "key.cache". - // Use of "_" is reserved for converting ":". - if (!preg_match('@^[a-zA-Z0-9/:-]+$@', $key)) { - throw new Exception( - pht( - "Invalid key '%s': directory caches may only contain letters, ". - "numbers, hyphen, colon and slash.", - $key)); - } - } - } - - - /** - * @task storage - */ - private function lockCache($wait = 0) { - if ($this->lock) { - throw new Exception( - pht( - 'Trying to %s with a lock!', - __FUNCTION__.'()')); - } - - if (!Filesystem::pathExists($this->getCacheDirectory())) { - Filesystem::createDirectory($this->getCacheDirectory(), 0755, true); - } - - $lock = PhutilFileLock::newForPath($this->getCacheDirectory().'.lock'); - $lock->lock($wait); - - $this->lock = $lock; - } - - - /** - * @task storage - */ - private function unlockCache() { - if (!$this->lock) { - throw new PhutilInvalidStateException('lockCache'); - } - - $this->lock->unlock(); - $this->lock = null; - } - -} diff --git a/src/cache/PhutilInRequestKeyValueCache.php b/src/cache/PhutilInRequestKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilInRequestKeyValueCache.php +++ /dev/null @@ -1,118 +0,0 @@ -limit = $limit; - return $this; - } - - -/* -( Key-Value Cache Implementation )------------------------------------- */ - - - public function isAvailable() { - return true; - } - - public function getKeys(array $keys) { - $results = array(); - $now = time(); - foreach ($keys as $key) { - if (!isset($this->cache[$key]) && !array_key_exists($key, $this->cache)) { - continue; - } - if (isset($this->ttl[$key]) && ($this->ttl[$key] < $now)) { - continue; - } - $results[$key] = $this->cache[$key]; - } - - return $results; - } - - public function setKeys(array $keys, $ttl = null) { - - foreach ($keys as $key => $value) { - $this->cache[$key] = $value; - } - - if ($ttl) { - $end = time() + $ttl; - foreach ($keys as $key => $value) { - $this->ttl[$key] = $end; - } - } else { - foreach ($keys as $key => $value) { - unset($this->ttl[$key]); - } - } - - if ($this->limit) { - $count = count($this->cache); - if ($count > $this->limit) { - $remove = array(); - foreach ($this->cache as $key => $value) { - $remove[] = $key; - - $count--; - if ($count <= $this->limit) { - break; - } - } - - $this->deleteKeys($remove); - } - } - - return $this; - } - - public function deleteKeys(array $keys) { - foreach ($keys as $key) { - unset($this->cache[$key]); - unset($this->ttl[$key]); - } - - return $this; - } - - public function getAllKeys() { - return $this->cache; - } - - public function destroyCache() { - $this->cache = array(); - $this->ttl = array(); - - return $this; - } - -} diff --git a/src/cache/PhutilKeyValueCache.php b/src/cache/PhutilKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilKeyValueCache.php +++ /dev/null @@ -1,121 +0,0 @@ -getKeys(array($key)); - return idx($map, $key, $default); - } - - - /** - * Set a single key in cache. See @{method:setKeys} to set multiple keys at - * once. - * - * See @{method:setKeys} for a description of TTLs. - * - * @param string Key to set. - * @param wild Value to set. - * @param int|null Optional TTL. - * @return this - * @task kvimpl - */ - final public function setKey($key, $value, $ttl = null) { - return $this->setKeys(array($key => $value), $ttl); - } - - - /** - * Delete a key from the cache. See @{method:deleteKeys} to delete multiple - * keys at once. - * - * @param string Key to delete. - * @return this - * @task kvimpl - */ - final public function deleteKey($key) { - return $this->deleteKeys(array($key)); - } - - - /** - * Get data from the cache. - * - * @param list List of cache keys to retrieve. - * @return dict Dictionary of keys that were found in the - * cache. Keys not present in the cache are - * omitted, so you can detect a cache miss. - * @task kvimpl - */ - abstract public function getKeys(array $keys); - - - /** - * Put data into the key-value cache. - * - * With a TTL ("time to live"), the cache will automatically delete the key - * after a specified number of seconds. By default, there is no expiration - * policy and data will persist in cache indefinitely. - * - * @param dict Map of cache keys to values. - * @param int|null TTL for cache keys, in seconds. - * @return this - * @task kvimpl - */ - abstract public function setKeys(array $keys, $ttl = null); - - - /** - * Delete a list of keys from the cache. - * - * @param list List of keys to delete. - * @return this - * @task kvimpl - */ - abstract public function deleteKeys(array $keys); - - - /** - * Completely destroy all data in the cache. - * - * @return this - * @task kvimpl - */ - abstract public function destroyCache(); - -} diff --git a/src/cache/PhutilKeyValueCacheNamespace.php b/src/cache/PhutilKeyValueCacheNamespace.php deleted file mode 100644 --- a/src/cache/PhutilKeyValueCacheNamespace.php +++ /dev/null @@ -1,65 +0,0 @@ -namespace = $namespace.':'; - - return $this; - } - - public function setKeys(array $keys, $ttl = null) { - return parent::setKeys(array_combine( - $this->prefixKeys(array_keys($keys)), - $keys), $ttl); - } - - public function getKeys(array $keys) { - $results = parent::getKeys($this->prefixKeys($keys)); - - if (!$results) { - return array(); - } - - return array_combine( - $this->unprefixKeys(array_keys($results)), - $results); - } - - public function deleteKeys(array $keys) { - return parent::deleteKeys($this->prefixKeys($keys)); - } - - private function prefixKeys(array $keys) { - if ($this->namespace == null) { - throw new Exception(pht('Namespace not set.')); - } - - $prefixed_keys = array(); - foreach ($keys as $key) { - $prefixed_keys[] = $this->namespace.$key; - } - - return $prefixed_keys; - } - - private function unprefixKeys(array $keys) { - if ($this->namespace == null) { - throw new Exception(pht('Namespace not set.')); - } - - $unprefixed_keys = array(); - foreach ($keys as $key) { - $unprefixed_keys[] = substr($key, strlen($this->namespace)); - } - - return $unprefixed_keys; - } - -} diff --git a/src/cache/PhutilKeyValueCacheProfiler.php b/src/cache/PhutilKeyValueCacheProfiler.php deleted file mode 100644 --- a/src/cache/PhutilKeyValueCacheProfiler.php +++ /dev/null @@ -1,108 +0,0 @@ -name = $name; - return $this; - } - - public function getName() { - return $this->name; - } - - /** - * Set a profiler for cache operations. - * - * @param PhutilServiceProfiler Service profiler. - * @return this - * @task kvimpl - */ - public function setProfiler(PhutilServiceProfiler $profiler) { - $this->profiler = $profiler; - return $this; - } - - - /** - * Get the current profiler. - * - * @return PhutilServiceProfiler|null Profiler, or null if none is set. - * @task kvimpl - */ - public function getProfiler() { - return $this->profiler; - } - - - public function getKeys(array $keys) { - $call_id = null; - if ($this->getProfiler()) { - $call_id = $this->getProfiler()->beginServiceCall( - array( - 'type' => 'kvcache-get', - 'name' => $this->getName(), - 'keys' => $keys, - )); - } - - $results = parent::getKeys($keys); - - if ($call_id !== null) { - $this->getProfiler()->endServiceCall( - $call_id, - array( - 'hits' => array_keys($results), - )); - } - - return $results; - } - - - public function setKeys(array $keys, $ttl = null) { - $call_id = null; - if ($this->getProfiler()) { - $call_id = $this->getProfiler()->beginServiceCall( - array( - 'type' => 'kvcache-set', - 'name' => $this->getName(), - 'keys' => array_keys($keys), - 'ttl' => $ttl, - )); - } - - $result = parent::setKeys($keys, $ttl); - - if ($call_id !== null) { - $this->getProfiler()->endServiceCall($call_id, array()); - } - - return $result; - } - - - public function deleteKeys(array $keys) { - $call_id = null; - if ($this->getProfiler()) { - $call_id = $this->getProfiler()->beginServiceCall( - array( - 'type' => 'kvcache-del', - 'name' => $this->getName(), - 'keys' => $keys, - )); - } - - $result = parent::deleteKeys($keys); - - if ($call_id !== null) { - $this->getProfiler()->endServiceCall($call_id, array()); - } - - return $result; - } - -} diff --git a/src/cache/PhutilKeyValueCacheProxy.php b/src/cache/PhutilKeyValueCacheProxy.php deleted file mode 100644 --- a/src/cache/PhutilKeyValueCacheProxy.php +++ /dev/null @@ -1,45 +0,0 @@ -proxy = $proxy; - } - - final protected function getProxy() { - return $this->proxy; - } - - public function isAvailable() { - return $this->getProxy()->isAvailable(); - } - - - public function getKeys(array $keys) { - return $this->getProxy()->getKeys($keys); - } - - - public function setKeys(array $keys, $ttl = null) { - return $this->getProxy()->setKeys($keys, $ttl); - } - - - public function deleteKeys(array $keys) { - return $this->getProxy()->deleteKeys($keys); - } - - - public function destroyCache() { - return $this->getProxy()->destroyCache(); - } - - public function __call($method, array $arguments) { - return call_user_func_array( - array($this->getProxy(), $method), - $arguments); - } - -} diff --git a/src/cache/PhutilKeyValueCacheStack.php b/src/cache/PhutilKeyValueCacheStack.php deleted file mode 100644 --- a/src/cache/PhutilKeyValueCacheStack.php +++ /dev/null @@ -1,131 +0,0 @@ - Ordered list of key-value caches. - * @return this - * @task config - */ - public function setCaches(array $caches) { - assert_instances_of($caches, 'PhutilKeyValueCache'); - $this->cachesForward = $caches; - $this->cachesBackward = array_reverse($caches); - - return $this; - } - - - /** - * Set the readthrough TTL for the next cache operation. The TTL applies to - * any keys set by the next call to @{method:getKey} or @{method:getKeys}, - * and is reset after the call finishes. - * - * // If this causes any caches to fill, they'll fill with a 15-second TTL. - * $stack->setNextTTL(15)->getKey('porcupine'); - * - * // TTL does not persist; this will use no TTL. - * $stack->getKey('hedgehog'); - * - * @param int TTL in seconds. - * @return this - * - * @task config - */ - public function setNextTTL($ttl) { - $this->nextTTL = $ttl; - return $this; - } - - -/* -( Key-Value Cache Implementation )------------------------------------- */ - - - public function getKeys(array $keys) { - - $remaining = array_fuse($keys); - $results = array(); - $missed = array(); - - try { - foreach ($this->cachesForward as $cache) { - $result = $cache->getKeys($remaining); - $remaining = array_diff_key($remaining, $result); - $results += $result; - if (!$remaining) { - while ($cache = array_pop($missed)) { - // TODO: This sets too many results in the closer caches, although - // it probably isn't a big deal in most cases; normally we're just - // filling the request cache. - $cache->setKeys($result, $this->nextTTL); - } - break; - } - $missed[] = $cache; - } - $this->nextTTL = null; - } catch (Exception $ex) { - $this->nextTTL = null; - throw $ex; - } - - return $results; - } - - - public function setKeys(array $keys, $ttl = null) { - foreach ($this->cachesBackward as $cache) { - $cache->setKeys($keys, $ttl); - } - } - - - public function deleteKeys(array $keys) { - foreach ($this->cachesBackward as $cache) { - $cache->deleteKeys($keys); - } - } - - - public function destroyCache() { - foreach ($this->cachesBackward as $cache) { - $cache->destroyCache(); - } - } - -} diff --git a/src/cache/PhutilMemcacheKeyValueCache.php b/src/cache/PhutilMemcacheKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilMemcacheKeyValueCache.php +++ /dev/null @@ -1,153 +0,0 @@ -bucketKeys($keys); - $results = array(); - - foreach ($buckets as $bucket => $bucket_keys) { - $conn = $this->getConnection($bucket); - $result = $conn->get($bucket_keys); - if (!$result) { - // If the call fails, treat it as a miss on all keys. - $result = array(); - } - - $results += $result; - } - - return $results; - } - - public function setKeys(array $keys, $ttl = null) { - $buckets = $this->bucketKeys(array_keys($keys)); - - // Memcache interprets TTLs as: - // - // - Seconds from now, for values from 1 to 2592000 (30 days). - // - Epoch timestamp, for values larger than 2592000. - // - // We support only relative TTLs, so convert excessively large relative - // TTLs into epoch TTLs. - if ($ttl > 2592000) { - $effective_ttl = time() + $ttl; - } else { - $effective_ttl = $ttl; - } - - foreach ($buckets as $bucket => $bucket_keys) { - $conn = $this->getConnection($bucket); - - foreach ($bucket_keys as $key) { - $conn->set($key, $keys[$key], 0, $effective_ttl); - } - } - - return $this; - } - - public function deleteKeys(array $keys) { - $buckets = $this->bucketKeys($keys); - - foreach ($buckets as $bucket => $bucket_keys) { - $conn = $this->getConnection($bucket); - foreach ($bucket_keys as $key) { - $conn->delete($key); - } - } - - return $this; - } - - public function destroyCache() { - foreach ($this->servers as $key => $spec) { - $this->getConnection($key)->flush(); - } - return $this; - } - - -/* -( Managing Memcache )-------------------------------------------------- */ - - - /** - * Set available memcache servers. For example: - * - * $cache->setServers( - * array( - * array( - * 'host' => '10.0.0.20', - * 'port' => 11211, - * ), - * array( - * 'host' => '10.0.0.21', - * 'port' => 11211, - * ), - * )); - * - * @param list List of server specifications. - * @return this - * @task memcache - */ - public function setServers(array $servers) { - $this->servers = array_values($servers); - return $this; - } - - private function bucketKeys(array $keys) { - $buckets = array(); - $n = count($this->servers); - - if (!$n) { - throw new PhutilInvalidStateException('setServers'); - } - - foreach ($keys as $key) { - $bucket = (int)((crc32($key) & 0x7FFFFFFF) % $n); - $buckets[$bucket][] = $key; - } - - return $buckets; - } - - - /** - * @phutil-external-symbol function memcache_pconnect - */ - private function getConnection($server) { - if (empty($this->connections[$server])) { - $spec = $this->servers[$server]; - $host = $spec['host']; - $port = $spec['port']; - - $conn = memcache_pconnect($host, $spec['port']); - - if (!$conn) { - throw new Exception( - pht( - 'Unable to connect to memcache server (%s:%d)!', - $host, - $port)); - } - - $this->connections[$server] = $conn; - } - return $this->connections[$server]; - } - -} diff --git a/src/cache/PhutilOnDiskKeyValueCache.php b/src/cache/PhutilOnDiskKeyValueCache.php deleted file mode 100644 --- a/src/cache/PhutilOnDiskKeyValueCache.php +++ /dev/null @@ -1,205 +0,0 @@ -wait = $wait; - return $this; - } - - public function getKeys(array $keys) { - $now = time(); - - $results = array(); - $reloaded = false; - foreach ($keys as $key) { - - // Try to read the value from cache. If we miss, load (or reload) the - // cache. - - while (true) { - if (isset($this->cache[$key])) { - $val = $this->cache[$key]; - if (empty($val['ttl']) || $val['ttl'] >= $now) { - $results[$key] = $val['val']; - break; - } - } - - if ($reloaded) { - break; - } - - $this->loadCache($hold_lock = false); - $reloaded = true; - } - } - - return $results; - } - - - public function setKeys(array $keys, $ttl = null) { - if ($ttl) { - $ttl_epoch = time() + $ttl; - } else { - $ttl_epoch = null; - } - - $dicts = array(); - foreach ($keys as $key => $value) { - $dict = array( - 'val' => $value, - ); - if ($ttl_epoch) { - $dict['ttl'] = $ttl_epoch; - } - $dicts[$key] = $dict; - } - - $this->loadCache($hold_lock = true); - foreach ($dicts as $key => $dict) { - $this->cache[$key] = $dict; - } - $this->saveCache(); - - return $this; - } - - - public function deleteKeys(array $keys) { - $this->loadCache($hold_lock = true); - foreach ($keys as $key) { - unset($this->cache[$key]); - } - $this->saveCache(); - - return $this; - } - - - public function destroyCache() { - Filesystem::remove($this->getCacheFile()); - return $this; - } - - -/* -( Cache Storage )------------------------------------------------------ */ - - - /** - * @task storage - */ - public function setCacheFile($file) { - $this->cacheFile = $file; - return $this; - } - - - /** - * @task storage - */ - private function loadCache($hold_lock) { - if ($this->lock) { - throw new Exception( - pht( - 'Trying to %s with a lock!', - __FUNCTION__.'()')); - } - - $lock = PhutilFileLock::newForPath($this->getCacheFile().'.lock'); - try { - $lock->lock($this->wait); - } catch (PhutilLockException $ex) { - if ($hold_lock) { - throw $ex; - } else { - $this->cache = array(); - return; - } - } - - try { - $this->cache = array(); - if (Filesystem::pathExists($this->getCacheFile())) { - $cache = unserialize(Filesystem::readFile($this->getCacheFile())); - if ($cache) { - $this->cache = $cache; - } - } - } catch (Exception $ex) { - $lock->unlock(); - throw $ex; - } - - if ($hold_lock) { - $this->lock = $lock; - } else { - $lock->unlock(); - } - } - - - /** - * @task storage - */ - private function saveCache() { - if (!$this->lock) { - throw new PhutilInvalidStateException('loadCache'); - } - - // We're holding a lock so we're safe to do a write to a well-known file. - // Write to the same directory as the cache so the rename won't imply a - // copy across volumes. - $new = $this->getCacheFile().'.new'; - Filesystem::writeFile($new, serialize($this->cache)); - Filesystem::rename($new, $this->getCacheFile()); - - $this->lock->unlock(); - $this->lock = null; - } - - - /** - * @task storage - */ - private function getCacheFile() { - if (!$this->cacheFile) { - throw new PhutilInvalidStateException('setCacheFile'); - } - return $this->cacheFile; - } - -} diff --git a/src/cache/__tests__/PhutilKeyValueCacheTestCase.php b/src/cache/__tests__/PhutilKeyValueCacheTestCase.php deleted file mode 100644 --- a/src/cache/__tests__/PhutilKeyValueCacheTestCase.php +++ /dev/null @@ -1,267 +0,0 @@ -doCacheTest($cache); - $cache->destroyCache(); - } - - public function testInRequestCacheLimit() { - $cache = new PhutilInRequestKeyValueCache(); - $cache->setLimit(4); - - $cache->setKey(1, 1); - $cache->setKey(2, 2); - $cache->setKey(3, 3); - $cache->setKey(4, 4); - - $this->assertEqual( - array( - 1 => 1, - 2 => 2, - 3 => 3, - 4 => 4, - ), - $cache->getAllKeys()); - - - $cache->setKey(5, 5); - - $this->assertEqual( - array( - 2 => 2, - 3 => 3, - 4 => 4, - 5 => 5, - ), - $cache->getAllKeys()); - } - - public function testOnDiskCache() { - $cache = new PhutilOnDiskKeyValueCache(); - $cache->setCacheFile(new TempFile()); - $this->doCacheTest($cache); - $cache->destroyCache(); - } - - public function testAPCCache() { - $cache = new PhutilAPCKeyValueCache(); - if (!$cache->isAvailable()) { - $this->assertSkipped(pht('Cache not available.')); - } - $this->doCacheTest($cache); - } - - public function testDirectoryCache() { - $cache = new PhutilDirectoryKeyValueCache(); - - $dir = Filesystem::createTemporaryDirectory(); - $cache->setCacheDirectory($dir); - $this->doCacheTest($cache); - $cache->destroyCache(); - } - - public function testDirectoryCacheSpecialDirectoryRules() { - $cache = new PhutilDirectoryKeyValueCache(); - - $dir = Filesystem::createTemporaryDirectory(); - $dir = $dir.'/dircache/'; - $cache->setCacheDirectory($dir); - - $cache->setKey('a', 1); - $this->assertEqual(true, Filesystem::pathExists($dir.'/a.cache')); - - $cache->setKey('a/b', 1); - $this->assertEqual(true, Filesystem::pathExists($dir.'/a/')); - $this->assertEqual(true, Filesystem::pathExists($dir.'/a/b.cache')); - - $cache->deleteKey('a/b'); - $this->assertEqual(false, Filesystem::pathExists($dir.'/a/')); - $this->assertEqual(false, Filesystem::pathExists($dir.'/a/b.cache')); - - $cache->destroyCache(); - $this->assertEqual(false, Filesystem::pathExists($dir)); - } - - public function testNamespaceCache() { - $namespace = 'namespace'.mt_rand(); - $in_request_cache = new PhutilInRequestKeyValueCache(); - $cache = new PhutilKeyValueCacheNamespace($in_request_cache); - $cache->setNamespace($namespace); - - $test_info = get_class($cache); - $keys = array( - 'key1' => mt_rand(), - 'key2' => '', - 'key3' => 'Phabricator', - ); - $cache->setKeys($keys); - $cached_keys = $in_request_cache->getAllKeys(); - - foreach ($keys as $key => $value) { - $cached_key = $namespace.':'.$key; - - $this->assertTrue( - isset($cached_keys[$cached_key]), - $test_info); - - $this->assertEqual( - $value, - $cached_keys[$cached_key], - $test_info); - } - - $cache->destroyCache(); - - $this->doCacheTest($cache); - $cache->destroyCache(); - } - - public function testCacheStack() { - $req_cache = new PhutilInRequestKeyValueCache(); - $disk_cache = new PhutilOnDiskKeyValueCache(); - $disk_cache->setCacheFile(new TempFile()); - $apc_cache = new PhutilAPCKeyValueCache(); - - $stack = array( - $req_cache, - $disk_cache, - ); - - if ($apc_cache->isAvailable()) { - $stack[] = $apc_cache; - } - - $cache = new PhutilKeyValueCacheStack(); - $cache->setCaches($stack); - - $this->doCacheTest($cache); - - $disk_cache->destroyCache(); - $req_cache->destroyCache(); - } - - private function doCacheTest(PhutilKeyValueCache $cache) { - $key1 = 'test:'.mt_rand(); - $key2 = 'test:'.mt_rand(); - - $default = 'cache-miss'; - $value1 = 'cache-hit1'; - $value2 = 'cache-hit2'; - - $test_info = get_class($cache); - - // Test that we miss correctly on missing values. - - $this->assertEqual( - $default, - $cache->getKey($key1, $default), - $test_info); - $this->assertEqual( - array( - ), - $cache->getKeys(array($key1, $key2)), - $test_info); - - - // Test that we can set individual keys. - - $cache->setKey($key1, $value1); - $this->assertEqual( - $value1, - $cache->getKey($key1, $default), - $test_info); - $this->assertEqual( - array( - $key1 => $value1, - ), - $cache->getKeys(array($key1, $key2)), - $test_info); - - - // Test that we can delete individual keys. - - $cache->deleteKey($key1); - - $this->assertEqual( - $default, - $cache->getKey($key1, $default), - $test_info); - $this->assertEqual( - array( - ), - $cache->getKeys(array($key1, $key2)), - $test_info); - - - - // Test that we can set multiple keys. - - $cache->setKeys( - array( - $key1 => $value1, - $key2 => $value2, - )); - - $this->assertEqual( - $value1, - $cache->getKey($key1, $default), - $test_info); - $this->assertEqual( - array( - $key1 => $value1, - $key2 => $value2, - ), - $cache->getKeys(array($key1, $key2)), - $test_info); - - - // Test that we can delete multiple keys. - - $cache->deleteKeys(array($key1, $key2)); - - $this->assertEqual( - $default, - $cache->getKey($key1, $default), - $test_info); - $this->assertEqual( - array( - ), - $cache->getKeys(array($key1, $key2)), - $test_info); - - - // NOTE: The TTL tests are necessarily slow (we must sleep() through the - // TTLs) and do not work with APC (it does not TTL until the next request) - // so they're disabled by default. If you're developing the cache stack, - // it may be useful to run them. - - return; - - // Test that keys expire when they TTL. - - $cache->setKey($key1, $value1, 1); - $cache->setKey($key2, $value2, 5); - - $this->assertEqual($value1, $cache->getKey($key1, $default)); - $this->assertEqual($value2, $cache->getKey($key2, $default)); - - sleep(2); - - $this->assertEqual($default, $cache->getKey($key1, $default)); - $this->assertEqual($value2, $cache->getKey($key2, $default)); - - - // Test that setting a 0 TTL overwrites a nonzero TTL. - - $cache->setKey($key1, $value1, 1); - $this->assertEqual($value1, $cache->getKey($key1, $default)); - $cache->setKey($key1, $value1, 0); - $this->assertEqual($value1, $cache->getKey($key1, $default)); - sleep(2); - $this->assertEqual($value1, $cache->getKey($key1, $default)); - } - -} diff --git a/src/grammar/PhutilContextFreeGrammar.php b/src/grammar/PhutilContextFreeGrammar.php deleted file mode 100644 --- a/src/grammar/PhutilContextFreeGrammar.php +++ /dev/null @@ -1,93 +0,0 @@ -generate(); - } - return implode($implode, $paragraph); - } - - public function generate() { - $count = 0; - $rules = $this->getRules(); - return $this->applyRules('[start]', $count, $rules); - } - - final protected function applyRules($input, &$count, array $rules) { - if (++$count > $this->limit) { - throw new Exception(pht('Token replacement count exceeded limit!')); - } - - $matches = null; - preg_match_all('/(\\[[^\\]]+\\])/', $input, $matches, PREG_OFFSET_CAPTURE); - - foreach (array_reverse($matches[1]) as $token_spec) { - list($token, $offset) = $token_spec; - $token_name = substr($token, 1, -1); - $options = array(); - - if (($name_end = strpos($token_name, ','))) { - $options_parser = new PhutilSimpleOptions(); - $options = $options_parser->parse($token_name); - $token_name = substr($token_name, 0, $name_end); - } - - if (empty($rules[$token_name])) { - throw new Exception(pht("Invalid token '%s' in grammar.", $token_name)); - } - - $key = array_rand($rules[$token_name]); - $replacement = $this->applyRules($rules[$token_name][$key], - $count, $rules); - - if (isset($options['indent'])) { - if (is_numeric($options['indent'])) { - $replacement = self::strPadLines($replacement, $options['indent']); - } else { - $replacement = self::strPadLines($replacement); - } - } - if (isset($options['trim'])) { - switch ($options['trim']) { - case 'left': - $replacement = ltrim($replacement); - break; - case 'right': - $replacement = rtrim($replacement); - break; - default: - case 'both': - $replacement = trim($replacement); - break; - } - } - if (isset($options['block'])) { - $replacement = "\n".$replacement."\n"; - } - - $input = substr_replace($input, $replacement, $offset, strlen($token)); - } - - return $input; - } - - private static function strPadLines($text, $num_spaces = 2) { - $text_lines = phutil_split_lines($text); - foreach ($text_lines as $linenr => $line) { - $text_lines[$linenr] = str_repeat(' ', $num_spaces).$line; - } - - return implode('', $text_lines); - } - -} diff --git a/src/internationalization/PhutilTranslator.php b/src/internationalization/PhutilTranslator.php --- a/src/internationalization/PhutilTranslator.php +++ b/src/internationalization/PhutilTranslator.php @@ -72,6 +72,12 @@ return $this; } + /** + * @phutil-external-symbol class PhutilSafeHTML + * @phutil-external-symbol class PhutilSafeHTMLProducerInterface + * @phutil-external-symbol function phutil_escape_html + * @phutil-external-symbol function phutil_safe_html + */ public function translate($text /* , ... */) { $args = func_get_args(); diff --git a/src/internationalization/__tests__/PhutilTranslatorTestCase.php b/src/internationalization/__tests__/PhutilTranslatorTestCase.php --- a/src/internationalization/__tests__/PhutilTranslatorTestCase.php +++ b/src/internationalization/__tests__/PhutilTranslatorTestCase.php @@ -30,28 +30,6 @@ $this->assertEqual('1 beer(s)', $translator->translate('%d beer(s)', 1)); } - public function testSingleVariant() { - $translator = $this->newTranslator('en_US'); - - // In this translation, we have no alternatives for the first conversion. - $translator->setTranslations( - array( - 'Run the command %s %d time(s).' => array( - array( - 'Run the command %s once.', - 'Run the command %s %d times.', - ), - ), - )); - - $this->assertEqual( - 'Run the command ls 123 times.', - (string)$translator->translate( - 'Run the command %s %d time(s).', - hsprintf('%s', 'ls'), - 123)); - } - public function testCzech() { $translator = $this->newTranslator('cs_CZ'); $translator->setTranslations( @@ -213,45 +191,6 @@ } } - public function testHTMLTranslations() { - $string = '%s awoke suddenly at %s.'; - $when = '<4 AM>'; - - $translator = $this->newTranslator('en_US'); - - // When no components are HTML, everything is treated as a string. - $who = 'Abraham'; - $translation = $translator->translate( - $string, - $who, - $when); - $this->assertEqual( - 'string', - gettype($translation)); - $this->assertEqual( - 'Abraham awoke suddenly at <4 AM>.', - $translation); - - // When at least one component is HTML, everything is treated as HTML. - $who = phutil_tag('span', array(), 'Abraham'); - $translation = $translator->translate( - $string, - $who, - $when); - $this->assertTrue($translation instanceof PhutilSafeHTML); - $this->assertEqual( - 'Abraham awoke suddenly at <4 AM>.', - $translation->getHTMLContent()); - - $translation = $translator->translate( - $string, - $who, - new PhutilNumber(1383930802)); - $this->assertEqual( - 'Abraham awoke suddenly at 1,383,930,802.', - $translation->getHTMLContent()); - } - private function newTranslator($locale_code) { $locale = PhutilLocale::loadLocale($locale_code); return id(new PhutilTranslator()) diff --git a/src/markup/PhutilMarkupEngine.php b/src/markup/PhutilMarkupEngine.php deleted file mode 100644 --- a/src/markup/PhutilMarkupEngine.php +++ /dev/null @@ -1,32 +0,0 @@ -content = (string)$content; - } - - public function __toString() { - return $this->content; - } - - public function getHTMLContent() { - return $this->content; - } - - public function appendHTML($html /* , ... */) { - foreach (func_get_args() as $html) { - $this->content .= phutil_escape_html($html); - } - return $this; - } - - public static function applyFunction($function, $string /* , ... */) { - $args = func_get_args(); - array_shift($args); - $args = array_map('phutil_escape_html', $args); - return new PhutilSafeHTML(call_user_func_array($function, $args)); - } - -// Requires http://pecl.php.net/operator. - - public function __concat($html) { - $clone = clone $this; - return $clone->appendHTML($html); - } - - public function __assign_concat($html) { - return $this->appendHTML($html); - } - -} diff --git a/src/markup/PhutilSafeHTMLProducerInterface.php b/src/markup/PhutilSafeHTMLProducerInterface.php deleted file mode 100644 --- a/src/markup/PhutilSafeHTMLProducerInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -assertEqual( - (string)phutil_tag('br'), - (string)phutil_tag('br', array())); - - $this->assertEqual( - (string)phutil_tag('br', array()), - (string)phutil_tag('br', array(), null)); - } - - public function testTagEmpty() { - $this->assertEqual( - '
', - (string)phutil_tag('br', array(), null)); - - $this->assertEqual( - '
', - (string)phutil_tag('div', array(), null)); - - $this->assertEqual( - '
', - (string)phutil_tag('div', array(), '')); - } - - public function testTagBasics() { - $this->assertEqual( - '
', - (string)phutil_tag('br')); - - $this->assertEqual( - '
y
', - (string)phutil_tag('div', array(), 'y')); - } - - public function testTagAttributes() { - $this->assertEqual( - '
y
', - (string)phutil_tag('div', array('u' => 'v'), 'y')); - - $this->assertEqual( - '
', - (string)phutil_tag('br', array('u' => 'v'))); - } - - public function testTagEscapes() { - $this->assertEqual( - '
', - (string)phutil_tag('br', array('u' => '<'))); - - $this->assertEqual( - '

', - (string)phutil_tag('div', array(), phutil_tag('br'))); - } - - public function testTagNullAttribute() { - $this->assertEqual( - '
', - (string)phutil_tag('br', array('y' => null))); - } - - public function testTagJavascriptProtocolRejection() { - $hrefs = array( - 'javascript:alert(1)' => true, - 'JAVASCRIPT:alert(2)' => true, - - // NOTE: When interpreted as a URI, this is dropped because of leading - // whitespace. - ' javascript:alert(3)' => array(true, false), - '/' => false, - '/path/to/stuff/' => false, - '' => false, - 'http://example.com/' => false, - '#' => false, - 'javascript://anything' => true, - - // Chrome 33 and IE11, at a minimum, treat this as Javascript. - "javascript\n:alert(4)" => true, - - // Opera currently accepts a variety of unicode spaces. This test case - // has a smattering of them. - "\xE2\x80\x89javascript:" => true, - "javascript\xE2\x80\x89:" => true, - "\xE2\x80\x84javascript:" => true, - "javascript\xE2\x80\x84:" => true, - - // Because we're aggressive, all of unicode should trigger detection - // by default. - "\xE2\x98\x83javascript:" => true, - "javascript\xE2\x98\x83:" => true, - "\xE2\x98\x83javascript\xE2\x98\x83:" => true, - - // We're aggressive about this, so we'll intentionally raise false - // positives in these cases. - 'javascript~:alert(5)' => true, - '!!!javascript!!!!:alert(6)' => true, - - // However, we should raise true negatives in these slightly more - // reasonable cases. - 'javascript/:docs.html' => false, - 'javascripts:x.png' => false, - 'COOLjavascript:page' => false, - '/javascript:alert(1)' => false, - ); - - foreach (array(true, false) as $use_uri) { - foreach ($hrefs as $href => $expect) { - if (is_array($expect)) { - $expect = ($use_uri ? $expect[1] : $expect[0]); - } - - if ($use_uri) { - $href_value = new PhutilURI($href); - } else { - $href_value = $href; - } - - $caught = null; - try { - phutil_tag('a', array('href' => $href_value), 'click for candy'); - } catch (Exception $ex) { - $caught = $ex; - } - - $desc = pht( - 'Unexpected result for "%s". ', - $href, - $use_uri ? pht('Yes') : pht('No'), - $expect ? pht('Yes') : pht('No')); - - $this->assertEqual( - $expect, - $caught instanceof Exception, - $desc); - } - } - } - - public function testURIEscape() { - $this->assertEqual( - '%2B/%20%3F%23%26%3A%21xyz%25', - phutil_escape_uri('+/ ?#&:!xyz%')); - } - - public function testURIPathComponentEscape() { - $this->assertEqual( - 'a%252Fb', - phutil_escape_uri_path_component('a/b')); - - $str = ''; - for ($ii = 0; $ii <= 255; $ii++) { - $str .= chr($ii); - } - - $this->assertEqual( - $str, - phutil_unescape_uri_path_component( - rawurldecode( // Simulates webserver. - phutil_escape_uri_path_component($str)))); - } - - public function testHsprintf() { - $this->assertEqual( - '
<3
', - (string)hsprintf('
%s
', '<3')); - } - - public function testAppendHTML() { - $html = phutil_tag('hr'); - $html->appendHTML(phutil_tag('br'), ''); - $this->assertEqual('

<evil>', $html->getHTMLContent()); - } - - public function testArrayEscaping() { - $this->assertEqual( - '
<div>
', - phutil_escape_html( - array( - hsprintf('
'), - array( - array( - '<', - array( - 'd', - array( - array( - hsprintf('i'), - ), - 'v', - ), - ), - array( - array( - '>', - ), - ), - ), - ), - hsprintf('
'), - ))); - - $this->assertEqual( - '


', - phutil_tag( - 'div', - array(), - array( - array( - array( - phutil_tag('br'), - array( - phutil_tag('hr'), - ), - phutil_tag('wbr'), - ), - ), - ))->getHTMLContent()); - } - -} diff --git a/src/markup/__tests__/PhutilSafeHTMLTestCase.php b/src/markup/__tests__/PhutilSafeHTMLTestCase.php deleted file mode 100644 --- a/src/markup/__tests__/PhutilSafeHTMLTestCase.php +++ /dev/null @@ -1,19 +0,0 @@ -assertSkipped(pht('Operator extension not available.')); - } - - $a = phutil_tag('a'); - $ab = $a.phutil_tag('b'); - $this->assertEqual('', $ab->getHTMLContent()); - $this->assertEqual('', $a->getHTMLContent()); - - $a .= phutil_tag('a'); - $this->assertEqual('', $a->getHTMLContent()); - } - -} diff --git a/src/markup/render.php b/src/markup/render.php deleted file mode 100644 --- a/src/markup/render.php +++ /dev/null @@ -1,251 +0,0 @@ -` tags, if the `rel` attribute is not specified, it - * is interpreted as `rel="noreferrer"`. - * - When rendering `` tags, the `href` attribute may not begin with - * `javascript:`. - * - * These special cases can not be disabled. - * - * IMPORTANT: The `$tag` attribute and the keys of the `$attributes` array are - * trusted blindly, and not escaped. You should not pass user data in these - * parameters. - * - * @param string The name of the tag, like `a` or `div`. - * @param map A map of tag attributes. - * @param wild Content to put in the tag. - * @return PhutilSafeHTML Tag object. - */ -function phutil_tag($tag, array $attributes = array(), $content = null) { - // If the `href` attribute is present, make sure it is not a "javascript:" - // URI. We never permit these. - if (!empty($attributes['href'])) { - // This might be a URI object, so cast it to a string. - $href = (string)$attributes['href']; - - if (isset($href[0])) { - // Block 'javascript:' hrefs at the tag level: no well-designed - // application should ever use them, and they are a potent attack vector. - - // This function is deep in the core and performance sensitive, so we're - // doing a cheap version of this test first to avoid calling preg_match() - // on URIs which begin with '/' or `#`. These cover essentially all URIs - // in Phabricator. - if (($href[0] !== '/') && ($href[0] !== '#')) { - // Chrome 33 and IE 11 both interpret "javascript\n:" as a Javascript - // URI, and all browsers interpret " javascript:" as a Javascript URI, - // so be aggressive about looking for "javascript:" in the initial - // section of the string. - - $normalized_href = preg_replace('([^a-z0-9/:]+)i', '', $href); - if (preg_match('/^javascript:/i', $normalized_href)) { - throw new Exception( - pht( - "Attempting to render a tag with an '%s' attribute that begins ". - "with '%s'. This is either a serious security concern or a ". - "serious architecture concern. Seek urgent remedy.", - 'href', - 'javascript:')); - } - } - } - } - - // For tags which can't self-close, treat null as the empty string -- for - // example, always render `
`, never `
`. - static $self_closing_tags = array( - 'area' => true, - 'base' => true, - 'br' => true, - 'col' => true, - 'command' => true, - 'embed' => true, - 'frame' => true, - 'hr' => true, - 'img' => true, - 'input' => true, - 'keygen' => true, - 'link' => true, - 'meta' => true, - 'param' => true, - 'source' => true, - 'track' => true, - 'wbr' => true, - ); - - $attr_string = ''; - foreach ($attributes as $k => $v) { - if ($v === null) { - continue; - } - $v = phutil_escape_html($v); - $attr_string .= ' '.$k.'="'.$v.'"'; - } - - if ($content === null) { - if (isset($self_closing_tags[$tag])) { - return new PhutilSafeHTML('<'.$tag.$attr_string.' />'); - } else { - $content = ''; - } - } else { - $content = phutil_escape_html($content); - } - - return new PhutilSafeHTML('<'.$tag.$attr_string.'>'.$content.''); -} - -function phutil_tag_div($class, $content = null) { - return phutil_tag('div', array('class' => $class), $content); -} - -function phutil_escape_html($string) { - if ($string instanceof PhutilSafeHTML) { - return $string; - } else if ($string instanceof PhutilSafeHTMLProducerInterface) { - $result = $string->producePhutilSafeHTML(); - if ($result instanceof PhutilSafeHTML) { - return phutil_escape_html($result); - } else if (is_array($result)) { - return phutil_escape_html($result); - } else if ($result instanceof PhutilSafeHTMLProducerInterface) { - return phutil_escape_html($result); - } else { - try { - assert_stringlike($result); - return phutil_escape_html((string)$result); - } catch (Exception $ex) { - throw new Exception( - pht( - "Object (of class '%s') implements %s but did not return anything ". - "renderable from %s.", - get_class($string), - 'PhutilSafeHTMLProducerInterface', - 'producePhutilSafeHTML()')); - } - } - } else if (is_array($string)) { - $result = ''; - foreach ($string as $item) { - $result .= phutil_escape_html($item); - } - return $result; - } - - return htmlspecialchars($string, ENT_QUOTES, 'UTF-8'); -} - -function phutil_escape_html_newlines($string) { - return PhutilSafeHTML::applyFunction('nl2br', $string); -} - -/** - * Mark string as safe for use in HTML. - */ -function phutil_safe_html($string) { - if ($string == '') { - return $string; - } else if ($string instanceof PhutilSafeHTML) { - return $string; - } else { - return new PhutilSafeHTML($string); - } -} - -/** - * HTML safe version of `implode()`. - */ -function phutil_implode_html($glue, array $pieces) { - $glue = phutil_escape_html($glue); - - foreach ($pieces as $k => $piece) { - $pieces[$k] = phutil_escape_html($piece); - } - - return phutil_safe_html(implode($glue, $pieces)); -} - -/** - * Format a HTML code. This function behaves like `sprintf()`, except that all - * the normal conversions (like %s) will be properly escaped. - */ -function hsprintf($html /* , ... */) { - $args = func_get_args(); - array_shift($args); - return new PhutilSafeHTML( - vsprintf($html, array_map('phutil_escape_html', $args))); -} - - -/** - * Escape text for inclusion in a URI or a query parameter. Note that this - * method does NOT escape '/', because "%2F" is invalid in paths and Apache - * will automatically 404 the page if it's present. This will produce correct - * (the URIs will work) and desirable (the URIs will be readable) behavior in - * these cases: - * - * '/path/?param='.phutil_escape_uri($string); # OK: Query Parameter - * '/path/to/'.phutil_escape_uri($string); # OK: URI Suffix - * - * It will potentially produce the WRONG behavior in this special case: - * - * COUNTEREXAMPLE - * '/path/to/'.phutil_escape_uri($string).'/thing/'; # BAD: URI Infix - * - * In this case, any '/' characters in the string will not be escaped, so you - * will not be able to distinguish between the string and the suffix (unless - * you have more information, like you know the format of the suffix). For infix - * URI components, use @{function:phutil_escape_uri_path_component} instead. - * - * @param string Some string. - * @return string URI encoded string, except for '/'. - */ -function phutil_escape_uri($string) { - return str_replace('%2F', '/', rawurlencode($string)); -} - - -/** - * Escape text for inclusion as an infix URI substring. See discussion at - * @{function:phutil_escape_uri}. This function covers an unusual special case; - * @{function:phutil_escape_uri} is usually the correct function to use. - * - * This function will escape a string into a format which is safe to put into - * a URI path and which does not contain '/' so it can be correctly parsed when - * embedded as a URI infix component. - * - * However, you MUST decode the string with - * @{function:phutil_unescape_uri_path_component} before it can be used in the - * application. - * - * @param string Some string. - * @return string URI encoded string that is safe for infix composition. - */ -function phutil_escape_uri_path_component($string) { - return rawurlencode(rawurlencode($string)); -} - - -/** - * Unescape text that was escaped by - * @{function:phutil_escape_uri_path_component}. See - * @{function:phutil_escape_uri} for discussion. - * - * Note that this function is NOT the inverse of - * @{function:phutil_escape_uri_path_component}! It undoes additional escaping - * which is added to survive the implied unescaping performed by the webserver - * when interpreting the request. - * - * @param string Some string emitted - * from @{function:phutil_escape_uri_path_component} and - * then accessed via a web server. - * @return string Original string. - */ -function phutil_unescape_uri_path_component($string) { - return rawurldecode($string); -} diff --git a/src/markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php b/src/markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php deleted file mode 100644 --- a/src/markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php +++ /dev/null @@ -1,115 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getLanguageFromFilename($filename) { - static $default_map = array( - // All files which have file extensions that we haven't already matched - // map to their extensions. - '@\\.([^./]+)$@' => 1, - ); - - $maps = array(); - if (!empty($this->config['filename.map'])) { - $maps[] = $this->config['filename.map']; - } - $maps[] = $default_map; - - foreach ($maps as $map) { - foreach ($map as $regexp => $lang) { - $matches = null; - if (preg_match($regexp, $filename, $matches)) { - if (is_numeric($lang)) { - return idx($matches, $lang); - } else { - return $lang; - } - } - } - } - - return null; - } - - public function getHighlightFuture($language, $source) { - if ($language === null) { - $language = PhutilLanguageGuesser::guessLanguage($source); - } - - $have_pygments = !empty($this->config['pygments.enabled']); - - if ($language == 'php' && PhutilXHPASTBinary::isAvailable()) { - return id(new PhutilXHPASTSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - if ($language == 'console') { - return id(new PhutilConsoleSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - if ($language == 'diviner' || $language == 'remarkup') { - return id(new PhutilDivinerSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - if ($language == 'rainbow') { - return id(new PhutilRainbowSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - if ($language == 'php') { - return id(new PhutilLexerSyntaxHighlighter()) - ->setConfig('lexer', new PhutilPHPFragmentLexer()) - ->setConfig('language', 'php') - ->getHighlightFuture($source); - } - - if ($language == 'py' || $language == 'python') { - return id(new PhutilLexerSyntaxHighlighter()) - ->setConfig('lexer', new PhutilPythonFragmentLexer()) - ->setConfig('language', 'py') - ->getHighlightFuture($source); - } - - if ($language == 'java') { - return id(new PhutilLexerSyntaxHighlighter()) - ->setConfig('lexer', new PhutilJavaFragmentLexer()) - ->setConfig('language', 'java') - ->getHighlightFuture($source); - } - - if ($language == 'json') { - return id(new PhutilLexerSyntaxHighlighter()) - ->setConfig('lexer', new PhutilJSONFragmentLexer()) - ->getHighlightFuture($source); - } - - if ($language == 'invisible') { - return id(new PhutilInvisibleSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - // Don't invoke Pygments for plain text, since it's expensive and has - // no effect. - if ($language !== 'text' && $language !== 'txt') { - if ($have_pygments) { - return id(new PhutilPygmentsSyntaxHighlighter()) - ->setConfig('language', $language) - ->getHighlightFuture($source); - } - } - - return id(new PhutilDefaultSyntaxHighlighter()) - ->getHighlightFuture($source); - } - -} diff --git a/src/markup/syntax/engine/PhutilSyntaxHighlighterEngine.php b/src/markup/syntax/engine/PhutilSyntaxHighlighterEngine.php deleted file mode 100644 --- a/src/markup/syntax/engine/PhutilSyntaxHighlighterEngine.php +++ /dev/null @@ -1,19 +0,0 @@ -getHighlightFuture($language, $source)->resolve(); - } catch (PhutilSyntaxHighlighterException $ex) { - return id(new PhutilDefaultSyntaxHighlighter()) - ->getHighlightFuture($source) - ->resolve(); - } - } - -} diff --git a/src/markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php b/src/markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php deleted file mode 100644 --- a/src/markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php +++ /dev/null @@ -1,28 +0,0 @@ - 'php', - '/x.php' => 'php', - 'x.y.php' => 'php', - '/x.y/z.php' => 'php', - '/x.php/' => null, - ); - - $engine = new PhutilDefaultSyntaxHighlighterEngine(); - foreach ($names as $path => $language) { - $detect = $engine->getLanguageFromFilename($path); - $this->assertEqual( - $language, - $detect, - pht('Language detect for %s', $path)); - } - } - -} diff --git a/src/markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php +++ /dev/null @@ -1,51 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - $in_command = false; - $lines = explode("\n", $source); - foreach ($lines as $key => $line) { - $matches = null; - - // Parse commands like this: - // - // some/path/ $ ./bin/example # Do things - // - // ...into path, command, and comment components. - - $pattern = - '@'. - ($in_command ? '()(.*?)' : '^(\S+[\\\\/] )?([$] .*?)'). - '(#.*|\\\\)?$@'; - - if (preg_match($pattern, $line, $matches)) { - $lines[$key] = hsprintf( - '%s%s%s', - $matches[1], - $matches[2], - (!empty($matches[3]) - ? hsprintf('%s', $matches[3]) - : '')); - $in_command = (idx($matches, 3) == '\\'); - } else { - $lines[$key] = hsprintf('%s', $line); - } - } - $lines = phutil_implode_html("\n", $lines); - - return new ImmediateFuture($lines); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php +++ /dev/null @@ -1,14 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - $source = phutil_escape_html($source); - - // This highlighter isn't perfect but tries to do an okay job at getting - // some of the basics at least. There's lots of room for improvement. - - $blocks = explode("\n\n", $source); - foreach ($blocks as $key => $block) { - if (preg_match('/^[^ ](?! )/m', $block)) { - $blocks[$key] = $this->highlightBlock($block); - } - } - $source = implode("\n\n", $blocks); - - $source = phutil_safe_html($source); - return new ImmediateFuture($source); - } - - private function highlightBlock($source) { - // Highlight "@{class:...}" links to other documentation pages. - $source = $this->highlightPattern('/@{([\w@]+?):([^}]+?)}/', $source, 'nc'); - - // Highlight "@title", "@group", etc. - $source = $this->highlightPattern('/^@(\w+)/m', $source, 'k'); - - // Highlight bold, italic and monospace. - $source = $this->highlightPattern('@\\*\\*(.+?)\\*\\*@s', $source, 's'); - $source = $this->highlightPattern('@(?highlightPattern( - '@##([\s\S]+?)##|\B`(.+?)`\B@', - $source, - 's'); - - // Highlight stuff that looks like headers. - $source = $this->highlightPattern('/^=(.*)$/m', $source, 'nv'); - - return $source; - } - - private function highlightPattern($regexp, $source, $class) { - $this->replaceClass = $class; - $source = preg_replace_callback( - $regexp, - array($this, 'replacePattern'), - $source); - - return $source; - } - - public function replacePattern($matches) { - - // NOTE: The goal here is to make sure a never crosses a newline. - - $content = $matches[0]; - $content = explode("\n", $content); - foreach ($content as $key => $line) { - $content[$key] = - ''. - $line. - ''; - } - return implode("\n", $content); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php +++ /dev/null @@ -1,43 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - $keys = array_map('chr', range(0x0, 0x1F)); - $vals = array_map( - array($this, 'decimalToHtmlEntityDecoded'), range(0x2400, 0x241F)); - - $invisible = array_combine($keys, $vals); - - $result = array(); - foreach (str_split($source) as $character) { - if (isset($invisible[$character])) { - $result[] = phutil_tag( - 'span', - array('class' => 'invisible'), - $invisible[$character]); - - if ($character === "\n") { - $result[] = $character; - } - } else { - $result[] = $character; - } - } - - $result = phutil_implode_html('', $result); - return new ImmediateFuture($result); - } - - private function decimalToHtmlEntityDecoded($dec) { - return html_entity_decode("&#{$dec};"); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php +++ /dev/null @@ -1,72 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - $strip = false; - $state = 'start'; - $lang = idx($this->config, 'language'); - - if ($lang == 'php') { - if (strpos($source, 'config, 'lexer'); - $tokens = $lexer->getTokens($source, $state); - $tokens = $lexer->mergeTokens($tokens); - - $result = array(); - foreach ($tokens as $token) { - list($type, $value, $context) = $token; - - $data_name = null; - switch ($type) { - case 'nc': - case 'nf': - case 'na': - $data_name = $value; - break; - } - - if (strpos($value, "\n") !== false) { - $value = explode("\n", $value); - } else { - $value = array($value); - } - foreach ($value as $part) { - if (strlen($part)) { - if ($type) { - $result[] = phutil_tag( - 'span', - array( - 'class' => $type, - 'data-symbol-context' => $context, - 'data-symbol-name' => $data_name, - ), - $part); - } else { - $result[] = $part; - } - } - $result[] = "\n"; - } - - // Throw away the last "\n". - array_pop($result); - } - - $result = phutil_implode_html('', $result); - - return new ImmediateFuture($result); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php +++ /dev/null @@ -1,229 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - $language = idx($this->config, 'language'); - - if (preg_match('/\r(?!\n)/', $source)) { - // TODO: Pygments converts "\r" newlines into "\n" newlines, so we can't - // use it on files with "\r" newlines. If we have "\r" not followed by - // "\n" in the file, skip highlighting. - $language = null; - } - - if ($language) { - $language = $this->getPygmentsLexerNameFromLanguageName($language); - - // See T13224. Under Ubuntu, avoid leaving an intermedite "dash" shell - // process so we hit "pygmentize" directly if we have to SIGKILL this - // because it explodes. - - $future = new ExecFuture( - 'exec pygmentize -O encoding=utf-8 -O stripnl=False -f html -l %s', - $language); - - $scrub = false; - if ($language == 'php' && strpos($source, 'setTimeout(15); - - $future->write($source); - - return new PhutilDefaultSyntaxHighlighterEnginePygmentsFuture( - $future, - $source, - $scrub); - } - - return id(new PhutilDefaultSyntaxHighlighter()) - ->getHighlightFuture($source); - } - - private function getPygmentsLexerNameFromLanguageName($language) { - static $map = array( - 'adb' => 'ada', - 'ads' => 'ada', - 'ahkl' => 'ahk', - 'as' => 'as3', - 'asax' => 'aspx-vb', - 'ascx' => 'aspx-vb', - 'ashx' => 'aspx-vb', - 'ASM' => 'nasm', - 'asm' => 'nasm', - 'asmx' => 'aspx-vb', - 'aspx' => 'aspx-vb', - 'autodelegate' => 'myghty', - 'autohandler' => 'mason', - 'aux' => 'tex', - 'axd' => 'aspx-vb', - 'b' => 'brainfuck', - 'bas' => 'vb.net', - 'bf' => 'brainfuck', - 'bmx' => 'blitzmax', - 'c++' => 'cpp', - 'c++-objdump' => 'cpp-objdump', - 'cc' => 'cpp', - 'cfc' => 'cfm', - 'cfg' => 'ini', - 'cfml' => 'cfm', - 'cl' => 'common-lisp', - 'clj' => 'clojure', - 'cmd' => 'bat', - 'coffee' => 'coffee-script', - 'cs' => 'csharp', - 'csh' => 'tcsh', - 'cw' => 'redcode', - 'cxx' => 'cpp', - 'cxx-objdump' => 'cpp-objdump', - 'darcspatch' => 'dpatch', - 'def' => 'modula2', - 'dhandler' => 'mason', - 'di' => 'd', - 'duby' => 'rb', - 'dyl' => 'dylan', - 'ebuild' => 'bash', - 'eclass' => 'bash', - 'el' => 'common-lisp', - 'eps' => 'postscript', - 'erl' => 'erlang', - 'erl-sh' => 'erl', - 'f' => 'fortran', - 'f90' => 'fortran', - 'feature' => 'Cucumber', - 'fhtml' => 'velocity', - 'flx' => 'felix', - 'flxh' => 'felix', - 'frag' => 'glsl', - 'g' => 'antlr-ruby', - 'G' => 'antlr-ruby', - 'gdc' => 'gooddata-cl', - 'gemspec' => 'rb', - 'geo' => 'glsl', - 'GNUmakefile' => 'make', - 'h' => 'c', - 'h++' => 'cpp', - 'hh' => 'cpp', - 'hpp' => 'cpp', - 'hql' => 'sql', - 'hrl' => 'erlang', - 'hs' => 'haskell', - 'htaccess' => 'apacheconf', - 'htm' => 'html', - 'html' => 'html+evoque', - 'hxx' => 'cpp', - 'hy' => 'hybris', - 'hyb' => 'hybris', - 'ik' => 'ioke', - 'inc' => 'pov', - 'j' => 'objective-j', - 'jbst' => 'duel', - 'kid' => 'genshi', - 'ksh' => 'bash', - 'less' => 'css', - 'lgt' => 'logtalk', - 'lisp' => 'common-lisp', - 'll' => 'llvm', - 'm' => 'objective-c', - 'mak' => 'make', - 'Makefile' => 'make', - 'makefile' => 'make', - 'man' => 'groff', - 'mao' => 'mako', - 'mc' => 'mason', - 'md' => 'minid', - 'mhtml' => 'mason', - 'mi' => 'mason', - 'ml' => 'ocaml', - 'mli' => 'ocaml', - 'mll' => 'ocaml', - 'mly' => 'ocaml', - 'mm' => 'objective-c', - 'mo' => 'modelica', - 'mod' => 'modula2', - 'moo' => 'moocode', - 'mu' => 'mupad', - 'myt' => 'myghty', - 'ns2' => 'newspeak', - 'pas' => 'delphi', - 'patch' => 'diff', - 'phtml' => 'html+php', - 'pl' => 'prolog', - 'plot' => 'gnuplot', - 'plt' => 'gnuplot', - 'pm' => 'perl', - 'po' => 'pot', - 'pp' => 'puppet', - 'pro' => 'prolog', - 'proto' => 'protobuf', - 'ps' => 'postscript', - 'pxd' => 'cython', - 'pxi' => 'cython', - 'py' => 'python', - 'pyw' => 'python', - 'pyx' => 'cython', - 'R' => 'splus', - 'r' => 'rebol', - 'r3' => 'rebol', - 'rake' => 'rb', - 'Rakefile' => 'rb', - 'rbw' => 'rb', - 'rbx' => 'rb', - 'rest' => 'rst', - 'rl' => 'ragel-em', - 'robot' => 'robotframework', - 'Rout' => 'rconsole', - 'rss' => 'xml', - 's' => 'gas', - 'S' => 'splus', - 'sc' => 'python', - 'scm' => 'scheme', - 'SConscript' => 'python', - 'SConstruct' => 'python', - 'scss' => 'css', - 'sh' => 'bash', - 'sh-session' => 'console', - 'spt' => 'cheetah', - 'sqlite3-console' => 'sqlite3', - 'st' => 'smalltalk', - 'sv' => 'v', - 'tac' => 'python', - 'tmpl' => 'cheetah', - 'toc' => 'tex', - 'tpl' => 'smarty', - 'txt' => 'text', - 'vapi' => 'vala', - 'vb' => 'vb.net', - 'vert' => 'glsl', - 'vhd' => 'vhdl', - 'vimrc' => 'vim', - 'vm' => 'velocity', - 'weechatlog' => 'irc', - 'wlua' => 'lua', - 'wsdl' => 'xml', - 'xhtml' => 'html', - 'xml' => 'xml+evoque', - 'xqy' => 'xquery', - 'xsd' => 'xml', - 'xsl' => 'xslt', - 'xslt' => 'xml', - 'yml' => 'yaml', - ); - - return idx($map, $language, $language); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php +++ /dev/null @@ -1,46 +0,0 @@ -config[$key] = $value; - return $this; - } - - public function getHighlightFuture($source) { - - $color = 0; - $colors = array( - 'rbw_r', - 'rbw_o', - 'rbw_y', - 'rbw_g', - 'rbw_b', - 'rbw_i', - 'rbw_v', - ); - - $result = array(); - foreach (phutil_utf8v($source) as $character) { - if ($character == ' ' || $character == "\n") { - $result[] = $character; - continue; - } - $result[] = phutil_tag( - 'span', - array('class' => $colors[$color]), - $character); - $color = ($color + 1) % count($colors); - } - - $result = phutil_implode_html('', $result); - return new ImmediateFuture($result); - } - -} diff --git a/src/markup/syntax/highlighter/PhutilSyntaxHighlighter.php b/src/markup/syntax/highlighter/PhutilSyntaxHighlighter.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/PhutilSyntaxHighlighter.php +++ /dev/null @@ -1,6 +0,0 @@ -setConfig('language', 'json') - ->setConfig('lexer', new PhutilJSONFragmentLexer()); - - $path = dirname(__FILE__).'/data/jsonfragment/'; - foreach (Filesystem::listDirectory($path, $include_hidden = false) as $f) { - if (preg_match('/.test$/', $f)) { - $expect = preg_replace('/.test$/', '.expect', $f); - $source = Filesystem::readFile($path.'/'.$f); - - $this->assertEqual( - Filesystem::readFile($path.'/'.$expect), - (string)$highlighter->getHighlightFuture($source)->resolve(), - $f); - } - } - } - -} diff --git a/src/markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php b/src/markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php +++ /dev/null @@ -1,25 +0,0 @@ -setConfig('language', 'php'); - $highlighter->setConfig('lexer', new PhutilPHPFragmentLexer()); - - - $path = dirname(__FILE__).'/phpfragment/'; - foreach (Filesystem::listDirectory($path, $include_hidden = false) as $f) { - if (preg_match('/.test$/', $f)) { - $expect = preg_replace('/.test$/', '.expect', $f); - $source = Filesystem::readFile($path.'/'.$f); - - $this->assertEqual( - Filesystem::readFile($path.'/'.$expect), - (string)$highlighter->getHighlightFuture($source)->resolve(), - $f); - } - } - } - -} diff --git a/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php b/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php +++ /dev/null @@ -1,39 +0,0 @@ -getHighlightFuture($source); - return $future->resolve(); - } - - private function read($file) { - $path = dirname(__FILE__).'/xhpast/'.$file; - return Filesystem::readFile($path); - } - - public function testBuiltinClassnames() { - $this->assertEqual( - $this->read('builtin-classname.expect'), - (string)$this->highlight($this->read('builtin-classname.source')), - pht('Builtin classnames should not be marked as linkable symbols.')); - $this->assertEqual( - rtrim($this->read('trailing-comment.expect')), - (string)$this->highlight($this->read('trailing-comment.source')), - pht('Trailing comments should not be dropped.')); - $this->assertEqual( - $this->read('multiline-token.expect'), - (string)$this->highlight($this->read('multiline-token.source')), - pht('Multi-line tokens should be split across lines.')); - $this->assertEqual( - $this->read('leading-whitespace.expect'), - (string)$this->highlight($this->read('leading-whitespace.source')), - pht('Snippets with leading whitespace should be preserved.')); - $this->assertEqual( - $this->read('no-leading-whitespace.expect'), - (string)$this->highlight($this->read('no-leading-whitespace.source')), - pht('Snippets with no leading whitespace should be preserved.')); - } - -} diff --git a/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.expect b/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.expect +++ /dev/null @@ -1,12 +0,0 @@ -{ - "key": 3.5, - "true": true, - "false": false, - "null": null, - "list": [1, 2, 3], - "object": { - "k1": "v1" - }, - "numbers": [0e1, 1e-1, -1e-1, -1e+1], - "\"\u1234'abc[]{}..." -} diff --git a/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.test b/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.test deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/data/jsonfragment/basics.test +++ /dev/null @@ -1,12 +0,0 @@ -{ - "key": 3.5, - "true": true, - "false": false, - "null": null, - "list": [1, 2, 3], - "object": { - "k1": "v1" - }, - "numbers": [0e1, 1e-1, -1e-1, -1e+1], - "\"\u1234'abc[]{}..." -} diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.expect b/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.expect +++ /dev/null @@ -1,16 +0,0 @@ -<? - -// comment? comment! ?> - -data - -<?php - -__halt_compiler /* ! */ ( // ) -) /* ;;;; */ - -; - -data data -<?php -data diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.test b/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.test deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/abuse.test +++ /dev/null @@ -1,16 +0,0 @@ - - -data - -public function f() { - ExampleClass::EXAMPLE_CONSTANT; - ExampleClass::exampleMethod(); - example_function(); -} diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/basics.test b/src/markup/syntax/highlighter/__tests__/phpfragment/basics.test deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/basics.test +++ /dev/null @@ -1,5 +0,0 @@ -public function f() { - ExampleClass::EXAMPLE_CONSTANT; - ExampleClass::exampleMethod(); - example_function(); -} diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.expect b/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.expect +++ /dev/null @@ -1,3 +0,0 @@ - foreach ($x as $y) { - z(); - } diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.test b/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.test deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/leading-whitespace.test +++ /dev/null @@ -1,3 +0,0 @@ - foreach ($x as $y) { - z(); - } diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.expect b/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.expect +++ /dev/null @@ -1,3 +0,0 @@ -foreach ($x as $y) { - z(); -} diff --git a/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.test b/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.test deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/phpfragment/no-leading-whitespace.test +++ /dev/null @@ -1,3 +0,0 @@ -foreach ($x as $y) { - z(); -} diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.expect b/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.expect +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -class C { - public function f() { - D::X; - self::X; - parent::X; - static::X; - } -} diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.source b/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.source deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/builtin-classname.source +++ /dev/null @@ -1,10 +0,0 @@ -foreach ($x as $y) { - z(); - } diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/leading-whitespace.source b/src/markup/syntax/highlighter/__tests__/xhpast/leading-whitespace.source deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/leading-whitespace.source +++ /dev/null @@ -1,3 +0,0 @@ - foreach ($x as $y) { - z(); - } diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.expect b/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.expect +++ /dev/null @@ -1,5 +0,0 @@ -<?php - -/* this comment -extends across -multiple lines */ diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.source b/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.source deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/multiline-token.source +++ /dev/null @@ -1,5 +0,0 @@ -foreach ($x as $y) { - z(); -} diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/no-leading-whitespace.source b/src/markup/syntax/highlighter/__tests__/xhpast/no-leading-whitespace.source deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/no-leading-whitespace.source +++ /dev/null @@ -1,3 +0,0 @@ -foreach ($x as $y) { - z(); -} diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.expect b/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.expect deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.expect +++ /dev/null @@ -1,3 +0,0 @@ -<?php -// xyz - diff --git a/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.source b/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.source deleted file mode 100644 --- a/src/markup/syntax/highlighter/__tests__/xhpast/trailing-comment.source +++ /dev/null @@ -1,2 +0,0 @@ -source = $source; - $this->scrub = $scrub; - } - - protected function didReceiveResult($result) { - list($err, $stdout, $stderr) = $result; - - if (!$err && strlen($stdout)) { - // Strip off fluff Pygments adds. - $stdout = preg_replace( - '@^
(.*)
\s*$@s', - '\1', - $stdout); - if ($this->scrub) { - $stdout = preg_replace('/^.*\n/', '', $stdout); - } - return phutil_safe_html($stdout); - } - - throw new PhutilSyntaxHighlighterException($stderr, $err); - } - -} diff --git a/src/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php b/src/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php deleted file mode 100644 --- a/src/markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php +++ /dev/null @@ -1,262 +0,0 @@ -source = $source; - $this->scrub = $scrub; - } - - protected function didReceiveResult($result) { - try { - return $this->applyXHPHighlight($result); - } catch (Exception $ex) { - // XHP can't highlight source that isn't syntactically valid. Fall back - // to the fragment lexer. - $source = ($this->scrub - ? preg_replace('/^.*\n/', '', $this->source) - : $this->source); - return id(new PhutilLexerSyntaxHighlighter()) - ->setConfig('lexer', new PhutilPHPFragmentLexer()) - ->setConfig('language', 'php') - ->getHighlightFuture($source) - ->resolve(); - } - } - - private function applyXHPHighlight($result) { - - // We perform two passes here: one using the AST to find symbols we care - // about -- particularly, class names and function names. These are used - // in the crossreference stuff to link into Diffusion. After we've done our - // AST pass, we do a followup pass on the token stream to catch all the - // simple stuff like strings and comments. - - $tree = XHPASTTree::newFromDataAndResolvedExecFuture( - $this->source, - $result); - - $root = $tree->getRootNode(); - - $tokens = $root->getTokens(); - $interesting_symbols = $this->findInterestingSymbols($root); - - - if ($this->scrub) { - // If we're scrubbing, we prepended "= 2) { - if ($tokens[0]->getTypeName() === 'T_OPEN_TAG') { - if ($tokens[1]->getTypeName() === 'T_WHITESPACE') { - $ok = true; - } - } - } - - if (!$ok) { - throw new Exception( - pht( - 'Expected T_OPEN_TAG, T_WHITESPACE tokens at head of results '. - 'for highlighting parse of PHP snippet.')); - } - - // Remove the "getValue(); - if ((strlen($value) < 1) || ($value[0] != "\n")) { - throw new Exception( - pht( - 'Expected "\\n" at beginning of T_WHITESPACE token at head of '. - 'tokens for highlighting parse of PHP snippet.')); - } - - $value = substr($value, 1); - $tokens[1]->overwriteValue($value); - } - - $out = array(); - foreach ($tokens as $key => $token) { - $value = $token->getValue(); - $class = null; - $multi = false; - $attrs = array(); - if (isset($interesting_symbols[$key])) { - $sym = $interesting_symbols[$key]; - $class = $sym[0]; - $attrs['data-symbol-context'] = idx($sym, 'context'); - $attrs['data-symbol-name'] = idx($sym, 'symbol'); - } else { - switch ($token->getTypeName()) { - case 'T_WHITESPACE': - break; - case 'T_DOC_COMMENT': - $class = 'dc'; - $multi = true; - break; - case 'T_COMMENT': - $class = 'c'; - $multi = true; - break; - case 'T_CONSTANT_ENCAPSED_STRING': - case 'T_ENCAPSED_AND_WHITESPACE': - case 'T_INLINE_HTML': - $class = 's'; - $multi = true; - break; - case 'T_VARIABLE': - $class = 'nv'; - break; - case 'T_OPEN_TAG': - case 'T_OPEN_TAG_WITH_ECHO': - case 'T_CLOSE_TAG': - $class = 'o'; - break; - case 'T_LNUMBER': - case 'T_DNUMBER': - $class = 'm'; - break; - case 'T_STRING': - static $magic = array( - 'true' => true, - 'false' => true, - 'null' => true, - ); - if (isset($magic[strtolower($value)])) { - $class = 'k'; - break; - } - $class = 'nx'; - break; - default: - $class = 'k'; - break; - } - } - - if ($class) { - $attrs['class'] = $class; - if ($multi) { - // If the token may have multiple lines in it, make sure each - // crosses no more than one line so the lines can be put - // in a table, etc., later. - $value = phutil_split_lines($value, $retain_endings = true); - } else { - $value = array($value); - } - foreach ($value as $val) { - $out[] = phutil_tag('span', $attrs, $val); - } - } else { - $out[] = $value; - } - } - - return phutil_implode_html('', $out); - } - - private function findInterestingSymbols(XHPASTNode $root) { - // Class name symbols appear in: - // class X extends X implements X, X { ... } - // new X(); - // $x instanceof X - // catch (X $x) - // function f(X $x) - // X::f(); - // X::$m; - // X::CONST; - - // These are PHP builtin tokens which can appear in a classname context. - // Don't link them since they don't go anywhere useful. - static $builtin_class_tokens = array( - 'self' => true, - 'parent' => true, - 'static' => true, - ); - - // Fortunately XHPAST puts all of these in a special node type so it's - // easy to find them. - $result_map = array(); - $class_names = $root->selectDescendantsOfType('n_CLASS_NAME'); - foreach ($class_names as $class_name) { - foreach ($class_name->getTokens() as $key => $token) { - if (isset($builtin_class_tokens[$token->getValue()])) { - // This is something like "self::method()". - continue; - } - $result_map[$key] = array( - 'nc', // "Name, Class" - 'symbol' => $class_name->getConcreteString(), - ); - } - } - - // Function name symbols appear in: - // f() - - $function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); - foreach ($function_calls as $call) { - $call = $call->getChildByIndex(0); - if ($call->getTypeName() == 'n_SYMBOL_NAME') { - // This is a normal function call, not some $f() shenanigans. - foreach ($call->getTokens() as $key => $token) { - $result_map[$key] = array( - 'nf', // "Name, Function" - 'symbol' => $call->getConcreteString(), - ); - } - } - } - - // Upon encountering $x->y, link y without context, since $x is unknown. - - $prop_access = $root->selectDescendantsOfType('n_OBJECT_PROPERTY_ACCESS'); - foreach ($prop_access as $access) { - $right = $access->getChildByIndex(1); - if ($right->getTypeName() == 'n_INDEX_ACCESS') { - // otherwise $x->y[0] doesn't get highlighted - $right = $right->getChildByIndex(0); - } - if ($right->getTypeName() == 'n_STRING') { - foreach ($right->getTokens() as $key => $token) { - $result_map[$key] = array( - 'na', // "Name, Attribute" - 'symbol' => $right->getConcreteString(), - ); - } - } - } - - // Upon encountering x::y, try to link y with context x. - - $static_access = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); - foreach ($static_access as $access) { - $class = $access->getChildByIndex(0); - $right = $access->getChildByIndex(1); - if ($class->getTypeName() == 'n_CLASS_NAME' && - ($right->getTypeName() == 'n_STRING' || - $right->getTypeName() == 'n_VARIABLE')) { - $classname = head($class->getTokens())->getValue(); - $result = array( - 'na', - 'symbol' => ltrim($right->getConcreteString(), '$'), - ); - if (!isset($builtin_class_tokens[$classname])) { - $result['context'] = $classname; - } - foreach ($right->getTokens() as $key => $token) { - $result_map[$key] = $result; - } - } - } - - return $result_map; - } - -} diff --git a/src/parser/PhutilPygmentizeParser.php b/src/parser/PhutilPygmentizeParser.php deleted file mode 100644 --- a/src/parser/PhutilPygmentizeParser.php +++ /dev/null @@ -1,83 +0,0 @@ -map = $map; - return $this; - } - - public function getMap() { - return $this->map; - } - - public function parse($block) { - $class_look = 'class="'; - $class_len = strlen($class_look); - - $class_start = null; - - $map = $this->map; - - $len = strlen($block); - $out = ''; - $mode = 'text'; - for ($ii = 0; $ii < $len; $ii++) { - $c = $block[$ii]; - switch ($mode) { - case 'text': - // We're in general text between tags, and just passing characers - // through unmodified. - if ($c == '<') { - $mode = 'tag'; - } - $out .= $c; - break; - case 'tag': - // We're inside a tag, and looking for `class="` so we can rewrite - // it. - if ($c == '>') { - $mode = 'text'; - } - if ($c == 'c') { - if (!substr_compare($block, $class_look, $ii, $class_len)) { - $mode = 'class'; - $ii += $class_len; - $class_start = $ii; - } - } - - if ($mode != 'class') { - $out .= $c; - } - break; - case 'class': - // We're inside a `class="..."` tag, and looking for the ending quote - // so we can replace it. - if ($c == '"') { - $class = substr($block, $class_start, $ii - $class_start); - - // If this class is present in the map, rewrite it into an inline - // style attribute. - if (isset($map[$class])) { - $out .= 'style="'.phutil_escape_html($map[$class]).'"'; - } else { - $out .= 'class="'.$class.'"'; - } - - $mode = 'tag'; - } - break; - } - } - - return $out; - } - -} diff --git a/src/parser/__tests__/PhutilPygmentizeParserTestCase.php b/src/parser/__tests__/PhutilPygmentizeParserTestCase.php deleted file mode 100644 --- a/src/parser/__tests__/PhutilPygmentizeParserTestCase.php +++ /dev/null @@ -1,43 +0,0 @@ -tryParser( - '', - '', - array(), - pht('Empty')); - - $this->tryParser( - '1', - '1', - array( - 'mi' => 'color: #ff0000', - ), - pht('Simple')); - - $this->tryParser( - '1', - '1', - array(), - pht('Missing Class')); - - $this->tryParser( - 'X', - 'X', - array( - 'nc' => 'color: #ff0000', - ), - pht('Extra Attribute')); - } - - private function tryParser($input, $expect, array $map, $label) { - $actual = id(new PhutilPygmentizeParser()) - ->setMap($map) - ->parse($input); - - $this->assertEqual($expect, $actual, pht('Pygmentize Parser: %s', $label)); - } - -} diff --git a/src/sprites/PhutilSprite.php b/src/sprites/PhutilSprite.php deleted file mode 100644 --- a/src/sprites/PhutilSprite.php +++ /dev/null @@ -1,76 +0,0 @@ -name = $name; - return $this; - } - - public function getName() { - return $this->name; - } - - public function setTargetCSS($target_css) { - $this->targetCSS = $target_css; - return $this; - } - - public function getTargetCSS() { - return $this->targetCSS; - } - - public function setSourcePosition($x, $y) { - $this->sourceX = $x; - $this->sourceY = $y; - return $this; - } - - public function setSourceSize($w, $h) { - $this->sourceW = $w; - $this->sourceH = $h; - return $this; - } - - public function getSourceH() { - return $this->sourceH; - } - - public function getSourceW() { - return $this->sourceW; - } - - public function getSourceY() { - return $this->sourceY; - } - - public function getSourceX() { - return $this->sourceX; - } - - public function setSourceFile($source_file, $scale = 1) { - $this->sourceFiles[$scale] = $source_file; - return $this; - } - - public function getSourceFile($scale) { - if (empty($this->sourceFiles[$scale])) { - throw new Exception(pht("No source file for scale '%s'!", $scale)); - } - - return $this->sourceFiles[$scale]; - } - -} diff --git a/src/sprites/PhutilSpriteSheet.php b/src/sprites/PhutilSpriteSheet.php deleted file mode 100644 --- a/src/sprites/PhutilSpriteSheet.php +++ /dev/null @@ -1,385 +0,0 @@ -generated = false; - $this->sprites[] = $sprite; - return $this; - } - - public function setCSSHeader($header) { - $this->generated = false; - $this->cssHeader = $header; - return $this; - } - - public function setScales(array $scales) { - $this->scales = array_values($scales); - return $this; - } - - public function getScales() { - return $this->scales; - } - - public function setSheetType($type) { - $this->type = $type; - return $this; - } - - public function setBasePath($base_path) { - $this->basePath = $base_path; - return $this; - } - - private function generate() { - if ($this->generated) { - return; - } - - $multi_row = true; - $multi_col = true; - $margin_w = 1; - $margin_h = 1; - - $type = $this->type; - switch ($type) { - case self::TYPE_STANDARD: - break; - case self::TYPE_REPEAT_X: - $multi_col = false; - $margin_w = 0; - - $width = null; - foreach ($this->sprites as $sprite) { - if ($width === null) { - $width = $sprite->getSourceW(); - } else if ($width !== $sprite->getSourceW()) { - throw new Exception( - pht( - "All sprites in a '%s' sheet must have the same width.", - 'repeat-x')); - } - } - break; - case self::TYPE_REPEAT_Y: - $multi_row = false; - $margin_h = 0; - - $height = null; - foreach ($this->sprites as $sprite) { - if ($height === null) { - $height = $sprite->getSourceH(); - } else if ($height !== $sprite->getSourceH()) { - throw new Exception( - pht( - "All sprites in a '%s' sheet must have the same height.", - 'repeat-y')); - } - } - break; - default: - throw new Exception(pht("Unknown sprite sheet type '%s'!", $type)); - } - - - $css = array(); - if ($this->cssHeader) { - $css[] = $this->cssHeader; - } - - $out_w = 0; - $out_h = 0; - - // Lay out the sprite sheet. We attempt to build a roughly square sheet - // so it's easier to manage, since 2000x20 is more cumbersome for humans - // to deal with than 200x200. - // - // To do this, we use a simple greedy algorithm, adding sprites one at a - // time. For each sprite, if the sheet is at least as wide as it is tall - // we create a new row. Otherwise, we try to add it to an existing row. - // - // This isn't optimal, but does a reasonable job in most cases and isn't - // too messy. - - // Group the sprites by their sizes. We lay them out in the sheet as - // boxes, but then put them into the boxes in the order they were added - // so similar sprites end up nearby on the final sheet. - $boxes = array(); - foreach (array_reverse($this->sprites) as $sprite) { - $s_w = $sprite->getSourceW() + $margin_w; - $s_h = $sprite->getSourceH() + $margin_h; - $boxes[$s_w][$s_h][] = $sprite; - } - - $rows = array(); - foreach ($this->sprites as $sprite) { - $s_w = $sprite->getSourceW() + $margin_w; - $s_h = $sprite->getSourceH() + $margin_h; - - // Choose a row for this sprite. - $maybe = array(); - foreach ($rows as $key => $row) { - if ($row['h'] < $s_h) { - // We can only add it to a row if the row is at least as tall as the - // sprite. - continue; - } - // We prefer rows which have the same height as the sprite, and then - // rows which aren't yet very wide. - $wasted_v = ($row['h'] - $s_h); - $wasted_h = ($row['w'] / $out_w); - $maybe[$key] = $wasted_v + $wasted_h; - } - - $row_key = null; - if ($maybe && $multi_col) { - // If there were any candidate rows, pick the best one. - asort($maybe); - $row_key = head_key($maybe); - } - - if ($row_key !== null && $multi_row) { - // If there's a candidate row, but adding the sprite to it would make - // the sprite wider than it is tall, create a new row instead. This - // generally keeps the sprite square-ish. - if ($rows[$row_key]['w'] + $s_w > $out_h) { - $row_key = null; - } - } - - if ($row_key === null) { - // Add a new row. - $rows[] = array( - 'w' => 0, - 'h' => $s_h, - 'boxes' => array(), - ); - $row_key = last_key($rows); - $out_h += $s_h; - } - - // Add the sprite box to the row. - $row = $rows[$row_key]; - $row['w'] += $s_w; - $row['boxes'][] = array($s_w, $s_h); - $rows[$row_key] = $row; - - $out_w = max($row['w'], $out_w); - } - - $images = array(); - foreach ($this->scales as $scale) { - $img = imagecreatetruecolor($out_w * $scale, $out_h * $scale); - imagesavealpha($img, true); - imagefill($img, 0, 0, imagecolorallocatealpha($img, 0, 0, 0, 127)); - - $images[$scale] = $img; - } - - - // Put the shorter rows first. At the same height, put the wider rows first. - // This makes the resulting sheet more human-readable. - foreach ($rows as $key => $row) { - $rows[$key]['sort'] = $row['h'] + (1 - ($row['w'] / $out_w)); - } - $rows = isort($rows, 'sort'); - - $pos_x = 0; - $pos_y = 0; - $rules = array(); - foreach ($rows as $row) { - $max_h = 0; - foreach ($row['boxes'] as $box) { - $sprite = array_pop($boxes[$box[0]][$box[1]]); - - foreach ($images as $scale => $img) { - $src = $this->loadSource($sprite, $scale); - imagecopy( - $img, - $src, - $scale * $pos_x, $scale * $pos_y, - $scale * $sprite->getSourceX(), $scale * $sprite->getSourceY(), - $scale * $sprite->getSourceW(), $scale * $sprite->getSourceH()); - } - - $rule = $sprite->getTargetCSS(); - $cssx = (-$pos_x).'px'; - $cssy = (-$pos_y).'px'; - - $rules[$sprite->getName()] = "{$rule} {\n". - " background-position: {$cssx} {$cssy};\n}"; - - $pos_x += $sprite->getSourceW() + $margin_w; - $max_h = max($max_h, $sprite->getSourceH()); - } - $pos_x = 0; - $pos_y += $max_h + $margin_h; - } - - // Generate CSS rules in input order. - foreach ($this->sprites as $sprite) { - $css[] = $rules[$sprite->getName()]; - } - - $this->images = $images; - $this->css = implode("\n\n", $css)."\n"; - $this->generated = true; - } - - public function generateImage($path, $scale = 1) { - $this->generate(); - $this->log(pht("Writing sprite '%s'...", $path)); - imagepng($this->images[$scale], $path); - return $this; - } - - public function generateCSS($path) { - $this->generate(); - $this->log(pht("Writing CSS '%s'...", $path)); - - $out = $this->css; - $out = str_replace('{X}', imagesx($this->images[1]), $out); - $out = str_replace('{Y}', imagesy($this->images[1]), $out); - - Filesystem::writeFile($path, $out); - return $this; - } - - public function needsRegeneration(array $manifest) { - return ($this->buildManifest() !== $manifest); - } - - private function buildManifest() { - $output = array(); - foreach ($this->sprites as $sprite) { - $output[$sprite->getName()] = array( - 'name' => $sprite->getName(), - 'rule' => $sprite->getTargetCSS(), - 'hash' => $this->loadSourceHash($sprite), - ); - } - - ksort($output); - - $data = array( - 'version' => self::MANIFEST_VERSION, - 'sprites' => $output, - 'scales' => $this->scales, - 'header' => $this->cssHeader, - 'type' => $this->type, - ); - - return $data; - } - - public function generateManifest($path) { - $data = $this->buildManifest(); - - $json = new PhutilJSON(); - $data = $json->encodeFormatted($data); - Filesystem::writeFile($path, $data); - return $this; - } - - private function log($message) { - echo $message."\n"; - } - - private function loadSourceHash(PhutilSprite $sprite) { - $inputs = array(); - - foreach ($this->scales as $scale) { - $file = $sprite->getSourceFile($scale); - - // If two users have a project in different places, like: - // - // /home/alincoln/project - // /home/htaft/project - // - // ...we want to ignore the `/home/alincoln` part when hashing the sheet, - // since the sprites don't change when the project directory moves. If - // the base path is set, build the hashes using paths relative to the - // base path. - - $file_key = $file; - if ($this->basePath) { - $file_key = Filesystem::readablePath($file, $this->basePath); - } - - if (empty($this->hashes[$file_key])) { - $this->hashes[$file_key] = md5(Filesystem::readFile($file)); - } - - $inputs[] = $file_key; - $inputs[] = $this->hashes[$file_key]; - } - - $inputs[] = $sprite->getSourceX(); - $inputs[] = $sprite->getSourceY(); - $inputs[] = $sprite->getSourceW(); - $inputs[] = $sprite->getSourceH(); - - return md5(implode(':', $inputs)); - } - - private function loadSource(PhutilSprite $sprite, $scale) { - $file = $sprite->getSourceFile($scale); - if (empty($this->sources[$file])) { - $data = Filesystem::readFile($file); - $image = imagecreatefromstring($data); - $this->sources[$file] = array( - 'image' => $image, - 'x' => imagesx($image), - 'y' => imagesy($image), - ); - } - - $s_w = $sprite->getSourceW() * $scale; - $i_w = $this->sources[$file]['x']; - if ($s_w > $i_w) { - throw new Exception( - pht( - "Sprite source for '%s' is too small (expected width %d, found %d).", - $file, - $s_w, - $i_w)); - } - - $s_h = $sprite->getSourceH() * $scale; - $i_h = $this->sources[$file]['y']; - if ($s_h > $i_h) { - throw new Exception( - pht( - "Sprite source for '%s' is too small (expected height %d, found %d).", - $file, - $s_h, - $i_h)); - } - - return $this->sources[$file]['image']; - } - -} diff --git a/src/utils/utils.php b/src/utils/utils.php --- a/src/utils/utils.php +++ b/src/utils/utils.php @@ -890,6 +890,9 @@ * @param string Block of text to be split into lines. * @param bool If true, retain line endings in result strings. * @return list List of lines. + * + * @phutil-external-symbol class PhutilSafeHTML + * @phutil-external-symbol function phutil_safe_html */ function phutil_split_lines($corpus, $retain_endings = true) { if (!strlen($corpus)) { @@ -910,7 +913,10 @@ } if ($corpus instanceof PhutilSafeHTML) { - return array_map('phutil_safe_html', $lines); + foreach ($lines as $key => $line) { + $lines[$key] = phutil_safe_html($line); + } + return $lines; } return $lines; @@ -1843,3 +1849,72 @@ return true; } + + +/** + * Escape text for inclusion in a URI or a query parameter. Note that this + * method does NOT escape '/', because "%2F" is invalid in paths and Apache + * will automatically 404 the page if it's present. This will produce correct + * (the URIs will work) and desirable (the URIs will be readable) behavior in + * these cases: + * + * '/path/?param='.phutil_escape_uri($string); # OK: Query Parameter + * '/path/to/'.phutil_escape_uri($string); # OK: URI Suffix + * + * It will potentially produce the WRONG behavior in this special case: + * + * COUNTEREXAMPLE + * '/path/to/'.phutil_escape_uri($string).'/thing/'; # BAD: URI Infix + * + * In this case, any '/' characters in the string will not be escaped, so you + * will not be able to distinguish between the string and the suffix (unless + * you have more information, like you know the format of the suffix). For infix + * URI components, use @{function:phutil_escape_uri_path_component} instead. + * + * @param string Some string. + * @return string URI encoded string, except for '/'. + */ +function phutil_escape_uri($string) { + return str_replace('%2F', '/', rawurlencode($string)); +} + + +/** + * Escape text for inclusion as an infix URI substring. See discussion at + * @{function:phutil_escape_uri}. This function covers an unusual special case; + * @{function:phutil_escape_uri} is usually the correct function to use. + * + * This function will escape a string into a format which is safe to put into + * a URI path and which does not contain '/' so it can be correctly parsed when + * embedded as a URI infix component. + * + * However, you MUST decode the string with + * @{function:phutil_unescape_uri_path_component} before it can be used in the + * application. + * + * @param string Some string. + * @return string URI encoded string that is safe for infix composition. + */ +function phutil_escape_uri_path_component($string) { + return rawurlencode(rawurlencode($string)); +} + + +/** + * Unescape text that was escaped by + * @{function:phutil_escape_uri_path_component}. See + * @{function:phutil_escape_uri} for discussion. + * + * Note that this function is NOT the inverse of + * @{function:phutil_escape_uri_path_component}! It undoes additional escaping + * which is added to survive the implied unescaping performed by the webserver + * when interpreting the request. + * + * @param string Some string emitted + * from @{function:phutil_escape_uri_path_component} and + * then accessed via a web server. + * @return string Original string. + */ +function phutil_unescape_uri_path_component($string) { + return rawurldecode($string); +}