diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1832c53..edcf449 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,723 +1,723 @@ 2, 'class' => array( 'AASTNode' => 'parser/aast/api/AASTNode.php', 'AASTNodeList' => 'parser/aast/api/AASTNodeList.php', 'AASTToken' => 'parser/aast/api/AASTToken.php', 'AASTTree' => 'parser/aast/api/AASTTree.php', 'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php', 'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php', 'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php', 'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php', 'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php', 'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php', 'AphrontMySQLDatabaseConnectionBase' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnectionBase.php', 'AphrontMySQLiDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php', 'AphrontQueryAccessDeniedException' => 'aphront/storage/exception/AphrontQueryAccessDeniedException.php', 'AphrontQueryCharacterSetException' => 'aphront/storage/exception/AphrontQueryCharacterSetException.php', 'AphrontQueryConnectionException' => 'aphront/storage/exception/AphrontQueryConnectionException.php', 'AphrontQueryConnectionLostException' => 'aphront/storage/exception/AphrontQueryConnectionLostException.php', 'AphrontQueryCountException' => 'aphront/storage/exception/AphrontQueryCountException.php', 'AphrontQueryDeadlockException' => 'aphront/storage/exception/AphrontQueryDeadlockException.php', 'AphrontQueryDuplicateKeyException' => 'aphront/storage/exception/AphrontQueryDuplicateKeyException.php', 'AphrontQueryException' => 'aphront/storage/exception/AphrontQueryException.php', 'AphrontQueryNotSupportedException' => 'aphront/storage/exception/AphrontQueryNotSupportedException.php', 'AphrontQueryObjectMissingException' => 'aphront/storage/exception/AphrontQueryObjectMissingException.php', 'AphrontQueryParameterException' => 'aphront/storage/exception/AphrontQueryParameterException.php', 'AphrontQueryRecoverableException' => 'aphront/storage/exception/AphrontQueryRecoverableException.php', 'AphrontQuerySchemaException' => 'aphront/storage/exception/AphrontQuerySchemaException.php', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php', 'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php', 'AphrontWriteGuardExitEventListener' => 'aphront/writeguard/event/AphrontWriteGuardExitEventListener.php', 'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php', 'CommandException' => 'future/exec/CommandException.php', 'ConduitClient' => 'conduit/ConduitClient.php', 'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitFuture' => 'conduit/ConduitFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php', 'FileFinder' => 'filesystem/FileFinder.php', 'FileFinderTestCase' => 'filesystem/__tests__/FileFinderTestCase.php', 'FileList' => 'filesystem/FileList.php', 'Filesystem' => 'filesystem/Filesystem.php', 'FilesystemException' => 'filesystem/FilesystemException.php', 'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php', 'Future' => 'future/Future.php', 'FutureIterator' => 'future/FutureIterator.php', 'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php', 'FutureProxy' => 'future/FutureProxy.php', 'HTTPFuture' => 'future/http/HTTPFuture.php', 'HTTPFutureCURLResponseStatus' => 'future/http/status/HTTPFutureCURLResponseStatus.php', 'HTTPFutureHTTPResponseStatus' => 'future/http/status/HTTPFutureHTTPResponseStatus.php', 'HTTPFutureParseResponseStatus' => 'future/http/status/HTTPFutureParseResponseStatus.php', 'HTTPFutureResponseStatus' => 'future/http/status/HTTPFutureResponseStatus.php', 'HTTPFutureTransportResponseStatus' => 'future/http/status/HTTPFutureTransportResponseStatus.php', 'HTTPSFuture' => 'future/http/HTTPSFuture.php', 'ImmediateFuture' => 'future/ImmediateFuture.php', 'LinesOfALarge' => 'filesystem/linesofalarge/LinesOfALarge.php', 'LinesOfALargeExecFuture' => 'filesystem/linesofalarge/LinesOfALargeExecFuture.php', 'LinesOfALargeExecFutureTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php', 'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php', 'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php', 'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php', 'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php', 'PhageAgentBootloader' => 'phage/bootloader/PhageAgentBootloader.php', 'PhageAgentTestCase' => 'phage/__tests__/PhageAgentTestCase.php', 'PhagePHPAgent' => 'phage/agent/PhagePHPAgent.php', 'PhagePHPAgentBootloader' => 'phage/bootloader/PhagePHPAgentBootloader.php', 'Phobject' => 'object/Phobject.php', 'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php', 'PhutilAWSException' => 'future/aws/PhutilAWSException.php', 'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php', 'PhutilAWSS3Future' => 'future/aws/PhutilAWSS3Future.php', 'PhutilAggregateException' => 'error/PhutilAggregateException.php', 'PhutilAmazonAuthAdapter' => 'auth/PhutilAmazonAuthAdapter.php', 'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php', 'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php', 'PhutilArgumentParserTestCase' => 'parser/argument/__tests__/PhutilArgumentParserTestCase.php', 'PhutilArgumentSpecification' => 'parser/argument/PhutilArgumentSpecification.php', 'PhutilArgumentSpecificationException' => 'parser/argument/exception/PhutilArgumentSpecificationException.php', 'PhutilArgumentSpecificationTestCase' => 'parser/argument/__tests__/PhutilArgumentSpecificationTestCase.php', 'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php', 'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php', 'PhutilArray' => 'utils/PhutilArray.php', 'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php', 'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php', 'PhutilAsanaAuthAdapter' => 'auth/PhutilAsanaAuthAdapter.php', 'PhutilAsanaFuture' => 'future/asana/PhutilAsanaFuture.php', 'PhutilAuthAdapter' => 'auth/PhutilAuthAdapter.php', 'PhutilAuthConfigurationException' => 'auth/exception/PhutilAuthConfigurationException.php', 'PhutilAuthCredentialException' => 'auth/exception/PhutilAuthCredentialException.php', 'PhutilAuthException' => 'auth/exception/PhutilAuthException.php', 'PhutilAuthUserAbortedException' => 'auth/exception/PhutilAuthUserAbortedException.php', 'PhutilBallOfPHP' => 'phage/util/PhutilBallOfPHP.php', 'PhutilBitbucketAuthAdapter' => 'auth/PhutilBitbucketAuthAdapter.php', 'PhutilBootloader' => 'moduleutils/PhutilBootloader.php', 'PhutilBootloaderException' => 'moduleutils/PhutilBootloaderException.php', 'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php', 'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php', 'PhutilBugtraqParser' => 'parser/PhutilBugtraqParser.php', 'PhutilBugtraqParserTestCase' => 'parser/__tests__/PhutilBugtraqParserTestCase.php', 'PhutilCLikeCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCLikeCodeSnippetContextFreeGrammar.php', 'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php', 'PhutilChannel' => 'channel/PhutilChannel.php', 'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php', 'PhutilChannelTestCase' => 'channel/__tests__/PhutilChannelTestCase.php', 'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php', 'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php', 'PhutilCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilCodeSnippetContextFreeGrammar.php', 'PhutilCommandString' => 'xsprintf/PhutilCommandString.php', 'PhutilConsole' => 'console/PhutilConsole.php', 'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php', 'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php', 'PhutilConsoleProgressBar' => 'console/PhutilConsoleProgressBar.php', 'PhutilConsoleServer' => 'console/PhutilConsoleServer.php', 'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php', 'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php', 'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php', 'PhutilConsoleTable' => 'console/PhutilConsoleTable.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', 'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php', 'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php', 'PhutilDaemon' => 'daemon/PhutilDaemon.php', 'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.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', 'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', 'PhutilDisqusAuthAdapter' => 'auth/PhutilDisqusAuthAdapter.php', 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', 'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php', 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEditDistanceMatrix' => 'utils/PhutilEditDistanceMatrix.php', 'PhutilEditDistanceMatrixTestCase' => 'utils/__tests__/PhutilEditDistanceMatrixTestCase.php', 'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php', 'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php', 'PhutilEmptyAuthAdapter' => 'auth/PhutilEmptyAuthAdapter.php', 'PhutilErrorHandler' => 'error/PhutilErrorHandler.php', 'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php', 'PhutilErrorTrap' => 'error/PhutilErrorTrap.php', 'PhutilEvent' => 'events/PhutilEvent.php', 'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php', 'PhutilEventEngine' => 'events/PhutilEventEngine.php', 'PhutilEventListener' => 'events/PhutilEventListener.php', 'PhutilEventType' => 'events/constant/PhutilEventType.php', 'PhutilExampleBufferedIterator' => 'utils/PhutilExampleBufferedIterator.php', 'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php', 'PhutilExecChannel' => 'channel/PhutilExecChannel.php', 'PhutilExecPassthru' => 'future/exec/PhutilExecPassthru.php', 'PhutilExtensionsTestCase' => 'moduleutils/__tests__/PhutilExtensionsTestCase.php', 'PhutilFacebookAuthAdapter' => 'auth/PhutilFacebookAuthAdapter.php', 'PhutilFatalDaemon' => 'daemon/torture/PhutilFatalDaemon.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilGitHubAuthAdapter' => 'auth/PhutilGitHubAuthAdapter.php', 'PhutilGitURI' => 'parser/PhutilGitURI.php', 'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php', 'PhutilGoogleAuthAdapter' => 'auth/PhutilGoogleAuthAdapter.php', 'PhutilHangForeverDaemon' => 'daemon/torture/PhutilHangForeverDaemon.php', 'PhutilHelpArgumentWorkflow' => 'parser/argument/workflow/PhutilHelpArgumentWorkflow.php', 'PhutilHgsprintfTestCase' => 'xsprintf/__tests__/PhutilHgsprintfTestCase.php', 'PhutilInfrastructureTestCase' => '__tests__/PhutilInfrastructureTestCase.php', 'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php', 'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php', 'PhutilJIRAAuthAdapter' => 'auth/PhutilJIRAAuthAdapter.php', 'PhutilJSON' => 'parser/PhutilJSON.php', 'PhutilJSONParser' => 'parser/PhutilJSONParser.php', 'PhutilJSONParserException' => 'parser/exception/PhutilJSONParserException.php', 'PhutilJSONParserTestCase' => 'parser/__tests__/PhutilJSONParserTestCase.php', 'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php', 'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php', 'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php', 'PhutilJavaCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilJavaCodeSnippetContextFreeGrammar.php', 'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php', 'PhutilKeyValueCacheAPC' => 'cache/PhutilKeyValueCacheAPC.php', 'PhutilKeyValueCacheDirectory' => 'cache/PhutilKeyValueCacheDirectory.php', 'PhutilKeyValueCacheInRequest' => 'cache/PhutilKeyValueCacheInRequest.php', 'PhutilKeyValueCacheMemcache' => 'cache/PhutilKeyValueCacheMemcache.php', 'PhutilKeyValueCacheNamespace' => 'cache/PhutilKeyValueCacheNamespace.php', 'PhutilKeyValueCacheOnDisk' => 'cache/PhutilKeyValueCacheOnDisk.php', 'PhutilKeyValueCacheProfiler' => 'cache/PhutilKeyValueCacheProfiler.php', 'PhutilKeyValueCacheProxy' => 'cache/PhutilKeyValueCacheProxy.php', 'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php', 'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php', 'PhutilLDAPAuthAdapter' => 'auth/PhutilLDAPAuthAdapter.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', 'PhutilLipsumContextFreeGrammar' => 'grammar/PhutilLipsumContextFreeGrammar.php', 'PhutilLock' => 'filesystem/PhutilLock.php', 'PhutilLockException' => 'filesystem/PhutilLockException.php', 'PhutilLogfileChannel' => 'channel/PhutilLogfileChannel.php', 'PhutilLunarPhase' => 'utils/PhutilLunarPhase.php', 'PhutilLunarPhaseTestCase' => 'utils/__tests__/PhutilLunarPhaseTestCase.php', 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', 'PhutilMethodNotImplementedException' => 'error/PhutilMethodNotImplementedException.php', 'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php', 'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php', 'PhutilModuleUtilsTestCase' => 'moduleutils/__tests__/PhutilModuleUtilsTestCase.php', 'PhutilNiceDaemon' => 'daemon/torture/PhutilNiceDaemon.php', 'PhutilNumber' => 'internationalization/PhutilNumber.php', 'PhutilOAuth1AuthAdapter' => 'auth/PhutilOAuth1AuthAdapter.php', 'PhutilOAuth1Future' => 'future/oauth/PhutilOAuth1Future.php', 'PhutilOAuth1FutureTestCase' => 'future/oauth/__tests__/PhutilOAuth1FutureTestCase.php', 'PhutilOAuthAuthAdapter' => 'auth/PhutilOAuthAuthAdapter.php', 'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php', 'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php', 'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php', 'PhutilPHPCodeSnippetContextFreeGrammar' => 'grammar/code/PhutilPHPCodeSnippetContextFreeGrammar.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', 'PhutilPHTTestCase' => 'internationalization/__tests__/PhutilPHTTestCase.php', 'PhutilParserGenerator' => 'parser/PhutilParserGenerator.php', 'PhutilParserGeneratorException' => 'parser/generator/exception/PhutilParserGeneratorException.php', 'PhutilParserGeneratorInvalidRuleException' => 'parser/generator/exception/PhutilParserGeneratorInvalidRuleException.php', 'PhutilParserGeneratorIrreducibleRuleException' => 'parser/generator/exception/PhutilParserGeneratorIrreducibleRuleException.php', 'PhutilParserGeneratorTestCase' => 'parser/__tests__/PhutilParserGeneratorTestCase.php', 'PhutilParserGeneratorUnknownSymbolException' => 'parser/generator/exception/PhutilParserGeneratorUnknownSymbolException.php', 'PhutilParserGeneratorUnreachableRuleException' => 'parser/generator/exception/PhutilParserGeneratorUnreachableRuleException.php', 'PhutilParserGeneratorUnreachableTerminalException' => 'parser/generator/exception/PhutilParserGeneratorUnreachableTerminalException.php', 'PhutilPayPalAPIFuture' => 'future/paypal/PhutilPayPalAPIFuture.php', 'PhutilPerson' => 'internationalization/PhutilPerson.php', 'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php', 'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php', 'PhutilPhobjectTestCase' => 'object/__tests__/PhutilPhobjectTestCase.php', 'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php', 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php', 'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php', 'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php', 'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', 'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php', 'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php', 'PhutilRealnameContextFreeGrammar' => 'grammar/PhutilRealnameContextFreeGrammar.php', 'PhutilRemarkupBlockInterpreter' => 'markup/engine/remarkup/blockrule/PhutilRemarkupBlockInterpreter.php', + 'PhutilRemarkupBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupBlockRule.php', 'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/PhutilRemarkupBlockStorage.php', + 'PhutilRemarkupBoldRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupBoldRule.php', + 'PhutilRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php', + 'PhutilRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupDefaultBlockRule.php', + 'PhutilRemarkupDelRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php', + 'PhutilRemarkupDocumentLinkRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php', 'PhutilRemarkupEngine' => 'markup/engine/PhutilRemarkupEngine.php', - 'PhutilRemarkupEngineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php', - 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php', - 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php', - 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php', - 'PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php', - 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php', - 'PhutilRemarkupEngineRemarkupInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php', - 'PhutilRemarkupEngineRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php', - 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php', - 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php', - 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php', - 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php', - 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php', - 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php', - 'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php', 'PhutilRemarkupEngineTestCase' => 'markup/engine/__tests__/PhutilRemarkupEngineTestCase.php', + 'PhutilRemarkupEscapeRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupEscapeRemarkupRule.php', + 'PhutilRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupHeaderBlockRule.php', + 'PhutilRemarkupHorizontalRuleBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php', + 'PhutilRemarkupHyperlinkRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php', + 'PhutilRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupInlineBlockRule.php', + 'PhutilRemarkupInterpreterBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupInterpreterBlockRule.php', + 'PhutilRemarkupItalicRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php', + 'PhutilRemarkupLinebreaksRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupLinebreaksRule.php', + 'PhutilRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupListBlockRule.php', + 'PhutilRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupLiteralBlockRule.php', + 'PhutilRemarkupMonospaceRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php', + 'PhutilRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupNoteBlockRule.php', + 'PhutilRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupQuotesBlockRule.php', + 'PhutilRemarkupReplyBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupReplyBlockRule.php', 'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRule.php', - 'PhutilRemarkupRuleBold' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleBold.php', - 'PhutilRemarkupRuleDel' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php', - 'PhutilRemarkupRuleDocumentLink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php', - 'PhutilRemarkupRuleEscapeRemarkup' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php', - 'PhutilRemarkupRuleHyperlink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php', - 'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php', - 'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php', - 'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php', - 'PhutilRemarkupRuleUnderline' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleUnderline.php', + 'PhutilRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupSimpleTableBlockRule.php', + 'PhutilRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupTableBlockRule.php', + 'PhutilRemarkupTestInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupTestInterpreterRule.php', + 'PhutilRemarkupUnderlineRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.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', 'PhutilShellLexerTestCase' => 'lexer/__tests__/PhutilShellLexerTestCase.php', 'PhutilSimpleOptions' => 'parser/PhutilSimpleOptions.php', 'PhutilSimpleOptionsLexer' => 'lexer/PhutilSimpleOptionsLexer.php', 'PhutilSimpleOptionsLexerTestCase' => 'lexer/__tests__/PhutilSimpleOptionsLexerTestCase.php', 'PhutilSimpleOptionsTestCase' => 'parser/__tests__/PhutilSimpleOptionsTestCase.php', 'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php', 'PhutilSprite' => 'sprites/PhutilSprite.php', 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', 'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php', 'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php', 'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php', 'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php', 'PhutilTranslator' => 'internationalization/PhutilTranslator.php', 'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php', 'PhutilTwitchAuthAdapter' => 'auth/PhutilTwitchAuthAdapter.php', 'PhutilTwitchFuture' => 'future/twitch/PhutilTwitchFuture.php', 'PhutilTwitterAuthAdapter' => 'auth/PhutilTwitterAuthAdapter.php', 'PhutilTypeCheckException' => 'parser/exception/PhutilTypeCheckException.php', 'PhutilTypeExtraParametersException' => 'parser/exception/PhutilTypeExtraParametersException.php', 'PhutilTypeLexer' => 'lexer/PhutilTypeLexer.php', 'PhutilTypeMissingParametersException' => 'parser/exception/PhutilTypeMissingParametersException.php', 'PhutilTypeSpec' => 'parser/PhutilTypeSpec.php', 'PhutilTypeSpecTestCase' => 'parser/__tests__/PhutilTypeSpecTestCase.php', 'PhutilURI' => 'parser/PhutilURI.php', 'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php', 'PhutilUTF8StringTruncator' => 'utils/PhutilUTF8StringTruncator.php', 'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php', 'PhutilUrisprintfTestCase' => 'xsprintf/__tests__/PhutilUrisprintfTestCase.php', 'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php', 'PhutilWordPressAuthAdapter' => 'auth/PhutilWordPressAuthAdapter.php', 'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php', 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', 'PhutilXHPASTSyntaxHighlighterFuture' => 'markup/syntax/highlighter/xhpast/PhutilXHPASTSyntaxHighlighterFuture.php', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'QueryFuture' => 'future/query/QueryFuture.php', 'TempFile' => 'filesystem/TempFile.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', 'XHPASTNodeTestCase' => 'parser/xhpast/api/__tests__/XHPASTNodeTestCase.php', 'XHPASTSyntaxErrorException' => 'parser/xhpast/api/XHPASTSyntaxErrorException.php', 'XHPASTToken' => 'parser/xhpast/api/XHPASTToken.php', 'XHPASTTree' => 'parser/xhpast/api/XHPASTTree.php', 'XHPASTTreeTestCase' => 'parser/xhpast/api/__tests__/XHPASTTreeTestCase.php', 'XsprintfUnknownConversionException' => 'xsprintf/exception/XsprintfUnknownConversionException.php', ), 'function' => array( 'Futures' => 'future/functions.php', '_phutil_date_format' => 'utils/viewutils.php', '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', '_qsprintf_check_type' => 'xsprintf/qsprintf.php', 'array_fuse' => 'utils/utils.php', 'array_interleave' => 'utils/utils.php', 'array_mergev' => 'utils/utils.php', 'array_select_keys' => 'utils/utils.php', 'assert_instances_of' => 'utils/utils.php', 'assert_stringlike' => 'utils/utils.php', 'coalesce' => 'utils/utils.php', 'csprintf' => 'xsprintf/csprintf.php', 'exec_manual' => 'future/exec/execx.php', 'execx' => 'future/exec/execx.php', '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', 'ifilter' => 'utils/utils.php', 'igroup' => 'utils/utils.php', 'ipull' => 'utils/utils.php', 'isort' => 'utils/utils.php', 'jsprintf' => 'xsprintf/jsprintf.php', 'last' => 'utils/utils.php', 'last_key' => 'utils/utils.php', 'ldap_sprintf' => 'xsprintf/ldapsprintf.php', 'mfilter' => 'utils/utils.php', 'mgroup' => 'utils/utils.php', 'mpull' => 'utils/utils.php', 'msort' => 'utils/utils.php', 'newv' => 'utils/utils.php', 'nonempty' => 'utils/utils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', 'phutil_censor_credentials' => 'utils/utils.php', 'phutil_console_confirm' => 'console/format.php', 'phutil_console_format' => 'console/format.php', 'phutil_console_get_terminal_width' => 'console/format.php', 'phutil_console_prompt' => 'console/format.php', 'phutil_console_require_tty' => 'console/format.php', 'phutil_console_wrap' => 'console/format.php', 'phutil_deprecated' => 'moduleutils/moduleutils.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_exit' => 'utils/utils.php', 'phutil_format_bytes' => 'utils/viewutils.php', 'phutil_format_relative_time' => 'utils/viewutils.php', 'phutil_format_relative_time_detailed' => 'utils/viewutils.php', 'phutil_format_units_generic' => 'utils/viewutils.php', 'phutil_fwrite_nonblocking_stream' => 'utils/utils.php', 'phutil_get_current_library_name' => 'moduleutils/moduleutils.php', 'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php', 'phutil_get_signal_name' => 'future/exec/execx.php', 'phutil_implode_html' => 'markup/render.php', 'phutil_is_hiphop_runtime' => 'utils/utils.php', 'phutil_is_utf8' => 'utils/utf8.php', 'phutil_is_utf8_slowly' => 'utils/utf8.php', 'phutil_is_utf8_with_only_bmp_characters' => 'utils/utf8.php', 'phutil_is_windows' => 'utils/utils.php', 'phutil_json_decode' => 'utils/utils.php', 'phutil_load_library' => 'moduleutils/core.php', 'phutil_loggable_string' => 'utils/utils.php', 'phutil_parse_bytes' => 'utils/viewutils.php', 'phutil_passthru' => 'future/exec/execx.php', 'phutil_register_library' => 'moduleutils/core.php', 'phutil_register_library_map' => 'moduleutils/core.php', 'phutil_safe_html' => 'markup/render.php', 'phutil_split_lines' => 'utils/utils.php', 'phutil_tag' => 'markup/render.php', 'phutil_tag_div' => 'markup/render.php', 'phutil_unescape_uri_path_component' => 'markup/render.php', 'phutil_units' => 'utils/utils.php', 'phutil_utf8_console_strlen' => 'utils/utf8.php', 'phutil_utf8_convert' => 'utils/utf8.php', 'phutil_utf8_hard_wrap' => 'utils/utf8.php', 'phutil_utf8_hard_wrap_html' => 'utils/utf8.php', 'phutil_utf8_is_combining_character' => 'utils/utf8.php', 'phutil_utf8_shorten' => 'utils/utf8.php', 'phutil_utf8_strlen' => 'utils/utf8.php', 'phutil_utf8_strtolower' => 'utils/utf8.php', 'phutil_utf8_strtoupper' => 'utils/utf8.php', 'phutil_utf8_strtr' => 'utils/utf8.php', 'phutil_utf8_ucwords' => 'utils/utf8.php', 'phutil_utf8ize' => 'utils/utf8.php', 'phutil_utf8v' => 'utils/utf8.php', 'phutil_utf8v_codepoints' => 'utils/utf8.php', 'phutil_utf8v_combined' => 'utils/utf8.php', 'phutil_var_export' => 'utils/utils.php', 'ppull' => 'utils/utils.php', 'qsprintf' => 'xsprintf/qsprintf.php', 'queryfx' => 'xsprintf/queryfx.php', 'queryfx_all' => 'xsprintf/queryfx.php', 'queryfx_one' => 'xsprintf/queryfx.php', 'urisprintf' => 'xsprintf/urisprintf.php', 'vcsprintf' => 'xsprintf/csprintf.php', 'vjsprintf' => 'xsprintf/jsprintf.php', 'vqsprintf' => 'xsprintf/qsprintf.php', 'vqueryfx' => 'xsprintf/queryfx.php', 'vqueryfx_all' => 'xsprintf/queryfx.php', 'vurisprintf' => 'xsprintf/urisprintf.php', 'xhp_parser_node_constants' => 'parser/xhpast/parser_nodes.php', 'xhpast_build' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_binary_path' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_build_instructions' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_parser_future' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_is_available' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_parser_token_constants' => 'parser/xhpast/parser_tokens.php', 'xsprintf' => 'xsprintf/xsprintf.php', 'xsprintf_callback_example' => 'xsprintf/xsprintf.php', 'xsprintf_command' => 'xsprintf/csprintf.php', 'xsprintf_javascript' => 'xsprintf/jsprintf.php', 'xsprintf_ldap' => 'xsprintf/ldapsprintf.php', 'xsprintf_mercurial' => 'xsprintf/hgsprintf.php', 'xsprintf_query' => 'xsprintf/qsprintf.php', 'xsprintf_uri' => 'xsprintf/urisprintf.php', ), 'xmap' => array( 'AASTNodeList' => array( 'Iterator', 'Countable', ), 'AbstractDirectedGraphTestCase' => 'PhutilTestCase', 'AphrontDatabaseConnection' => 'PhutilQsprintfInterface', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontMySQLDatabaseConnectionBase' => 'AphrontDatabaseConnection', 'AphrontMySQLiDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryCharacterSetException' => 'AphrontQueryException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDeadlockException' => 'AphrontQueryRecoverableException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryException' => 'Exception', 'AphrontQueryNotSupportedException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontQuerySchemaException' => 'AphrontQueryException', 'AphrontWriteGuardExitEventListener' => 'PhutilEventListener', 'BaseHTTPFuture' => 'Future', 'CommandException' => 'Exception', 'ConduitClientException' => 'Exception', 'ConduitFuture' => 'FutureProxy', 'ExecFuture' => 'Future', 'ExecFutureTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase', 'FileFinderTestCase' => 'PhutilTestCase', 'FilesystemException' => 'Exception', 'FilesystemTestCase' => 'PhutilTestCase', 'FutureIterator' => 'Iterator', 'FutureIteratorTestCase' => 'PhutilTestCase', 'FutureProxy' => 'Future', 'HTTPFuture' => 'BaseHTTPFuture', 'HTTPFutureCURLResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPFutureHTTPResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPFutureParseResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatus' => 'Exception', 'HTTPFutureTransportResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPSFuture' => 'BaseHTTPFuture', 'ImmediateFuture' => 'Future', 'LinesOfALarge' => 'Iterator', 'LinesOfALargeExecFuture' => 'LinesOfALarge', 'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase', 'LinesOfALargeFile' => 'LinesOfALarge', 'LinesOfALargeFileTestCase' => 'PhutilTestCase', 'PHPASTParserTestCase' => 'PhutilTestCase', 'PhageAgentTestCase' => 'PhutilTestCase', 'PhagePHPAgentBootloader' => 'PhageAgentBootloader', 'Phobject' => 'Iterator', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', 'PhutilAWSException' => 'Exception', 'PhutilAWSFuture' => 'FutureProxy', 'PhutilAWSS3Future' => 'PhutilAWSFuture', 'PhutilAggregateException' => 'Exception', 'PhutilAmazonAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilArgumentParserException' => 'Exception', 'PhutilArgumentParserTestCase' => 'PhutilTestCase', 'PhutilArgumentSpecificationException' => 'PhutilArgumentParserException', 'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase', 'PhutilArgumentUsageException' => 'PhutilArgumentParserException', 'PhutilArray' => array( 'Phobject', 'Countable', 'ArrayAccess', 'Iterator', ), 'PhutilArrayTestCase' => 'PhutilTestCase', 'PhutilArrayWithDefaultValue' => 'PhutilArray', 'PhutilAsanaAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilAsanaFuture' => 'FutureProxy', 'PhutilAuthConfigurationException' => 'PhutilAuthException', 'PhutilAuthCredentialException' => 'PhutilAuthException', 'PhutilAuthException' => 'Exception', 'PhutilAuthUserAbortedException' => 'PhutilAuthException', 'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilBootloaderException' => 'Exception', 'PhutilBufferedIterator' => 'Iterator', 'PhutilBufferedIteratorTestCase' => 'PhutilTestCase', 'PhutilBugtraqParserTestCase' => 'PhutilTestCase', 'PhutilCLikeCodeSnippetContextFreeGrammar' => 'PhutilCodeSnippetContextFreeGrammar', 'PhutilCallbackFilterIterator' => 'FilterIterator', 'PhutilChannelChannel' => 'PhutilChannel', 'PhutilChannelTestCase' => 'PhutilTestCase', 'PhutilChunkedIterator' => 'Iterator', 'PhutilChunkedIteratorTestCase' => 'PhutilTestCase', 'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhutilCommandString' => 'Phobject', 'PhutilConsoleProgressBar' => 'Phobject', 'PhutilConsoleServerChannel' => 'PhutilChannelChannel', 'PhutilConsoleStdinNotInteractiveException' => 'Exception', 'PhutilConsoleTable' => 'Phobject', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', 'PhutilCsprintfTestCase' => 'PhutilTestCase', 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph', 'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEditDistanceMatrixTestCase' => 'PhutilTestCase', 'PhutilEmailAddressTestCase' => 'PhutilTestCase', 'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', 'PhutilErrorTrap' => 'Phobject', 'PhutilEventType' => 'PhutilEventConstants', 'PhutilExampleBufferedIterator' => 'PhutilBufferedIterator', 'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon', 'PhutilExecChannel' => 'PhutilChannel', 'PhutilExecPassthru' => 'Phobject', 'PhutilExtensionsTestCase' => 'PhutilTestCase', 'PhutilFacebookAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilFatalDaemon' => 'PhutilTortureTestDaemon', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', 'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilGitURITestCase' => 'PhutilTestCase', 'PhutilGoogleAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon', 'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow', 'PhutilHgsprintfTestCase' => 'PhutilTestCase', 'PhutilInfrastructureTestCase' => 'PhutilTestCase', 'PhutilJIRAAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilJSONParserException' => 'Exception', 'PhutilJSONParserTestCase' => 'PhutilTestCase', 'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel', 'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilJSONTestCase' => 'PhutilTestCase', 'PhutilJavaCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar', 'PhutilKeyValueCacheAPC' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheDirectory' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheInRequest' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheMemcache' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheNamespace' => 'PhutilKeyValueCacheProxy', 'PhutilKeyValueCacheOnDisk' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheProfiler' => 'PhutilKeyValueCacheProxy', 'PhutilKeyValueCacheProxy' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheTestCase' => 'PhutilTestCase', 'PhutilLDAPAuthAdapter' => 'PhutilAuthAdapter', 'PhutilLanguageGuesserTestCase' => 'PhutilTestCase', 'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter', 'PhutilLibraryConflictException' => 'Exception', 'PhutilLipsumContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhutilLockException' => 'Exception', 'PhutilLogfileChannel' => 'PhutilChannelChannel', 'PhutilLunarPhaseTestCase' => 'PhutilTestCase', 'PhutilMarkupTestCase' => 'PhutilTestCase', 'PhutilMethodNotImplementedException' => 'Exception', 'PhutilMetricsChannel' => 'PhutilChannelChannel', 'PhutilMissingSymbolException' => 'Exception', 'PhutilModuleUtilsTestCase' => 'PhutilTestCase', 'PhutilNiceDaemon' => 'PhutilTortureTestDaemon', 'PhutilOAuth1AuthAdapter' => 'PhutilAuthAdapter', 'PhutilOAuth1Future' => 'FutureProxy', 'PhutilOAuth1FutureTestCase' => 'PhutilTestCase', 'PhutilOAuthAuthAdapter' => 'PhutilAuthAdapter', 'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase', 'PhutilPHPCodeSnippetContextFreeGrammar' => 'PhutilCLikeCodeSnippetContextFreeGrammar', 'PhutilPHPFragmentLexer' => 'PhutilLexer', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase', 'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel', 'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilPHTTestCase' => 'PhutilTestCase', 'PhutilParserGeneratorException' => 'Exception', 'PhutilParserGeneratorInvalidRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorIrreducibleRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorTestCase' => 'PhutilTestCase', 'PhutilParserGeneratorUnknownSymbolException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorUnreachableRuleException' => 'PhutilParserGeneratorException', 'PhutilParserGeneratorUnreachableTerminalException' => 'PhutilParserGeneratorException', 'PhutilPayPalAPIFuture' => 'FutureProxy', 'PhutilPersonTest' => 'PhutilPerson', 'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter', 'PhutilPhobjectTestCase' => 'PhutilTestCase', 'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon', 'PhutilProtocolChannel' => 'PhutilChannelChannel', 'PhutilProxyException' => 'Exception', 'PhutilPythonFragmentLexer' => 'PhutilLexer', 'PhutilQueryStringParserTestCase' => 'PhutilTestCase', 'PhutilReadableSerializerTestCase' => 'PhutilTestCase', 'PhutilRealnameContextFreeGrammar' => 'PhutilContextFreeGrammar', + 'PhutilRemarkupBoldRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupCodeBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupDefaultBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupDelRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupDocumentLinkRule' => 'PhutilRemarkupRule', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', - 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupInterpreterRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupListBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'PhutilRemarkupEngineBlockRule', - 'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter', 'PhutilRemarkupEngineTestCase' => 'PhutilTestCase', - 'PhutilRemarkupRuleBold' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleDel' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleDocumentLink' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleEscapeRemarkup' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleHyperlink' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule', - 'PhutilRemarkupRuleUnderline' => 'PhutilRemarkupRule', + 'PhutilRemarkupEscapeRemarkupRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupHeaderBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupHyperlinkRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupInlineBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupInterpreterBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupItalicRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupLinebreaksRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupListBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupLiteralBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupMonospaceRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupNoteBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupQuotesBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupReplyBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupSimpleTableBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupTableBlockRule' => 'PhutilRemarkupBlockRule', + 'PhutilRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter', + 'PhutilRemarkupUnderlineRule' => 'PhutilRemarkupRule', 'PhutilRope' => 'Phobject', 'PhutilRopeTestCase' => 'PhutilTestCase', 'PhutilSafeHTMLTestCase' => 'PhutilTestCase', 'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon', 'PhutilShellLexer' => 'PhutilLexer', 'PhutilShellLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsLexer' => 'PhutilLexer', 'PhutilSimpleOptionsLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsTestCase' => 'PhutilTestCase', 'PhutilSocketChannel' => 'PhutilChannel', 'PhutilSyntaxHighlighterException' => 'Exception', 'PhutilTestCase' => 'ArcanistPhutilTestCase', 'PhutilTestPhobject' => 'Phobject', 'PhutilTortureTestDaemon' => 'PhutilDaemon', 'PhutilTranslatorTestCase' => 'PhutilTestCase', 'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilTwitchFuture' => 'FutureProxy', 'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilTypeCheckException' => 'Exception', 'PhutilTypeExtraParametersException' => 'Exception', 'PhutilTypeLexer' => 'PhutilLexer', 'PhutilTypeMissingParametersException' => 'Exception', 'PhutilTypeSpecTestCase' => 'PhutilTestCase', 'PhutilURITestCase' => 'PhutilTestCase', 'PhutilUTF8StringTruncator' => 'Phobject', 'PhutilUTF8TestCase' => 'PhutilTestCase', 'PhutilUrisprintfTestCase' => 'PhutilTestCase', 'PhutilUtilsTestCase' => 'PhutilTestCase', 'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilWordPressFuture' => 'FutureProxy', 'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'QueryFuture' => 'Future', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', 'XHPASTNodeTestCase' => 'PhutilTestCase', 'XHPASTSyntaxErrorException' => 'Exception', 'XHPASTToken' => 'AASTToken', 'XHPASTTree' => 'AASTTree', 'XHPASTTreeTestCase' => 'PhutilTestCase', 'XsprintfUnknownConversionException' => 'InvalidArgumentException', ), )); diff --git a/src/markup/engine/PhutilRemarkupEngine.php b/src/markup/engine/PhutilRemarkupEngine.php index e6ac80a..8f8ebe0 100644 --- a/src/markup/engine/PhutilRemarkupEngine.php +++ b/src/markup/engine/PhutilRemarkupEngine.php @@ -1,298 +1,298 @@ config[$key] = $value; return $this; } public function getConfig($key, $default = null) { return idx($this->config, $key, $default); } public function setMode($mode) { $this->mode = $mode; return $this; } public function isTextMode() { return $this->mode & self::MODE_TEXT; } public function setBlockRules(array $rules) { - assert_instances_of($rules, 'PhutilRemarkupEngineBlockRule'); + assert_instances_of($rules, 'PhutilRemarkupBlockRule'); $rules = msort($rules, 'getPriority'); $this->blockRules = $rules; foreach ($this->blockRules as $rule) { $rule->setEngine($this); } $post_rules = array(); foreach ($this->blockRules as $block_rule) { foreach ($block_rule->getMarkupRules() as $rule) { $key = $rule->getPostprocessKey(); if ($key !== null) { $post_rules[$key] = $rule; } } } $this->postprocessRules = $post_rules; return $this; } public function getTextMetadata($key, $default = null) { if (isset($this->metadata[$key])) { return $this->metadata[$key]; } return idx($this->metadata, $key, $default); } public function setTextMetadata($key, $value) { $this->metadata[$key] = $value; return $this; } public function storeText($text) { if ($this->isTextMode()) { $text = phutil_safe_html($text); } return $this->storage->store($text); } public function overwriteStoredText($token, $new_text) { if ($this->isTextMode()) { $new_text = phutil_safe_html($new_text); } $this->storage->overwrite($token, $new_text); return $this; } public function markupText($text) { return $this->postprocessText($this->preprocessText($text)); } public function pushState($state) { if (empty($this->states[$state])) { $this->states[$state] = 0; } $this->states[$state]++; return $this; } public function popState($state) { if (empty($this->states[$state])) { throw new Exception("State '{$state}' pushed more than popped!"); } $this->states[$state]--; if (!$this->states[$state]) { unset($this->states[$state]); } return $this; } public function getState($state) { return !empty($this->states[$state]); } public function preprocessText($text) { $this->metadata = array(); $this->storage = new PhutilRemarkupBlockStorage(); $blocks = $this->splitTextIntoBlocks($text); $output = array(); foreach ($blocks as $block) { $output[] = $this->markupBlock($block); } $output = $this->flattenOutput($output); $map = $this->storage->getMap(); unset($this->storage); $metadata = $this->metadata; return array( 'output' => $output, 'storage' => $map, 'metadata' => $metadata, ); } private function splitTextIntoBlocks($text, $depth = 0) { // Apply basic block and paragraph normalization to the text. NOTE: We don't // strip trailing whitespace because it is semantic in some contexts, // notably inlined diffs that the author intends to show as a code block. $text = phutil_split_lines($text, true); $block_rules = $this->blockRules; $blocks = array(); $cursor = 0; $prev_block = array(); while (isset($text[$cursor])) { $starting_cursor = $cursor; foreach ($block_rules as $block_rule) { $num_lines = $block_rule->getMatchingLineCount($text, $cursor); if ($num_lines) { if ($blocks) { $prev_block = last($blocks); } $curr_block = array( 'start' => $cursor, 'num_lines' => $num_lines, 'rule' => $block_rule, 'is_empty' => self::isEmptyBlock($text, $cursor, $num_lines), 'children' => array(), ); if ($prev_block && self::shouldMergeBlocks($text, $prev_block, $curr_block)) { $blocks[last_key($blocks)]['num_lines'] += $curr_block['num_lines']; $blocks[last_key($blocks)]['is_empty'] = $blocks[last_key($blocks)]['is_empty'] && $curr_block['is_empty']; } else { $blocks[] = $curr_block; } $cursor += $num_lines; break; } } if ($starting_cursor === $cursor) { throw new Exception('Block in text did not match any block rule.'); } } foreach ($blocks as $key => $block) { $lines = array_slice($text, $block['start'], $block['num_lines']); $blocks[$key]['text'] = implode('', $lines); } // Stop splitting child blocks apart if we get too deep. This arrests // any blocks which have looping child rules, and stops the stack from // exploding if someone writes a hilarious comment with 5,000 levels of // quoted text. if ($depth < self::MAX_CHILD_DEPTH) { foreach ($blocks as $key => $block) { $rule = $block['rule']; if (!$rule->supportsChildBlocks()) { continue; } list($parent_text, $child_text) = $rule->extractChildText( $block['text']); $blocks[$key]['text'] = $parent_text; $blocks[$key]['children'] = $this->splitTextIntoBlocks( $child_text, $depth + 1); } } return $blocks; } private function markupBlock(array $block) { $children = array(); foreach ($block['children'] as $child) { $children[] = $this->markupBlock($child); } if ($children) { $children = $this->flattenOutput($children); } else { $children = null; } return $block['rule']->markupText($block['text'], $children); } private function flattenOutput(array $output) { if ($this->isTextMode()) { $output = implode("\n\n", $output)."\n"; } else { $output = phutil_implode_html("\n\n", $output); } return $output; } private static function shouldMergeBlocks($text, $prev_block, $curr_block) { $block_rules = ipull(array($prev_block, $curr_block), 'rule'); - $default_rule = 'PhutilRemarkupEngineRemarkupDefaultBlockRule'; + $default_rule = 'PhutilRemarkupDefaultBlockRule'; try { assert_instances_of($block_rules, $default_rule); // If the last block was empty keep merging if ($prev_block['is_empty']) { return true; } // If this line is blank keep merging if ($curr_block['is_empty']) { return true; } // If the current line and the last line have content, keep merging if (strlen(trim($text[$curr_block['start'] - 1]))) { if (strlen(trim($text[$curr_block['start']]))) { return true; } } } catch (Exception $e) { } return false; } private static function isEmptyBlock($text, $start, $num_lines) { for ($cursor = $start; $cursor < $start + $num_lines; $cursor++) { if (strlen(trim($text[$cursor]))) { return false; } } return true; } public function postprocessText(array $dict) { $this->metadata = idx($dict, 'metadata', array()); $this->storage = new PhutilRemarkupBlockStorage(); $this->storage->setMap(idx($dict, 'storage', array())); foreach ($this->blockRules as $block_rule) { $block_rule->postprocess(); } foreach ($this->postprocessRules as $rule) { $rule->didMarkupText(); } return $this->restoreText(idx($dict, 'output'), $this->isTextMode()); } public function restoreText($text) { return $this->storage->restore($text, $this->isTextMode()); } } diff --git a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php index 0149c79..0547a5c 100644 --- a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php +++ b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php @@ -1,114 +1,113 @@ markupText($root.$file); } } private function markupText($markup_file) { $contents = Filesystem::readFile($markup_file); $file = basename($markup_file); $parts = explode("\n~~~~~~~~~~\n", $contents); $this->assertEqual(3, count($parts), $markup_file); list($input_remarkup, $expected_output, $expected_text) = $parts; $engine = $this->buildNewTestEngine(); switch ($file) { case 'raw-escape.txt': - // NOTE: Here, we want to test PhutilRemarkupRuleEscapeRemarkup and + // NOTE: Here, we want to test PhutilRemarkupEscapeRemarkupRule and // PhutilRemarkupBlockStorage, which are triggered by "\1". In the // test, "~" is used as a placeholder for "\1" since it's hard to type // "\1". $input_remarkup = str_replace('~', "\1", $input_remarkup); $expected_output = str_replace('~', "\1", $expected_output); $expected_text = str_replace('~', "\1", $expected_text); break; case 'toc.txt': $engine->setConfig('header.generate-toc', true); break; } $actual_output = (string)$engine->markupText($input_remarkup); switch ($file) { case 'toc.txt': $table_of_contents = - PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents( - $engine); + PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); $actual_output = $table_of_contents."\n\n".$actual_output; break; } $this->assertEqual( $expected_output, $actual_output, "Failed to markup HTML in file '{$file}'."); $engine->setMode(PhutilRemarkupEngine::MODE_TEXT); $actual_output = (string)$engine->markupText($input_remarkup); $this->assertEqual( $expected_text, $actual_output, "Failed to markup text in file '{$file}'."); } private function buildNewTestEngine() { $engine = new PhutilRemarkupEngine(); $engine->setConfig('uri.prefix', 'http://www.example.com/'); $engine->setConfig( 'uri.allowed-protocols', array( 'http' => true, 'mailto' => true, )); $rules = array(); - $rules[] = new PhutilRemarkupRuleEscapeRemarkup(); - $rules[] = new PhutilRemarkupRuleMonospace(); - $rules[] = new PhutilRemarkupRuleDocumentLink(); - $rules[] = new PhutilRemarkupRuleHyperlink(); - $rules[] = new PhutilRemarkupRuleBold(); - $rules[] = new PhutilRemarkupRuleItalic(); - $rules[] = new PhutilRemarkupRuleDel(); - $rules[] = new PhutilRemarkupRuleUnderline(); + $rules[] = new PhutilRemarkupEscapeRemarkupRule(); + $rules[] = new PhutilRemarkupMonospaceRule(); + $rules[] = new PhutilRemarkupDocumentLinkRule(); + $rules[] = new PhutilRemarkupHyperlinkRule(); + $rules[] = new PhutilRemarkupBoldRule(); + $rules[] = new PhutilRemarkupItalicRule(); + $rules[] = new PhutilRemarkupDelRule(); + $rules[] = new PhutilRemarkupUnderlineRule(); $blocks = array(); - $blocks[] = new PhutilRemarkupEngineRemarkupQuotesBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupReplyBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupHeaderBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupCodeBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupLiteralBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupTableBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupSimpleTableBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupDefaultBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupListBlockRule(); - $blocks[] = new PhutilRemarkupEngineRemarkupInterpreterRule(); + $blocks[] = new PhutilRemarkupQuotesBlockRule(); + $blocks[] = new PhutilRemarkupReplyBlockRule(); + $blocks[] = new PhutilRemarkupHeaderBlockRule(); + $blocks[] = new PhutilRemarkupHorizontalRuleBlockRule(); + $blocks[] = new PhutilRemarkupCodeBlockRule(); + $blocks[] = new PhutilRemarkupLiteralBlockRule(); + $blocks[] = new PhutilRemarkupNoteBlockRule(); + $blocks[] = new PhutilRemarkupTableBlockRule(); + $blocks[] = new PhutilRemarkupSimpleTableBlockRule(); + $blocks[] = new PhutilRemarkupDefaultBlockRule(); + $blocks[] = new PhutilRemarkupListBlockRule(); + $blocks[] = new PhutilRemarkupInterpreterBlockRule(); foreach ($blocks as $block) { - if (!($block instanceof PhutilRemarkupEngineRemarkupCodeBlockRule)) { + if (!($block instanceof PhutilRemarkupCodeBlockRule)) { $block->setMarkupRules($rules); } } $engine->setBlockRules($blocks); return $engine; } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupBlockRule.php similarity index 98% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupBlockRule.php index dba7bde..119cf0d 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupBlockRule.php @@ -1,145 +1,145 @@ engine = $engine; $this->updateRules(); return $this; } final protected function getEngine() { return $this->engine; } public function setMarkupRules(array $rules) { assert_instances_of($rules, 'PhutilRemarkupRule'); $this->rules = $rules; $this->updateRules(); return $this; } private function updateRules() { $engine = $this->getEngine(); if ($engine) { $this->rules = msort($this->rules, 'getPriority'); foreach ($this->rules as $rule) { $rule->setEngine($engine); } } return $this; } final public function getMarkupRules() { return $this->rules; } final public function postprocess() { $this->didMarkupText(); } final protected function applyRules($text) { foreach ($this->getMarkupRules() as $rule) { $text = $rule->apply($text); } return $text; } public function supportsChildBlocks() { return false; } public function extractChildText($text) { throw new PhutilMethodNotImplementedException(); } protected function renderRemarkupTable(array $out_rows) { assert_instances_of($out_rows, 'array'); if ($this->getEngine()->isTextMode()) { $lengths = array(); foreach ($out_rows as $r => $row) { foreach ($row['content'] as $c => $cell) { $text = $this->getEngine()->restoreText($cell['content']); $lengths[$c][$r] = phutil_utf8_strlen($text); } } $max_lengths = array_map('max', $lengths); $out = array(); foreach ($out_rows as $r => $row) { $headings = false; foreach ($row['content'] as $c => $cell) { $length = $max_lengths[$c] - $lengths[$c][$r]; $out[] = '| '.$cell['content'].str_repeat(' ', $length).' '; if ($cell['type'] == 'th') { $headings = true; } } $out[] = "|\n"; if ($headings) { foreach ($row['content'] as $c => $cell) { $char = ($cell['type'] == 'th' ? '-' : ' '); $out[] = '| '.str_repeat($char, $max_lengths[$c]).' '; } $out[] = "|\n"; } } return rtrim(implode('', $out), "\n"); } $out = array(); $out[] = "\n"; foreach ($out_rows as $row) { $cells = array(); foreach ($row['content'] as $cell) { $cells[] = phutil_tag($cell['type'], array(), $cell['content']); } $out[] = phutil_tag($row['type'], array(), $cells); $out[] = "\n"; } return phutil_tag('table', array('class' => 'remarkup-table'), $out); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php similarity index 98% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php index b310fed..a1f81b4 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupCodeBlockRule.php @@ -1,188 +1,187 @@ false, 'lang' => null, 'name' => null, 'lines' => null, ); $parser = new PhutilSimpleOptions(); $custom = $parser->parse(head($lines)); if ($custom) { $valid = true; foreach ($custom as $key => $value) { if (!array_key_exists($key, $options)) { $valid = false; break; } } if ($valid) { array_shift($lines); $options = $custom + $options; } } // Normalize the text back to a 0-level indent. $min_indent = 80; foreach ($lines as $line) { for ($ii = 0; $ii < strlen($line); $ii++) { if ($line[$ii] != ' ') { $min_indent = min($ii, $min_indent); break; } } } $text = implode("\n", $lines); if ($min_indent) { $indent_string = str_repeat(' ', $min_indent); $text = preg_replace('/^'.$indent_string.'/m', '', $text); } if ($this->getEngine()->isTextMode()) { $out = array(); $header = array(); if ($options['counterexample']) { $header[] = 'counterexample'; } if ($options['name'] != '') { $header[] = 'name='.$options['name']; } if ($header) { $out[] = implode(', ', $header); } $text = preg_replace('/^/m', ' ', $text); $out[] = $text; return implode("\n", $out); } if (empty($options['lang'])) { // If the user hasn't specified "lang=..." explicitly, try to guess the // language. If we fail, fall back to configured defaults. $lang = PhutilLanguageGuesser::guessLanguage($text); if (!$lang) { $lang = nonempty( $this->getEngine()->getConfig('phutil.codeblock.language-default'), 'php'); } $options['lang'] = $lang; } $code_body = $this->highlightSource($text, $options); $name_header = null; if ($options['name']) { $name_header = phutil_tag( 'div', array( 'class' => 'remarkup-code-header', ), $options['name']); } return phutil_tag( 'div', array( 'class' => 'remarkup-code-block', 'data-code-lang' => $options['lang'], 'data-sigil' => 'remarkup-code-block', ), array($name_header, $code_body)); } private function highlightSource($text, array $options) { if ($options['counterexample']) { $aux_class = ' remarkup-counterexample'; } else { $aux_class = null; } $aux_style = null; if ($options['lines']) { // Put a minimum size on this because the scrollbar is otherwise // unusable. $height = max(6, (int)$options['lines']); $aux_style = 'max-height: '.(2 * $height).'em;'; } $engine = $this->getEngine()->getConfig('syntax-highlighter.engine'); if (!$engine) { $engine = 'PhutilDefaultSyntaxHighlighterEngine'; } $engine = newv($engine, array()); $engine->setConfig( 'pygments.enabled', $this->getEngine()->getConfig('pygments.enabled')); return phutil_tag( 'pre', array( 'class' => 'remarkup-code'.$aux_class, 'style' => $aux_style, ), PhutilSafeHTML::applyFunction( 'rtrim', $engine->highlightSource($options['lang'], $text))); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupDefaultBlockRule.php similarity index 87% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupDefaultBlockRule.php index 7b4fe34..44fc977 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupDefaultBlockRule.php @@ -1,36 +1,35 @@ applyRules($text); if ($this->getEngine()->isTextMode()) { if (!$this->getEngine()->getConfig('preserve-linebreaks')) { $text = preg_replace('/ *\n */', ' ', $text); } return $text; } if ($this->getEngine()->getConfig('preserve-linebreaks')) { $text = phutil_escape_html_newlines($text); } if (!strlen($text)) { return null; } return phutil_tag('p', array(), $text); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupHeaderBlockRule.php similarity index 97% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupHeaderBlockRule.php index 4948f38..76728c1 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupHeaderBlockRule.php @@ -1,163 +1,162 @@ 1) { $level = ($lines[1][0] == '=') ? 1 : 2; $text = trim($lines[0]); } else { $level = 0; for ($ii = 0; $ii < min(5, strlen($text)); $ii++) { if ($text[$ii] == '=' || $text[$ii] == '#') { ++$level; } else { break; } } $text = trim($text, ' =#'); } $engine = $this->getEngine(); if ($engine->isTextMode()) { $char = ($level == 1) ? '=' : '-'; return $text."\n".str_repeat($char, phutil_utf8_strlen($text)); } $use_anchors = $engine->getConfig('header.generate-toc'); $anchor = null; if ($use_anchors) { $anchor = $this->generateAnchor($level, $text); } $text = phutil_tag( 'h'.($level + 1), array( 'class' => 'remarkup-header', ), array($anchor, $this->applyRules($text))); return $text; } private function generateAnchor($level, $text) { $anchor = strtolower($text); $anchor = preg_replace('/[^a-z0-9]/', '-', $anchor); $anchor = preg_replace('/--+/', '-', $anchor); $anchor = trim($anchor, '-'); $anchor = substr($anchor, 0, 24); $anchor = trim($anchor, '-'); $base = $anchor; $key = self::KEY_HEADER_TOC; $engine = $this->getEngine(); $anchors = $engine->getTextMetadata($key, array()); $suffix = 1; while (!strlen($anchor) || isset($anchors[$anchor])) { $anchor = $base.'-'.$suffix; $anchor = trim($anchor, '-'); $suffix++; } // When a document contains a link inside a header, like this: // // = [[ http://wwww.example.com/ | example ]] = // // ...we want to generate a TOC entry with just "example", but link the // header itself. We push the 'toc' state so all the link rules generate // just names. $engine->pushState('toc'); $text = $this->applyRules($text); $text = $engine->restoreText($text); $anchors[$anchor] = array($level, $text); $engine->popState('toc'); $engine->setTextMetadata($key, $anchors); return phutil_tag( 'a', array( 'name' => $anchor, ), ''); } public static function renderTableOfContents(PhutilRemarkupEngine $engine) { $key = self::KEY_HEADER_TOC; $anchors = $engine->getTextMetadata($key, array()); if (count($anchors) < 2) { // Don't generate a TOC if there are no headers, or if there's only // one header (since such a TOC would be silly). return null; } $depth = 0; $toc = array(); foreach ($anchors as $anchor => $info) { list($level, $name) = $info; while ($depth < $level) { $toc[] = hsprintf(''); $depth--; } $toc[] = phutil_tag( 'li', array(), phutil_tag( 'a', array( 'href' => '#'.$anchor, ), $name)); } while ($depth > 0) { $toc[] = hsprintf(''); $depth--; } return phutil_implode_html("\n", $toc); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php similarity index 87% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php index 155d9de..487a34e 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php @@ -1,33 +1,33 @@ 'remarkup-hr')); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupInlineBlockRule.php similarity index 65% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupInlineBlockRule.php index c74b58e..3a72197 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupInlineBlockRule.php @@ -1,14 +1,13 @@ applyRules($text); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupInterpreterBlockRule.php similarity index 93% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupInterpreterBlockRule.php index 8da9184..9e2684f 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupInterpreterBlockRule.php @@ -1,90 +1,89 @@ parse($matches[2]); } $interpreters = id(new PhutilSymbolLoader()) ->setAncestorClass('PhutilRemarkupBlockInterpreter') ->loadObjects(); foreach ($interpreters as $interpreter) { $interpreter->setEngine($this->getEngine()); } $lines[$first_key] = preg_replace( self::START_BLOCK_PATTERN, '', $lines[$first_key]); $lines[$last_key] = preg_replace( self::END_BLOCK_PATTERN, '', $lines[$last_key]); if (trim($lines[$first_key]) === '') { unset($lines[$first_key]); } if (trim($lines[$last_key]) === '') { unset($lines[$last_key]); } $content = implode("\n", $lines); $interpreters = mpull($interpreters, null, 'getInterpreterName'); if (isset($interpreters[$matches[1]])) { return $interpreters[$matches[1]]->markupContent($content, $argv); } $message = pht('No interpreter found: %s', $matches[1]); if ($this->getEngine()->isTextMode()) { return '('.$message.')'; } return phutil_tag( 'div', array( 'class' => 'remarkup-interpreter-error', ), $message); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupListBlockRule.php similarity index 99% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupListBlockRule.php index e14fcad..ce8b4fd 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupListBlockRule.php @@ -1,502 +1,501 @@ $line) { $matches = null; if (preg_match($regex, $line)) { $regex = self::CONT_BLOCK_PATTERN; if (preg_match('/^(\s+)/', $line, $matches)) { $space = strlen($matches[1]); } else { $space = 0; } $min_space = min($min_space, $space); } } $regex = self::START_BLOCK_PATTERN; if ($min_space) { foreach ($lines as $key => $line) { if (preg_match($regex, $line)) { $regex = self::CONT_BLOCK_PATTERN; $lines[$key] = substr($line, $min_space); } } } // The input text may have linewraps in it, like this: // // - derp derp derp derp // derp derp derp derp // - blarp blarp blarp blarp // // Group text lines together into list items, stored in $items. So the // result in the above case will be: // // array( // array( // "- derp derp derp derp", // " derp derp derp derp", // ), // array( // "- blarp blarp blarp blarp", // ), // ); $item = array(); $regex = self::START_BLOCK_PATTERN; foreach ($lines as $line) { if (preg_match($regex, $line)) { $regex = self::CONT_BLOCK_PATTERN; if ($item) { $items[] = $item; $item = array(); } } $item[] = $line; } if ($item) { $items[] = $item; } // Process each item to normalize the text, remove line wrapping, and // determine its depth (indentation level) and style (ordered vs unordered). // // Given the above example, the processed array will look like: // // array( // array( // 'text' => 'derp derp derp derp derp derp derp derp', // 'depth' => 0, // 'style' => '-', // ), // array( // 'text' => 'blarp blarp blarp blarp', // 'depth' => 0, // 'style' => '-', // ), // ); $has_marks = false; foreach ($items as $key => $item) { $item = preg_replace('/\s*\n\s*/', ' ', implode("\n", $item)); $item = rtrim($item); if (!strlen($item)) { unset($items[$key]); continue; } $matches = null; if (preg_match('/^\s*([-*#]{2,})/', $item, $matches)) { // Alternate-style indents; use number of list item symbols. $depth = strlen($matches[1]) - 1; } else if (preg_match('/^(\s+)/', $item, $matches)) { // Markdown-style indents; use indent depth. $depth = strlen($matches[1]); } else { $depth = 0; } if (preg_match('/^\s*(?:#|[0-9])/', $item)) { $style = '#'; } else { $style = '-'; } // Strip leading indicators off the item. $text = preg_replace(self::STRIP_BLOCK_PATTERN, '', $item); // Look for "[]", "[ ]", "[*]", "[x]", etc., which we render as a // checkbox. $mark = null; $matches = null; if (preg_match('/^\s*\[(.?)\]\s*/', $text, $matches)) { if (strlen(trim($matches[1]))) { $mark = true; } else { $mark = false; } $has_marks = true; $text = substr($text, strlen($matches[0])); } $items[$key] = array( 'text' => $text, 'depth' => $depth, 'style' => $style, 'mark' => $mark, ); } $items = array_values($items); // Users can create a sub-list by indenting any deeper amount than the // previous list, so these are both valid: // // - a // - b // // - a // - b // // In the former case, we'll have depths (0, 2). In the latter case, depths // (0, 4). We don't actually care about how many spaces there are, only // how many list indentation levels (that is, we want to map both of // those cases to (0, 1), indicating "outermost list" and "first sublist"). // // This is made more complicated because lists at two different indentation // levels might be at the same list level: // // - a // - b // - c // - d // // Here, 'b' and 'd' are at the same list level (2) but different indent // levels (2, 4). // // Users can also create "staircases" like this: // // - a // - b // # c // // While this is silly, we'd like to render it as faithfully as possible. // // In order to do this, we convert the list of nodes into a tree, // normalizing indentation levels and inserting dummy nodes as necessary to // make the tree well-formed. See additional notes at buildTree(). // // In the case above, the result is a tree like this: // // - // - // - a // - b // # c $l = 0; $r = count($items); $tree = $this->buildTree($items, $l, $r, $cur_level = 0); // We may need to open a list on a node, but they do not have // list style information yet. We need to propagate list style information // backward through the tree. In the above example, the tree now looks // like this: // // - // - // - a // - b // # c $this->adjustTreeStyleInformation($tree); // Finally, we have enough information to render the tree. $out = $this->renderTree($tree, 0, $has_marks); if ($this->getEngine()->isTextMode()) { $out = implode('', $out); $out = rtrim($out, "\n"); $out = preg_replace('/ +$/m', '', $out); return $out; } return phutil_implode_html('', $out); } /** * See additional notes in @{method:markupText}. */ private function buildTree(array $items, $l, $r, $cur_level) { if ($l == $r) { return array(); } if ($cur_level > self::MAXIMUM_LIST_NESTING_DEPTH) { // This algorithm is recursive and we don't need you blowing the stack // with your oh-so-clever 50,000-item-deep list. Cap indentation levels // at a reasonable number and just shove everything deeper up to this // level. $nodes = array(); for ($ii = $l; $ii < $r; $ii++) { $nodes[] = array( 'level' => $cur_level, 'items' => array(), ) + $items[$ii]; } return $nodes; } $min = $l; for ($ii = $r - 1; $ii >= $l; $ii--) { if ($items[$ii]['depth'] <= $items[$min]['depth']) { $min = $ii; } } $min_depth = $items[$min]['depth']; $nodes = array(); if ($min != $l) { $nodes[] = array( 'text' => null, 'level' => $cur_level, 'style' => null, 'mark' => null, 'items' => $this->buildTree($items, $l, $min, $cur_level + 1), ); } $last = $min; for ($ii = $last + 1; $ii < $r; $ii++) { if ($items[$ii]['depth'] == $min_depth) { $nodes[] = array( 'level' => $cur_level, 'items' => $this->buildTree($items, $last + 1, $ii, $cur_level + 1), ) + $items[$last]; $last = $ii; } } $nodes[] = array( 'level' => $cur_level, 'items' => $this->buildTree($items, $last + 1, $r, $cur_level + 1), ) + $items[$last]; return $nodes; } /** * See additional notes in @{method:markupText}. */ private function adjustTreeStyleInformation(array &$tree) { // The effect here is just to walk backward through the nodes at this level // and apply the first style in the list to any empty nodes we inserted // before it. As we go, also recurse down the tree. $style = '-'; for ($ii = count($tree) - 1; $ii >= 0; $ii--) { if ($tree[$ii]['style'] !== null) { // This is the earliest node we've seen with style, so set the // style to its style. $style = $tree[$ii]['style']; } else { // This node has no style, so apply the current style. $tree[$ii]['style'] = $style; } if ($tree[$ii]['items']) { $this->adjustTreeStyleInformation($tree[$ii]['items']); } } } /** * See additional notes in @{method:markupText}. */ private function renderTree(array $tree, $level, $has_marks) { $style = idx(head($tree), 'style'); $out = array(); if (!$this->getEngine()->isTextMode()) { switch ($style) { case '#': $tag = 'ol'; break; case '-': $tag = 'ul'; break; } if ($has_marks) { $out[] = hsprintf( '<%s class="remarkup-list remarkup-list-with-checkmarks">', $tag); } else { $out[] = hsprintf( '<%s class="remarkup-list">', $tag); } $out[] = "\n"; } $number = 1; foreach ($tree as $item) { if ($this->getEngine()->isTextMode()) { if ($item['text'] === null) { // Don't render anything. } else { $out[] = str_repeat(' ', 2 * $level); if ($item['mark'] !== null) { if ($item['mark']) { $out[] = '[X] '; } else { $out[] = '[ ] '; } } else { switch ($style) { case '#': $out[] = $number.'. '; $number++; break; case '-': $out[] = '- '; break; } } $out[] = $this->applyRules($item['text'])."\n"; } } else { if ($item['text'] === null) { $out[] = hsprintf('
  • '); } else { if ($item['mark'] !== null) { if ($item['mark'] == true) { $out[] = hsprintf( '
  • '); } else { $out[] = hsprintf( '
  • '); } $out[] = phutil_tag( 'input', array( 'type' => 'checkbox', 'checked' => ($item['mark'] ? 'checked' : null), 'disabled' => 'disabled', )); $out[] = ' '; } else { $out[] = hsprintf('
  • '); } $out[] = $this->applyRules($item['text']); } } if ($item['items']) { $subitems = $this->renderTree($item['items'], $level + 1, $has_marks); foreach ($subitems as $i) { $out[] = $i; } } if (!$this->getEngine()->isTextMode()) { $out[] = hsprintf("
  • \n"); } } if (!$this->getEngine()->isTextMode()) { switch ($style) { case '#': $out[] = hsprintf(''); break; case '-': $out[] = hsprintf(''); break; } } return $out; } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupLiteralBlockRule.php similarity index 87% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupLiteralBlockRule.php index 08fba58..72646ff 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupLiteralBlockRule.php @@ -1,34 +1,33 @@ getEngine()->isTextMode()) { return $text; } $text = phutil_split_lines($text, $retain_endings = true); return phutil_implode_html(phutil_tag('br', array()), $text); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupNoteBlockRule.php similarity index 95% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupNoteBlockRule.php index 6089253..64e9fbb 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupNoteBlockRule.php @@ -1,93 +1,92 @@ getRegEx(), $lines[$cursor])) { $num_lines++; $cursor++; while (isset($lines[$cursor])) { if (trim($lines[$cursor])) { $num_lines++; $cursor++; continue; } break; } } return $num_lines; } public function markupText($text, $children) { $matches = array(); preg_match($this->getRegEx(), $text, $matches); if (idx($matches, 'showword')) { $word = $matches['showword']; $show = true; } else { $word = $matches['hideword']; $show = false; } $class_suffix = phutil_utf8_strtolower($word); // This is the "(IMPORTANT)" or "NOTE:" part. $word_part = rtrim(substr($text, 0, strlen($matches[0]))); // This is the actual text. $text_part = substr($text, strlen($matches[0])); $text_part = $this->applyRules(rtrim($text_part)); $text_mode = $this->getEngine()->isTextMode(); if ($text_mode) { return $word_part.' '.$text_part; } if ($show) { $content = array( phutil_tag( 'span', array( 'class' => 'remarkup-note-word', ), $word_part), ' ', $text_part); } else { $content = $text_part; } return phutil_tag( 'div', array( 'class' => 'remarkup-'.$class_suffix ), $content); } private function getRegEx() { $words = array( 'NOTE', 'IMPORTANT', 'WARNING', ); foreach ($words as $k => $word) { $words[$k] = preg_quote($word, '/'); } $words = implode('|', $words); return '/^(?:'. '(?:\((?P'.$words.')\))'. '|'. '(?:(?P'.$words.'):))\s*'. '/'; } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupQuotesBlockRule.php similarity index 89% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupQuotesBlockRule.php index 957acaf..9e168cf 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupQuotesBlockRule.php @@ -1,43 +1,42 @@ /', $lines[$pos])) { do { ++$pos; } while (isset($lines[$pos]) && preg_match('/^>/', $lines[$pos])); } return ($pos - $cursor); } public function supportsChildBlocks() { return true; } public function extractChildText($text) { $text = phutil_split_lines($text, true); foreach ($text as $key => $line) { $text[$key] = substr($line, 1); } return array('', implode('', $text)); } public function markupText($text, $children) { if ($this->getEngine()->isTextMode()) { $lines = phutil_split_lines($children); return '> '.implode("\n> ", $lines); } return phutil_tag( 'blockquote', array(), $children); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupReplyBlockRule.php similarity index 95% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupReplyBlockRule.php index 44da3a0..491ef7c 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupReplyBlockRule.php @@ -1,93 +1,92 @@ >!/', $lines[$pos])) { do { ++$pos; } while (isset($lines[$pos]) && preg_match('/^>/', $lines[$pos])); } return ($pos - $cursor); } public function supportsChildBlocks() { return true; } public function extractChildText($text) { $text = phutil_split_lines($text, true); $head = array(); $body = array(); $head = substr(reset($text), 3); $body = array_slice($text, 1); // Remove the carets. foreach ($body as $key => $line) { $body[$key] = substr($line, 1); } // Strip leading empty lines. foreach ($body as $key => $line) { if (strlen(trim($line))) { break; } unset($body[$key]); } return array(trim($head), implode('', $body)); } public function markupText($text, $children) { $text = $this->applyRules($text); if ($this->getEngine()->isTextMode()) { $children = phutil_split_lines($children, true); foreach ($children as $key => $child) { if (strlen(trim($child))) { $children[$key] = '> '.$child; } else { $children[$key] = '>'.$child; } } $children = implode('', $children); return $text."\n\n".$children; } return phutil_tag( 'blockquote', array( 'class' => 'remarkup-reply-block', ), array( "\n", phutil_tag( 'div', array( 'class' => 'remarkup-reply-head', ), $text), "\n", phutil_tag( 'div', array( 'class' => 'remarkup-reply-body', ), $children), "\n", )); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupSimpleTableBlockRule.php similarity index 94% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupSimpleTableBlockRule.php index ce6fd90..9b47f40 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupSimpleTableBlockRule.php @@ -1,74 +1,73 @@ 'td', 'content' => $this->applyRules($cell)); } if (!$headings) { $rows[] = array('type' => 'tr', 'content' => $cells); } else if ($rows) { // Mark previous row with headings. foreach ($cells as $i => $cell) { if ($cell['content']) { $rows[last_key($rows)]['content'][$i]['type'] = 'th'; } } } } if (!$rows) { return $this->applyRules($text); } return $this->renderRemarkupTable($rows); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupTableBlockRule.php similarity index 96% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupTableBlockRule.php index 65273b9..658971b 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupTableBlockRule.php @@ -1,106 +1,105 @@ /i', $lines[$cursor])) { $num_lines++; $cursor++; while (isset($lines[$cursor])) { $num_lines++; if (preg_match('@$@i', $lines[$cursor])) { break; } $cursor++; } } return $num_lines; } public function markupText($text, $children) { $matches = array(); if (!preg_match('@^(.*)
    $@si', $text, $matches)) { return $this->fail( $text, 'Bad table (expected ...
    )'); } $body = $matches[1]; $row_fragment = '(?:\s*(.*)\s*)'; $cell_fragment = '(?:\s*<(td|th)>(.*)\s*)'; // Test that the body contains only valid rows. if (!preg_match('@^'.$row_fragment.'+$@Usi', $body)) { return $this->fail( $body, 'Bad table syntax (expected rows ...)'); } // Capture the rows. $row_regex = '@'.$row_fragment.'@Usi'; if (!preg_match_all($row_regex, $body, $matches, PREG_SET_ORDER)) { throw new Exception( 'Bug in Remarkup tables, parsing fails for input: '.$text); } $out_rows = array(); $rows = $matches; foreach ($rows as $row) { $content = $row[1]; // Test that the row contains only valid cells. if (!preg_match('@^'.$cell_fragment.'+$@Usi', $content)) { return $this->fail( $content, 'Bad table syntax (expected cells ...)'); } // Capture the cells. $cell_regex = '@'.$cell_fragment.'@Usi'; if (!preg_match_all($cell_regex, $content, $matches, PREG_SET_ORDER)) { throw new Exception( 'Bug in Remarkup tables, parsing fails for input: '.$text); } $out_cells = array(); foreach ($matches as $cell) { $cell_type = $cell[1]; $cell_content = $cell[2]; $out_cells[] = array( 'type' => $cell_type, 'content' => $this->applyRules($cell_content), ); } $out_rows[] = array( 'type' => 'tr', 'content' => $out_cells, ); } return $this->renderRemarkupTable($out_rows); } private function fail($near, $message) { $message = sprintf( '%s near: %s', $message, phutil_utf8_shorten($near, 32000)); if ($this->getEngine()->isTextMode()) { return '('.$message.')'; } return hsprintf('
    %s
    ', $message); } } diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupTestInterpreterRule.php similarity index 83% rename from src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php rename to src/markup/engine/remarkup/blockrule/PhutilRemarkupTestInterpreterRule.php index 91d7dd1..7ffc4bb 100644 --- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php +++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupTestInterpreterRule.php @@ -1,17 +1,17 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@\\*\\*(.+?)\\*\\*@s', array($this, 'applyCallback'), $text); } protected function applyCallback($matches) { return hsprintf('%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php similarity index 86% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php index 25c6311..623441c 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDelRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php similarity index 98% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php index a460d3b..0d5debf 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupDocumentLinkRule.php @@ -1,123 +1,123 @@ getEngine()->isTextMode()) { $text = $link; if (strncmp($link, '/', 1) == 0 || strncmp($link, '#', 1) == 0) { $base = $this->getEngine()->getConfig('uri.prefix'); if (strncmp($link, '/', 1) == 0) { $base = rtrim($base, '/'); } $text = $base.$text; } // If present, strip off "mailto:". $text = preg_replace('/^mailto:/', '', $text); if ($link == $name) { return $text; } return $name.' <'.$text.'>'; } // By default, we open links in a new window or tab. For anchors on the same // page, just jump normally. $target = '_blank'; if (strncmp($link, '#', 1) == 0) { $target = null; } $name = preg_replace('/^mailto:/', '', $name); if ($this->getEngine()->getState('toc')) { return $name; } else { return phutil_tag( 'a', array( 'href' => $link, 'class' => 'remarkup-link', 'target' => $target, ), $name); } } public function markupAlternateLink($matches) { $uri = trim($matches[2]); // NOTE: We apply some special rules to avoid false positives here. The // major concern is that we do not want to convert `x[0][1](y)` in a // discussion about C source code into a link. To this end, we: // // - Don't match at word boundaries; // - require the URI to contain a "/" character or "@" character; and // - reject URIs which being with a quote character. if ($uri[0] == '"' || $uri[0] == "'" || $uri[0] == '`') { return $matches[0]; } if (strpos($uri, '/') === false && strpos($uri, '@') === false) { return $matches[0]; } return $this->markupDocumentLink( array( $matches[0], $matches[2], $matches[1], )); } public function markupDocumentLink($matches) { $uri = trim($matches[1]); $name = trim(idx($matches, 2, $uri)); // If whatever is being linked to begins with "/" or "#", or has "://", // or is "mailto:", treat it as a URI instead of a wiki page. $is_uri = preg_match('@(^/)|(://)|(^#)|(^mailto:)@', $uri); if ($is_uri && strncmp('/', $uri, 1) && strncmp('#', $uri, 1)) { $protocols = $this->getEngine()->getConfig( 'uri.allowed-protocols', array()); $protocol = id(new PhutilURI($uri))->getProtocol(); if (!idx($protocols, $protocol)) { // Don't treat this as a URI if it's not an allowed protocol. $is_uri = false; } } if (!$is_uri) { return $matches[0]; } return $this->getEngine()->storeText($this->renderHyperlink($uri, $name)); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupEscapeRemarkupRule.php similarity index 81% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupEscapeRemarkupRule.php index 0b49d41..d9f56e2 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupEscapeRemarkupRule.php @@ -1,19 +1,19 @@ getEngine()->storeText("\1"); return str_replace("\1", $replace, $text); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php similarity index 95% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php index e316f86..2e3d87b 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupHyperlinkRule.php @@ -1,102 +1,99 @@ " around them get linked exactly, without // the "<>". Angle brackets are basically special and mean "this is a URL // with weird characters". This is assumed to be reasonable because they // don't appear in normal text or normal URLs. $text = preg_replace_callback( '@<(\w{3,}://[^\s'.PhutilRemarkupBlockStorage::MAGIC_BYTE.']+?)>@', array($this, 'markupHyperlink'), $text); // Anything else we match "ungreedily", which means we'll look for // stuff that's probably puncutation or otherwise not part of the URL and // not link it. This lets someone write "QuicK! Go to // http://www.example.com/!". We also apply some paren balancing rules. // NOTE: We're explicitly avoiding capturing stored blocks, so text like // `http://www.example.com/[[x | y]]` doesn't get aggressively captured. $text = preg_replace_callback( '@(\w{3,}://[^\s'.PhutilRemarkupBlockStorage::MAGIC_BYTE.']+)@', array($this, 'markupHyperlinkUngreedy'), $text); return $text; } protected function markupHyperlink($matches) { $protocols = $this->getEngine()->getConfig( 'uri.allowed-protocols', array()); $protocol = id(new PhutilURI($matches[1]))->getProtocol(); if (!idx($protocols, $protocol)) { // If this URI doesn't use a whitelisted protocol, don't link it. This // is primarily intended to prevent javascript:// silliness. return $this->getEngine()->storeText($matches[1]); } return $this->storeRenderedHyperlink($matches[1]); } protected function storeRenderedHyperlink($link) { return $this->getEngine()->storeText($this->renderHyperlink($link)); } protected function renderHyperlink($link) { if ($this->getEngine()->isTextMode()) { return $link; } if ($this->getEngine()->getState('toc')) { return $link; } else { return phutil_tag( 'a', array( 'href' => $link, 'class' => 'remarkup-link', 'target' => '_blank', ), $link); } } protected function markupHyperlinkUngreedy($matches) { $match = $matches[1]; $tail = null; $trailing = null; if (preg_match('/[;,.:!?]+$/', $match, $trailing)) { $tail = $trailing[0]; $match = substr($match, 0, -strlen($tail)); } // If there's a closing paren at the end but no balancing open paren in // the URL, don't link the close paren. This is an attempt to gracefully // handle the two common paren cases, Wikipedia links and English language // parentheticals, e.g.: // // http://en.wikipedia.org/wiki/Noun_(disambiguation) // (see also http://www.example.com) // // We could apply a craftier heuristic here which tries to actually balance // the parens, but this is probably sufficient. if (preg_match('/\\)$/', $match) && !preg_match('/\\(/', $match)) { $tail = ')'.$tail; $match = substr($match, 0, -1); } return hsprintf('%s%s', $this->markupHyperlink(array(null, $match)), $tail); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php similarity index 85% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php index d5f5790..f05410c 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupItalicRule.php @@ -1,24 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupLinebreaksRule.php similarity index 72% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupLinebreaksRule.php index ece540d..2db33d9 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupLinebreaksRule.php @@ -1,13 +1,13 @@ getEngine()->isTextMode()) { return $text; } return phutil_escape_html_newlines($text); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php similarity index 93% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php index 45b7e63..7a5999b 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupMonospaceRule.php @@ -1,39 +1,39 @@ getEngine()->isTextMode()) { $result = $matches[0]; } else { $match = isset($matches[2]) ? $matches[2] : $matches[1]; $result = phutil_tag( 'tt', array( 'class' => 'remarkup-monospaced', ), $match); } return $this->getEngine()->storeText($result); } } diff --git a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleUnderline.php b/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php similarity index 85% rename from src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleUnderline.php rename to src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php index 9cb4137..0224fb7 100644 --- a/src/markup/engine/remarkup/markuprule/PhutilRemarkupRuleUnderline.php +++ b/src/markup/engine/remarkup/markuprule/PhutilRemarkupUnderlineRule.php @@ -1,25 +1,24 @@ getEngine()->isTextMode()) { return $text; } return $this->replaceHTML( '@(?%s', $matches[1]); } }