├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── github │ └── gitbucket │ └── markedj │ ├── Grammer.java │ ├── InlineLexer.java │ ├── Lexer.java │ ├── Marked.java │ ├── Options.java │ ├── Parser.java │ ├── Renderer.java │ ├── Utils.java │ ├── extension │ ├── Extension.java │ ├── TokenConsumer.java │ └── gfm │ │ └── alert │ │ ├── DefaultGFMAlertRenderer.java │ │ ├── GFMAlertEndToken.java │ │ ├── GFMAlertExtension.java │ │ ├── GFMAlertOptions.java │ │ ├── GFMAlertRenderer.java │ │ ├── GFMAlertStartToken.java │ │ └── GFMAlerts.java │ ├── rule │ ├── FindAllRule.java │ ├── FindFirstRule.java │ ├── NoopRule.java │ └── Rule.java │ └── token │ ├── BlockquoteEndToken.java │ ├── BlockquoteStartToken.java │ ├── CodeToken.java │ ├── HeadingToken.java │ ├── HrToken.java │ ├── HtmlToken.java │ ├── ListEndToken.java │ ├── ListItemEndToken.java │ ├── ListItemStartToken.java │ ├── ListStartToken.java │ ├── LooseItemStartToken.java │ ├── ParagraphToken.java │ ├── SpaceToken.java │ ├── TableToken.java │ ├── TextToken.java │ └── Token.java └── test ├── java └── io │ └── github │ └── gitbucket │ └── markedj │ ├── BlockquotesTest.java │ ├── GFMAlertsTest.java │ ├── MarkedTest.java │ └── Resources.java └── resources ├── empty_item_of_list.html ├── empty_item_of_list.md ├── gfm └── alerts │ ├── caution.html │ ├── caution.md │ ├── important.html │ ├── important.md │ ├── note.html │ ├── note.md │ ├── testWithMultipleAlerts.html │ ├── testWithMultipleAlerts.md │ ├── testWithParagraphBeforeAndAfter.html │ ├── testWithParagraphBeforeAndAfter.md │ ├── tip.html │ ├── tip.md │ ├── warning.html │ ├── warning.md │ ├── warning_custom_renderer.html │ └── warning_custom_title.html ├── gitbucket.html ├── gitbucket.md ├── ldap_settings.html ├── ldap_settings.md ├── multiple_blockquotes_1.html ├── multiple_blockquotes_1.md ├── multiple_blockquotes_2.html ├── multiple_blockquotes_2.md ├── nested_content_of_list.html ├── nested_content_of_list.md ├── nptable.html ├── nptable.md ├── quote.html ├── quote.md ├── stackoverflow.txt ├── stackoverflow2.txt ├── table.html ├── table.md ├── wikilink.html └── wikilink.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @takezoe @xuwei-k 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "maven" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | java: [8, 21] 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up JDK 21 | uses: actions/setup-java@v4 22 | with: 23 | java-version: ${{ matrix.java }} 24 | distribution: temurin 25 | - name: Build 26 | run: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 27 | - name: Test 28 | run: mvn test -B 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.class 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | .idea/ 15 | *.iml 16 | 17 | # Visual Studio Code 18 | .vscode 19 | 20 | # Eclipse 21 | .settings 22 | .classpath 23 | .project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markedj [![build](https://github.com/gitbucket/markedj/workflows/build/badge.svg?branch=master)](https://github.com/gitbucket/markedj/actions?query=branch%3Amaster+workflow%3Abuild) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/markedj/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/markedj) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/gitbucket/markedj/blob/master/LICENSE) 2 | 3 | JVM port of graceful markdown processor [marked.js](https://github.com/chjj/marked). 4 | 5 | ## Usage 6 | 7 | First, add following dependency into your `pom.xml`: 8 | 9 | ```xml 10 | 11 | 12 | io.github.gitbucket 13 | markedj 14 | 1.0.20 15 | 16 | 17 | ``` 18 | 19 | You can easily use markedj via `io.github.gitbucket.markedj.Marked`: 20 | 21 | ```java 22 | import io.github.gitbucket.markedj.*; 23 | 24 | String markdown = ... 25 | 26 | // With default options 27 | String html1 = Marked.marked(markdown); 28 | 29 | // Specify options 30 | Options options = new Options(); 31 | options.setSanitize(true); 32 | 33 | String html2 = Marked.marked(markdown, options); 34 | ``` 35 | 36 | ## Options 37 | 38 | `io.github.gitbucket.markedj.Options` has following properties to control Markdown conversion: 39 | 40 | Name | Default | Description 41 | :------------|:--------|:------------ 42 | gfm | true | Enable [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown). 43 | tables | true | Enable GFM [tables](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables). This option requires the `gfm` option to be true. 44 | breaks | false | Enable GFM [line breaks](https://help.github.com/articles/github-flavored-markdown#newlines). This option requires the `gfm` option to be true. 45 | sanitize | false | Ignore any HTML that has been input. 46 | langPrefix | "lang-" | Prefix of class attribute of code block 47 | headerPrefix | "" | Prefix of id attribute of header 48 | safelist | See [Options.java](https://github.com/gitbucket/markedj/blob/master/src/main/java/io/github/gitbucket/markedj/Options.java) | Safelist of HTML tags. 49 | extensions | empty | Extensions. See [Extensions](#extensions) section 50 | 51 | By default, markedj uses Jsoup's safelist mechanism for HTML rendering. It restricts renderable tags, attributes and even protocols of attribute values. For example, the image url must be `http://` or `https://` by default. You can remove this restriction by customizing the safelist as follows: 52 | 53 | ```java 54 | String html1 = Marked.marked("![alt text](/img/some-image.png \"title\")"); 55 | // =>

\"alt

56 | 57 | Options options = new Options(); 58 | options.getSafelist().removeProtocols("img", "src", "http", "https"); 59 | 60 | String html2 = Marked.marked("![alt text](/img/some-image.png \"title\")", options); 61 | // =>

alt text

62 | ``` 63 | 64 | ## Extensions 65 | 66 | Markedj can be extended by implementing [custom extensions](https://github.com/gitbucket/markedj/blob/master/src/main/java/io/github/gitbucket/markedj/extension/Extension.java). 67 | Extensions can be used by adding them to the options. 68 | 69 | ```java 70 | Options options = new Options(); 71 | options.addExtension(new GFMAlertExtension()); 72 | String html = Marked.marked("> [!NOTE]\n> This is a note!", options); 73 | ``` 74 | 75 | ### GFMAlert extension 76 | 77 | Support for github like [alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts). 78 | 79 | For styling, some project-specific CSS is required. 80 | 81 | ```java 82 | Options options = new Options(); 83 | // Override default title for note alert 84 | GFMAlertOptions alertOptions = new GFMAlertOptions(); 85 | alertOptions.setTitle(GFMAlerts.Alert.WARNING, "Attention!!!"); 86 | GFMAlertExtension gfmAlerts = new GFMAlertExtension(alertOptions); 87 | options.addExtension(gfmAlerts); 88 | String html = Marked.marked("> [!NOTE]\n> This is a note!", options); 89 | ``` 90 | 91 | Supported alert types are `NOTE`, `TOP`, `IMPORTANT`, `WARNING`, and `CAUTION`. Here is a Markdown example: 92 | 93 | ```markdown 94 | > [!NOTE] 95 | > Useful information that users should know, even when skimming content. 96 | ``` 97 | 98 | This is translated to the following HTML: 99 | 100 | ```html 101 |
102 |

Note

103 |

Useful information that users should know, even when skimming content.

104 |
105 | ``` 106 | 107 | Generated HTML can be customized by implementing your own renderer. [DefaultGFMAlertRenderer](https://github.com/gitbucket/markedj/blob/master/src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/DefaultGFMAlertRenderer.java) is used by default. 108 | 109 | ## for Developers 110 | 111 | ### Release 112 | 113 | Run the following command to upload artifacts to sonatype: 114 | 115 | ``` 116 | mvn clean deploy -DperformRelease=true 117 | ``` 118 | 119 | Then, go to https://oss.sonatype.org/, close and release the staging repository. 120 | 121 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | io.github.gitbucket 5 | markedj 6 | jar 7 | 1.0.20 8 | markedj 9 | https://github.com/gitbucket/markedj 10 | JVM port of graceful markdown processor marked.js 11 | 2015 12 | 13 | 14 | The Apache Software License, Version 2.0 15 | http://www.apache.org/licenses/LICENSE-2.0.txt 16 | 17 | 18 | 19 | GitBucket 20 | https://github.com/gitbucket 21 | 22 | 23 | scm:git:https://github.com/gitbucket/markedj.git 24 | scm:git:https://github.com/gitbucket/markedj.git 25 | https://github.com/gitbucket/markedj 26 | 27 | 28 | GitHub Issues 29 | https://github.com/gitbucket/markedj/issues 30 | 31 | 32 | 33 | takezoe 34 | Naoki Takezoe 35 | takezoe_at_gmail.com 36 | http://takezoe.hatenablog.com/ 37 | +9 38 | 39 | 40 | 41 | 42 | 43 | org.jsoup 44 | jsoup 45 | 1.19.1 46 | 47 | 48 | junit 49 | junit 50 | 4.13.2 51 | provided 52 | 53 | 54 | org.assertj 55 | assertj-core 56 | 3.27.3 57 | test 58 | 59 | 60 | 61 | 62 | 63 | release-sign-artifacts 64 | 65 | 66 | performRelease 67 | true 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-gpg-plugin 75 | 76 | 77 | sign-artifacts 78 | verify 79 | 80 | sign 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.wagon 94 | wagon-ssh 95 | 3.5.3 96 | 97 | 98 | 99 | 109 | 110 | org.apache.maven.plugins 111 | maven-compiler-plugin 112 | 3.14.0 113 | 114 | 1.8 115 | 1.8 116 | 117 | 118 | 119 | maven-source-plugin 120 | 3.3.1 121 | 122 | 123 | attach-sources 124 | package 125 | 126 | jar-no-fork 127 | 128 | 129 | 130 | 131 | 132 | maven-javadoc-plugin 133 | 134 | UTF-8 135 | UTF-8 136 | en 137 | 138 | 139 | 140 | javadoc-jar 141 | package 142 | 143 | jar 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | sonatype-nexus-staging 154 | https://oss.sonatype.org/service/local/staging/deploy/maven2 155 | 156 | 157 | sonatype-nexus-snapshot 158 | https://oss.sonatype.org/content/repositories/snapshots 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Grammer.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import io.github.gitbucket.markedj.rule.FindFirstRule; 4 | import io.github.gitbucket.markedj.rule.FindAllRule; 5 | import io.github.gitbucket.markedj.rule.NoopRule; 6 | import io.github.gitbucket.markedj.rule.Rule; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class Grammer { 12 | 13 | public static String BULLET = "(?:[*+-]|\\d+\\.)"; 14 | public static String COMMENT = ""; 15 | public static String TAG = "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b"; 16 | public static String CLOSED = "<(" + TAG + ")[\\s\\S]+?<\\/\\1>"; 17 | public static String CLOSING = "<" + TAG + "(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>"; 18 | public static String HR = "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))"; 19 | public static String DEF = "^ *\\[([^\\]]+)\\]: *]+)>?(?: +[\"(]([^\\n]+)[\")])? *(?:\\n+|$)"; 20 | public static String BLOCK_HR = "^( *[-*_]){3,} *(?:\\n+|$)"; 21 | public static String BLOCK_HEADING = "^ *(#{1,6}) *([^\\n]+?) *#* *(?:\\n+|$)"; 22 | public static String BLOCK_LHEADING = "^([^\\n]+)\\n *(=|-){2,} *(?:\\n+|$)"; 23 | public static String BLOCK_BLOCKQUOTE = "^( *>[^\\n]+(\\n(?!" + removeLineStart(DEF) + ")[^\\n]+)*\\n*)+"; 24 | //public static String BLOCK_LIST = "^( *)(" + BULLET + ") [\\s\\S]+?(?:" + HR + "|\\n+(?=" + removeLineStart(DEF) + ")|\\n{2,}(?! )(?!\\1" + BULLET + " )\\n*|\\s*$)"; 25 | public static String BLOCK_LIST = "^( *)(" + BULLET + ") [\\s\\S]+?(?:\\n{2,}(?! )(?!\\1" + BULLET + " )\\n*|\\s*$)"; 26 | public static String BLOCK_DEF = "^ *\\[([^\\]]+)\\]: *]+)>?(?: +[\"(]([^\\n]+)[\")])? *(?:\\n+|$)"; 27 | public static String BLOCK_PARAGRAPH = "^((?:[^\\n]+\\n?(?!" + removeLineStart(BLOCK_HR) + "|" + removeLineStart(BLOCK_HEADING) + "|" + removeLineStart(BLOCK_LHEADING) + "|" + removeLineStart(BLOCK_BLOCKQUOTE) + "|<" + TAG + "|" + removeLineStart(BLOCK_DEF) + "))+)\\n*"; 28 | public static String BLOCK_GFM_FENCES = "^ *(`{3,}|~{3,})([ \\S]+)?\\n([\\s\\S]*?)\\s*\\1 *(?:\\n+|$)"; 29 | 30 | public static Map BLOCK_RULES = new HashMap<>(); 31 | public static Map BLOCK_GFM_RULES = new HashMap<>(); 32 | public static Map BLOCK_TABLE_RULES = new HashMap<>(); 33 | 34 | static { 35 | BLOCK_RULES.put("newline", new FindFirstRule("^\n+")); 36 | BLOCK_RULES.put("code", new FindFirstRule("^( {4}[^\n]+\n*)+")); 37 | BLOCK_RULES.put("fences", new NoopRule()); 38 | BLOCK_RULES.put("hr", new FindFirstRule(BLOCK_HR)); 39 | BLOCK_RULES.put("heading", new FindFirstRule(BLOCK_HEADING)); 40 | BLOCK_RULES.put("nptable", new NoopRule()); 41 | BLOCK_RULES.put("lheading", new FindFirstRule(BLOCK_LHEADING)); 42 | BLOCK_RULES.put("blockquote", new FindFirstRule(BLOCK_BLOCKQUOTE)); 43 | BLOCK_RULES.put("list", new FindFirstRule(BLOCK_LIST)); 44 | BLOCK_RULES.put("html", new FindFirstRule("^ *(?:" + COMMENT + " *(?:\\n|\\s*$)|" + CLOSED + " *(?:\\n{2,}|\\s*$)|" + CLOSING + " *(?:\\n{2,}|\\s*$))")); 45 | BLOCK_RULES.put("def", new FindFirstRule(BLOCK_DEF)); 46 | BLOCK_RULES.put("table", new NoopRule()); 47 | BLOCK_RULES.put("paragraph", new FindFirstRule(BLOCK_PARAGRAPH)); 48 | BLOCK_RULES.put("text", new FindFirstRule("^[^\n]+")); 49 | BLOCK_RULES.put("item", new FindAllRule(("(?m)^( *)(" + BULLET + ") [^\\n]*(?:\\n(?!\\1" + BULLET + " )[^\\n]*)*"))); 50 | 51 | BLOCK_GFM_RULES.putAll(BLOCK_RULES); 52 | BLOCK_GFM_RULES.put("fences", new FindFirstRule(BLOCK_GFM_FENCES)); 53 | BLOCK_GFM_RULES.put("paragraph", new FindFirstRule(BLOCK_PARAGRAPH.replace("(?!", "(?!" + removeLineStart(BLOCK_GFM_FENCES).replace("\\1", "\\2") + "|" + removeLineStart(BLOCK_LIST).replace("\\1", "\\3") + "|"))); 54 | BLOCK_GFM_RULES.put("heading", new FindFirstRule("^ *(#{1,6}) +([^\\n]+?) *#* *(?:\\n+|$)")); 55 | // TODO 56 | // block.gfm.paragraph = replace(block.paragraph) 57 | // ('(?!', '(?!' 58 | // + block.gfm.fences.source.replace('\\1', '\\2') + '|' 59 | // + block.list.source.replace('\\1', '\\3') + '|') 60 | // (); 61 | 62 | BLOCK_TABLE_RULES.putAll(BLOCK_GFM_RULES); 63 | BLOCK_TABLE_RULES.put("nptable", new FindFirstRule("^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)\\n*")); 64 | BLOCK_TABLE_RULES.put("table", new FindFirstRule("^ *\\|(.+)\\n *\\|( *[-:]+[-| :]*)\\n((?: *\\|.*(?:\\n|$))*)\\n*")); 65 | } 66 | 67 | private static String removeLineStart(String regex){ 68 | return regex.replaceAll("(^|[^\\[])\\^", "$1"); 69 | } 70 | 71 | public static String INSIDE = "(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*"; 72 | public static String HREF = "\\s*?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*"; 73 | 74 | public static Map INLINE_RULES = new HashMap<>(); 75 | public static Map INLINE_GFM_RULES = new HashMap<>(); 76 | public static Map INLINE_BREAKS_RULES = new HashMap<>(); 77 | 78 | public static String INLINE_ESCAPE = "^\\\\([\\\\`*{}\\[\\]()#+\\-.!_>])"; 79 | public static String INLINE_TEXT = "^[\\s\\S]+?(?=[\\\\]+(@|:\\/)[^ >]+)>")); 85 | INLINE_RULES.put("url", new NoopRule()); 86 | INLINE_RULES.put("tag", new FindFirstRule("^|^<\\/?\\w+(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>")); 87 | INLINE_RULES.put("link", new FindFirstRule(("^!?\\[(" + INSIDE + ")\\]\\(" + HREF + "\\)"))); 88 | INLINE_RULES.put("reflink", new FindFirstRule(("^!?\\[(" + INSIDE + ")\\]\\s*\\[([^\\]]*)\\]"))); 89 | INLINE_RULES.put("nolink", new FindFirstRule("^!?\\[((?:\\[[^\\]]*\\]|[^\\[\\]])*)\\]")); 90 | INLINE_RULES.put("strong", new FindFirstRule("^__([\\s\\S]+?)__(?!_)|^\\*\\*([\\s\\S]+?)\\*\\*(?!\\*)")); 91 | INLINE_RULES.put("em", new FindFirstRule("^\\b_((?:[^_]|__)+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)")); 92 | INLINE_RULES.put("code", new FindFirstRule("^(`+)\\s*([\\s\\S]*?[^`])\\s*\\1(?!`)")); 93 | INLINE_RULES.put("br", new FindFirstRule(INLINE_BR)); 94 | INLINE_RULES.put("del", new NoopRule()); 95 | INLINE_RULES.put("text", new FindFirstRule(INLINE_TEXT)); 96 | 97 | INLINE_GFM_RULES.putAll(INLINE_RULES); 98 | INLINE_GFM_RULES.put("escape", new FindFirstRule(INLINE_ESCAPE.replace("])", "~|])"))); 99 | INLINE_GFM_RULES.put("url", new FindFirstRule("^(https?:\\/\\/[^\\s<]+[^<.,:;\"')\\]\\s])")); 100 | INLINE_GFM_RULES.put("del", new FindFirstRule("^~~(?=\\S)([\\s\\S]*?\\S)~~")); 101 | INLINE_GFM_RULES.put("text", new FindFirstRule(INLINE_TEXT.replace("]|", "~]|").replace("|", "|https?://|"))); 102 | 103 | INLINE_BREAKS_RULES.putAll(INLINE_GFM_RULES); 104 | INLINE_BREAKS_RULES.put("br", new FindFirstRule(INLINE_BR.replace("{2,}", "*"))); 105 | INLINE_BREAKS_RULES.put("text", new FindFirstRule(INLINE_TEXT.replace("]|", "~]|").replace("|", "|https?://|").replace("{2,}", "*"))); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/InlineLexer.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import io.github.gitbucket.markedj.rule.Rule; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.regex.Pattern; 8 | 9 | import static io.github.gitbucket.markedj.Utils.*; 10 | 11 | public class InlineLexer { 12 | 13 | protected Map rules; 14 | protected Options options; 15 | protected Renderer renderer; 16 | protected boolean inLink = false; 17 | protected Map links; 18 | 19 | public InlineLexer(Map rules, Map links, Options options, Renderer renderer){ 20 | this.rules = rules; 21 | this.links = links; 22 | this.options = options; 23 | this.renderer = renderer; 24 | } 25 | 26 | public String output(String src){ 27 | StringBuilder out = new StringBuilder(); 28 | StringBuilder textBuffer = new StringBuilder(); 29 | 30 | while(src.length() > 0){ 31 | //escape 32 | { 33 | List cap = rules.get("escape").exec(src); 34 | if(!cap.isEmpty()){ 35 | if(textBuffer.length() != 0){ 36 | out.append(renderer.text(textBuffer.toString())); 37 | textBuffer.setLength(0); 38 | } 39 | 40 | src = src.substring(cap.get(0).length()); 41 | out.append(cap.get(1)); 42 | continue; 43 | } 44 | } 45 | 46 | // autolink 47 | { 48 | List cap = rules.get("autolink").exec(src); 49 | if(!cap.isEmpty()){ 50 | if(textBuffer.length() != 0){ 51 | out.append(renderer.text(textBuffer.toString())); 52 | textBuffer.setLength(0); 53 | } 54 | 55 | src = src.substring(cap.get(0).length()); 56 | String text; 57 | String href; 58 | if(cap.get(2).equals("@")){ 59 | if(cap.get(1).startsWith("mailto:")){ 60 | text = cap.get(1).substring(7); 61 | } else { 62 | text = cap.get(1); 63 | } 64 | href = "mailto:" + text; 65 | } else { 66 | text = escape(cap.get(1)); 67 | href = text; 68 | } 69 | out.append(renderer.link(href, null, text)); 70 | continue; 71 | } 72 | } 73 | 74 | // url (gfm) 75 | if(!inLink){ 76 | List cap = rules.get("url").exec(src); 77 | if(!cap.isEmpty()){ 78 | if(textBuffer.length() != 0){ 79 | out.append(renderer.text(textBuffer.toString())); 80 | textBuffer.setLength(0); 81 | } 82 | 83 | src = src.substring(cap.get(0).length()); 84 | String text = escape(cap.get(1)); 85 | String href = text; 86 | out.append(renderer.link(text, null, href)); 87 | continue; 88 | } 89 | } 90 | 91 | // tag 92 | { 93 | List cap = rules.get("tag").exec(src); 94 | if(!cap.isEmpty()){ 95 | if(textBuffer.length() != 0){ 96 | out.append(renderer.text(textBuffer.toString())); 97 | textBuffer.setLength(0); 98 | } 99 | 100 | if(!inLink && Pattern.compile("^").matcher(cap.get(0)).find()){ 103 | inLink = false; 104 | } 105 | 106 | src = src.substring(cap.get(0).length()); 107 | if(options.isSanitize()){ 108 | out.append(escape(cap.get(0))); 109 | } else { 110 | out.append(cap.get(0)); 111 | } 112 | continue; 113 | } 114 | } 115 | 116 | // link 117 | { 118 | List cap = rules.get("link").exec(src); 119 | if(!cap.isEmpty()){ 120 | if(textBuffer.length() != 0){ 121 | out.append(renderer.text(textBuffer.toString())); 122 | textBuffer.setLength(0); 123 | } 124 | 125 | src = src.substring(cap.get(0).length()); 126 | inLink = true; 127 | out.append(outputLink(cap, new Lexer.Link(cap.get(2), cap.get(3)))); 128 | inLink = false; 129 | continue; 130 | } 131 | } 132 | 133 | // reflink, nolink 134 | { 135 | List cap = rules.get("reflink").exec(src); 136 | if(cap.isEmpty()){ 137 | cap = rules.get("nolink").exec(src); 138 | } 139 | if(!cap.isEmpty()){ 140 | if(textBuffer.length() != 0){ 141 | out.append(renderer.text(textBuffer.toString())); 142 | textBuffer.setLength(0); 143 | } 144 | 145 | src = src.substring(cap.get(0).length()); 146 | String key; 147 | if(cap.size() > 2){ 148 | key = cap.get(2).replaceAll("\\s+", ""); 149 | } else { 150 | key = cap.get(1).replaceAll("\\s+", ""); 151 | } 152 | Lexer.Link link = links.get(key.toLowerCase()); 153 | if(link == null || isEmpty(link.getHref())){ 154 | out.append(renderer.nolink(cap.get(0))); 155 | continue; 156 | } 157 | inLink = true; 158 | out.append(outputLink(cap, link)); 159 | inLink = false; 160 | continue; 161 | } 162 | } 163 | 164 | // strong 165 | { 166 | List cap = rules.get("strong").exec(src); 167 | if(!cap.isEmpty()){ 168 | if(textBuffer.length() != 0){ 169 | out.append(renderer.text(textBuffer.toString())); 170 | textBuffer.setLength(0); 171 | } 172 | 173 | src = src.substring(cap.get(0).length()); 174 | out.append(renderer.strong(output(or(cap.get(2), cap.get(1))))); 175 | continue; 176 | } 177 | } 178 | 179 | // em 180 | { 181 | List cap = rules.get("em").exec(src); 182 | if(!cap.isEmpty()){ 183 | if(textBuffer.length() != 0){ 184 | out.append(renderer.text(textBuffer.toString())); 185 | textBuffer.setLength(0); 186 | } 187 | 188 | src = src.substring(cap.get(0).length()); 189 | out.append(renderer.em(output(or(cap.get(2), cap.get(1))))); 190 | continue; 191 | } 192 | } 193 | 194 | // code 195 | { 196 | List cap = rules.get("code").exec(src); 197 | if(!cap.isEmpty()){ 198 | if(textBuffer.length() != 0){ 199 | out.append(renderer.text(textBuffer.toString())); 200 | textBuffer.setLength(0); 201 | } 202 | 203 | src = src.substring(cap.get(0).length()); 204 | out.append(renderer.codespan(escape(cap.get(2), true))); 205 | continue; 206 | } 207 | } 208 | 209 | // br 210 | { 211 | List cap = rules.get("br").exec(src); 212 | if(!cap.isEmpty()){ 213 | if(textBuffer.length() != 0){ 214 | out.append(renderer.text(textBuffer.toString())); 215 | textBuffer.setLength(0); 216 | } 217 | 218 | src = src.substring(cap.get(0).length()); 219 | out.append(renderer.br()); 220 | continue; 221 | } 222 | } 223 | 224 | // del (gfm) 225 | { 226 | List cap = rules.get("del").exec(src); 227 | if(!cap.isEmpty()){ 228 | if(textBuffer.length() != 0){ 229 | out.append(renderer.text(textBuffer.toString())); 230 | textBuffer.setLength(0); 231 | } 232 | 233 | src = src.substring(cap.get(0).length()); 234 | out.append(renderer.del(output(cap.get(1)))); 235 | continue; 236 | } 237 | } 238 | 239 | // text 240 | { 241 | List cap = rules.get("text").exec(src); 242 | if(!cap.isEmpty()){ 243 | src = src.substring(cap.get(0).length()); 244 | // TODO smartypants 245 | //out.append(renderer.text(escape(smartypants(cap.get(0))))); 246 | textBuffer.append(escape(cap.get(0))); 247 | continue; 248 | } 249 | } 250 | 251 | // TODO Error 252 | //println("Infinite loop on byte: " + source.charAt(0).toByte) 253 | } 254 | 255 | if(textBuffer.length() != 0){ 256 | out.append(renderer.text(textBuffer.toString())); 257 | } 258 | 259 | return out.toString(); 260 | } 261 | 262 | protected String outputLink(List cap, Lexer.Link link){ 263 | String href = escape(link.getHref()); 264 | if(cap.get(0).charAt(0) != '!'){ 265 | return renderer.link(href, link.getTitle(), output(cap.get(1))); 266 | } else { 267 | return renderer.image(href, link.getTitle(), escape(cap.get(1))); 268 | } 269 | } 270 | 271 | // protected String smartypants(String text){ // TODO 272 | // return text; 273 | // } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Lexer.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import io.github.gitbucket.markedj.rule.Rule; 4 | import io.github.gitbucket.markedj.token.*; 5 | 6 | import java.util.*; 7 | 8 | import static io.github.gitbucket.markedj.Utils.*; 9 | import io.github.gitbucket.markedj.extension.Extension; 10 | 11 | public class Lexer { 12 | 13 | protected Options options; 14 | protected Map rules = null; 15 | 16 | public Lexer(Options options){ 17 | this.options = options; 18 | if(!options.isGfm()){ 19 | this.rules = Grammer.BLOCK_RULES; 20 | } else if(options.isTables()){ 21 | this.rules = Grammer.BLOCK_TABLE_RULES; 22 | } else { 23 | this.rules = Grammer.BLOCK_GFM_RULES; 24 | } 25 | } 26 | 27 | public LexerResult lex(String src){ 28 | LexerContext context = new LexerContext(); 29 | 30 | token(src 31 | .replace("\r\n", "\n") 32 | .replace("\r", "\n") 33 | .replace("\t", " ") 34 | .replace("\u00a0", " ") 35 | .replace("\u2424", "\n"), 36 | true, false, context); 37 | 38 | return new LexerResult(context.getTokens(), context.getLinks()); 39 | } 40 | 41 | protected void token(String src, boolean top, boolean bq, LexerContext context){ 42 | while(src.length() > 0){ 43 | // newline 44 | { 45 | List cap = rules.get("newline").exec(src); 46 | if(!cap.isEmpty()){ 47 | src = src.substring(cap.get(0).length()); 48 | if(cap.get(0).length() > 1){ 49 | context.pushToken(new SpaceToken()); 50 | } 51 | } 52 | } 53 | 54 | // code 55 | { 56 | List cap = rules.get("code").exec(src); 57 | if(!cap.isEmpty()){ 58 | src = src.substring(cap.get(0).length()); 59 | String code = cap.get(0).replaceAll("(?m)^ {4}", ""); 60 | context.pushToken(new CodeToken(code.replaceAll("\\n+$", ""), null, false)); 61 | continue; 62 | } 63 | } 64 | 65 | // fences (gfm) 66 | { 67 | List cap = rules.get("fences").exec(src); 68 | if(!cap.isEmpty()){ 69 | src = src.substring(cap.get(0).length()); 70 | String lang = cap.get(2); 71 | if(lang != null){ 72 | // Ignore extra information in Pandoc, R Markdown or PHP Markdown Extra. 73 | // https://github.com/gitbucket/markedj/issues/20 74 | lang = lang.replaceFirst("\\{.*}", ""); 75 | } 76 | context.pushToken(new CodeToken(cap.get(3), trim(lang), false)); 77 | continue; 78 | } 79 | } 80 | 81 | // heading 82 | { 83 | List cap = rules.get("heading").exec(src); 84 | if(!cap.isEmpty()){ 85 | src = src.substring(cap.get(0).length()); 86 | context.pushToken(new HeadingToken(cap.get(1).length(), cap.get(2))); 87 | continue; 88 | } 89 | } 90 | 91 | // table no leading pipe (gfm) 92 | if(top){ 93 | List cap = rules.get("nptable").exec(src); 94 | if(!cap.isEmpty()){ 95 | src = src.substring(cap.get(0).length()); 96 | 97 | String[] headers = cap.get(1).replaceAll("^ *| *\\| *$", "").split(" *\\| *"); 98 | String[] aligns = cap.get(2).replaceAll("^ *|\\| *$", "").split(" *\\| *"); 99 | String[] rows = cap.get(3).replaceAll("\n$", "").split("\n"); 100 | 101 | List headerList = array2list(headers); 102 | 103 | List alignList = new ArrayList<>(); 104 | for (String s : aligns) { 105 | if(s.matches("^ *-+: *$")){ 106 | alignList.add("right"); 107 | } else if(s.matches("^ *:-+: *$")){ 108 | alignList.add("center"); 109 | } else if(s.matches("^ *:-+ *$")){ 110 | alignList.add("left"); 111 | } else { 112 | alignList.add(null); 113 | } 114 | } 115 | 116 | int maxColumns = Math.max(headers.length, aligns.length); 117 | 118 | List> rowList = new ArrayList<>(); 119 | for (String row : rows) { 120 | String[] columns = row.split(" *\\| *"); 121 | if(maxColumns < columns.length){ 122 | maxColumns = columns.length; 123 | } 124 | rowList.add(array2list(columns)); 125 | } 126 | 127 | fillList(headerList, maxColumns, ""); 128 | fillList(alignList, maxColumns, null); 129 | for(List row: rowList){ 130 | fillList(row, maxColumns, ""); 131 | } 132 | 133 | context.pushToken(new TableToken(headerList, alignList, rowList)); 134 | continue; 135 | } 136 | } 137 | 138 | // lheading 139 | { 140 | List cap = rules.get("lheading").exec(src); 141 | if(!cap.isEmpty()){ 142 | src = src.substring(cap.get(0).length()); 143 | if(cap.get(2).equals("=")){ 144 | context.pushToken(new HeadingToken(1, cap.get(1))); 145 | } else { 146 | context.pushToken(new HeadingToken(2, cap.get(1))); 147 | } 148 | continue; 149 | } 150 | } 151 | 152 | // hr 153 | { 154 | List cap = rules.get("hr").exec(src); 155 | if(!cap.isEmpty()){ 156 | src = src.substring(cap.get(0).length()); 157 | context.pushToken(new HrToken()); 158 | continue; 159 | } 160 | } 161 | 162 | { 163 | Extension.LexResult result = null; 164 | for (Extension extension : options.getExtensions()) { 165 | result = extension.lex(src, context, this::token); 166 | if (result.matches()) { 167 | src = result.getSource(); 168 | break; 169 | } 170 | } 171 | if (result != null && result.matches()) { 172 | continue; 173 | } 174 | } 175 | 176 | // blockquote 177 | { 178 | List cap = rules.get("blockquote").exec(src); 179 | if(!cap.isEmpty()){ 180 | src = src.substring(cap.get(0).length()); 181 | context.pushToken(new BlockquoteStartToken()); 182 | token(cap.get(0).replaceAll("(?m)^ *> ?", ""), top, true, context); 183 | context.pushToken(new BlockquoteEndToken()); 184 | continue; 185 | } 186 | } 187 | 188 | // list 189 | { 190 | List cap = rules.get("list").exec(src); 191 | if(!cap.isEmpty()){ 192 | src = src.substring(cap.get(0).length()); 193 | String bull = cap.get(2); 194 | 195 | context.pushToken(new ListStartToken(bull.matches("^[0-9]+\\.$"))); 196 | boolean next = false; 197 | 198 | // Get each top-level item. 199 | cap = rules.get("item").exec(cap.get(0)); 200 | if(!cap.isEmpty()){ 201 | for(int i = 0; i < cap.size(); i++){ 202 | String item = cap.get(i); 203 | 204 | // Remove the list item's bullet 205 | // so it is seen as the nextToken token. 206 | int space = item.length(); 207 | item = item.replaceAll("^ *([*+-]|\\d+\\.) +", ""); 208 | 209 | // Outdent whatever the 210 | // list item contains. Hacky. 211 | if(item.indexOf("\n ") > 0){ 212 | space = space - item.length(); 213 | item = item.replaceAll("(?m)^ {1," + space + "}", ""); 214 | } 215 | 216 | // // Determine whether the nextToken list item belongs here. 217 | // // Backpedal if it does not belong in this list. 218 | // if(options.isSmartLists() && i != cap.size() - 1){ 219 | // Pattern p = Pattern.compile(Grammer.BULLET); 220 | // if(p.matcher(cap.get(i + 1)).find()){ 221 | // src = String.join("\n", cap.subList(i + 1, cap.size())) + src; 222 | // i = i - 1; 223 | // } 224 | // } 225 | 226 | // Determine whether item is loose or not. 227 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 228 | // for discount behavior. 229 | boolean loose = next || item.matches("\\n\\n(?!\\s*$)"); 230 | if(i != cap.size() - 1){ 231 | next = !item.isEmpty() && item.charAt(item.length() - 1) == '\n'; 232 | if(!loose) { 233 | loose = next; 234 | } 235 | } 236 | 237 | if(loose){ 238 | context.pushToken(new LooseItemStartToken()); 239 | } else { 240 | context.pushToken(new ListItemStartToken()); 241 | } 242 | 243 | token(item, false, bq, context); 244 | context.pushToken(new ListItemEndToken()); 245 | } 246 | } 247 | context.pushToken(new ListEndToken()); 248 | continue; 249 | } 250 | } 251 | 252 | // html 253 | { 254 | List cap = rules.get("html").exec(src); 255 | if(!cap.isEmpty()){ 256 | src = src.substring(cap.get(0).length()); 257 | if(options.isSanitize()){ 258 | context.pushToken(new ParagraphToken(cap.get(0))); 259 | } else { 260 | context.pushToken(new HtmlToken(cap.get(0))); 261 | } 262 | continue; 263 | } 264 | } 265 | 266 | // def 267 | if(!bq && top){ 268 | List cap = rules.get("def").exec(src); 269 | if(!cap.isEmpty()){ 270 | src = src.substring(cap.get(0).length()); 271 | context.defineLink(cap.get(1).toLowerCase(), new Link(cap.get(2), cap.get(3))); 272 | continue; 273 | } 274 | } 275 | 276 | // table (gfm) 277 | if(top){ 278 | List cap = rules.get("table").exec(src); 279 | if(!cap.isEmpty()){ 280 | src = src.substring(cap.get(0).length()); 281 | 282 | String[] headers = cap.get(1).replaceAll("^ *| *\\| *$", "").split(" *\\| *"); 283 | String[] aligns = cap.get(2).replaceAll("^ *|\\| *$", "").split(" *\\| *"); 284 | String[] rows = cap.get(3).replaceAll("(?: *\\| *)?\\n$", "").split("\\n"); 285 | 286 | List headerList = array2list(headers); 287 | 288 | List alignList = new ArrayList<>(); 289 | for (String s : aligns) { 290 | if(s.matches("^ *-+: *$")){ 291 | alignList.add("right"); 292 | } else if(s.matches("^ *:-+: *$")){ 293 | alignList.add("center"); 294 | } else if(s.matches("^ *:-+ *$")){ 295 | alignList.add("left"); 296 | } else { 297 | alignList.add(null); 298 | } 299 | } 300 | 301 | int maxColumns = Math.max(headers.length, aligns.length); 302 | 303 | List> rowList = new ArrayList<>(); 304 | for (String row : rows) { 305 | String[] columns = row.replaceAll("^ *\\| *| *\\| *$", "").split(" *\\| *"); 306 | if(maxColumns < columns.length){ 307 | maxColumns = columns.length; 308 | } 309 | rowList.add(array2list(columns)); 310 | } 311 | 312 | fillList(headerList, maxColumns, ""); 313 | fillList(alignList, maxColumns, null); 314 | for(List row: rowList){ 315 | fillList(row, maxColumns, ""); 316 | } 317 | 318 | context.pushToken(new TableToken(headerList, alignList, rowList)); 319 | continue; 320 | } 321 | } 322 | 323 | // top-level paragraph 324 | if(top){ 325 | List cap = rules.get("paragraph").exec(src); 326 | if(!cap.isEmpty()){ 327 | src = src.substring(cap.get(0).length()); 328 | if(cap.get(1).charAt(cap.get(1).length() - 1) == '\n'){ 329 | context.pushToken(new ParagraphToken(cap.get(1).substring(0, cap.get(1).length() - 1))); 330 | } else { 331 | context.pushToken(new ParagraphToken(cap.get(1))); 332 | } 333 | continue; 334 | } 335 | } 336 | 337 | // text 338 | { 339 | List cap = rules.get("text").exec(src); 340 | if(!cap.isEmpty()){ 341 | src = src.substring(cap.get(0).length()); 342 | context.pushToken(new TextToken((cap.get(0)))); 343 | continue; 344 | } 345 | } 346 | 347 | // TODO Error 348 | //println("Infinite loop on byte: " + source.charAt(0).toByte) 349 | } 350 | } 351 | 352 | public static class LexerContext { 353 | private Stack tokens = new Stack<>(); 354 | private Map links = new HashMap<>(); 355 | 356 | public void pushToken(Token token){ 357 | this.tokens.push(token); 358 | } 359 | 360 | public void defineLink(String key, Link link){ 361 | this.links.put(key, link); 362 | } 363 | 364 | public Stack getTokens() { 365 | return tokens; 366 | } 367 | 368 | public Map getLinks() { 369 | return links; 370 | } 371 | } 372 | 373 | public static class LexerResult { 374 | private Stack tokens; 375 | private Map links = new HashMap<>(); 376 | 377 | public LexerResult(Stack tokens, Map links){ 378 | this.tokens = tokens; 379 | this.links = links; 380 | } 381 | 382 | public Stack getTokens() { 383 | return tokens; 384 | } 385 | 386 | public Map getLinks() { 387 | return links; 388 | } 389 | } 390 | 391 | 392 | public static class Link { 393 | private String href; 394 | private String title; 395 | 396 | public Link(String href, String title){ 397 | this.href = href; 398 | this.title = title; 399 | } 400 | 401 | public String getHref() { 402 | return href; 403 | } 404 | 405 | public String getTitle() { 406 | return title; 407 | } 408 | } 409 | 410 | } 411 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Marked.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import org.jsoup.Jsoup; 4 | import org.jsoup.safety.Safelist; 5 | 6 | public class Marked { 7 | 8 | public static String marked(String src){ 9 | Options options = new Options(); 10 | return marked(src, options, new Renderer(options)); 11 | } 12 | 13 | public static String marked(String src, Options options){ 14 | return marked(src, options, new Renderer(options)); 15 | } 16 | 17 | public static String marked(String src, Options options, Renderer renderer){ 18 | Lexer lexer = new Lexer(options); 19 | Lexer.LexerResult result = lexer.lex(src); 20 | Parser parser = new Parser(options, renderer); 21 | String html = parser.parse(result.getTokens(), result.getLinks()); 22 | 23 | Safelist safelist = options.getSafelist(); 24 | 25 | if(safelist != null) { 26 | return Jsoup.clean(html, safelist); 27 | } else { 28 | return html; 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Options.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import io.github.gitbucket.markedj.extension.Extension; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import org.jsoup.safety.Safelist; 7 | 8 | public class Options { 9 | 10 | private boolean gfm = true; 11 | private boolean tables = true; 12 | private boolean breaks = false; 13 | private boolean sanitize = false; 14 | private String langPrefix = "lang-"; 15 | private String headerPrefix = ""; 16 | private Safelist safelist = new Safelist() 17 | .addTags( 18 | "a", "b", "blockquote", "br", "caption", "cite", "code", "col", 19 | "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", 20 | "i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong", 21 | "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", 22 | "ul", "input", "del", "hr", "svg", "path") 23 | .addAttributes("a", "href", "title") 24 | .addProtocols("a", "href", "http", "https", "mailto", "ftp", "#") 25 | .addAttributes("blockquote", "cite") 26 | .addAttributes("col", "span", "width") 27 | .addAttributes("colgroup", "span", "width") 28 | .addAttributes("img", "align", "alt", "height", "src", "title", "width") 29 | .addProtocols("img", "src", "http", "https") 30 | .addAttributes("ol", "start", "type") 31 | .addAttributes("q", "cite") 32 | .addAttributes("table", "summary", "width") 33 | .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width") 34 | .addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width") 35 | .addAttributes("ul", "type") 36 | .addAttributes("input", "type", "checked", "name", "value", "disabled") 37 | .addAttributes("svg", "class", "viewBox", "version", "width", "height", "aria-hidden") 38 | .addAttributes("path", "d") 39 | .addAttributes(":all", "id", "class", "style") 40 | ; 41 | 42 | private List extensions = new ArrayList<>(); 43 | 44 | public void addExtension (Extension extension) { 45 | extensions.add(extension); 46 | } 47 | 48 | public List getExtensions () { 49 | return extensions; 50 | } 51 | 52 | public void setGfm(boolean gfm) { 53 | this.gfm = gfm; 54 | } 55 | 56 | public void setTables(boolean tables) { 57 | this.tables = tables; 58 | } 59 | 60 | public void setBreaks(boolean breaks) { 61 | this.breaks = breaks; 62 | } 63 | 64 | public void setSanitize(boolean sanitize) { 65 | this.sanitize = sanitize; 66 | } 67 | 68 | public void setLangPrefix(String langPrefix) { 69 | this.langPrefix = langPrefix; 70 | } 71 | 72 | public void setHeaderPrefix(String headerPrefix) { 73 | this.headerPrefix = headerPrefix; 74 | } 75 | 76 | public void setSafelist(Safelist safelist){ 77 | this.safelist = safelist; 78 | } 79 | 80 | public boolean isGfm() { 81 | return gfm; 82 | } 83 | 84 | public boolean isTables() { 85 | return tables; 86 | } 87 | 88 | public boolean isBreaks() { 89 | return breaks; 90 | } 91 | 92 | public boolean isSanitize() { 93 | return sanitize; 94 | } 95 | 96 | public String getLangPrefix() { 97 | return langPrefix; 98 | } 99 | 100 | public String getHeaderPrefix() { 101 | return headerPrefix; 102 | } 103 | 104 | public Safelist getSafelist(){ 105 | return safelist; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Parser.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import io.github.gitbucket.markedj.extension.Extension; 4 | import io.github.gitbucket.markedj.rule.Rule; 5 | import io.github.gitbucket.markedj.token.*; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.Stack; 10 | 11 | public class Parser { 12 | 13 | protected Options options; 14 | protected Renderer renderer; 15 | 16 | public Parser(Options options, Renderer renderer){ 17 | this.options = options; 18 | this.renderer = renderer; 19 | } 20 | 21 | public String parse(Stack src, Map links){ 22 | Map rules; 23 | if(options.isGfm()){ 24 | if(options.isBreaks()){ 25 | rules = Grammer.INLINE_BREAKS_RULES; 26 | } else { 27 | rules = Grammer.INLINE_GFM_RULES; 28 | } 29 | } else { 30 | rules = Grammer.INLINE_RULES; 31 | } 32 | 33 | ParserContext context = new ParserContext(src, links, rules); 34 | StringBuilder out = new StringBuilder(); 35 | 36 | while(context.nextToken() != null){ 37 | out.append(tok(context)); 38 | } 39 | 40 | return out.toString(); 41 | } 42 | 43 | protected String parseText(ParserContext context){ 44 | StringBuilder body = new StringBuilder(((TextToken) context.currentToken()).getText()); 45 | while(true){ 46 | Token p = context.peekToken(); 47 | if(p == null || !p.getType().equals("TextToken")){ 48 | break; 49 | } 50 | body.append("\n" + ((TextToken) context.nextToken()).getText()); 51 | } 52 | return context.getInlineLexer().output(body.toString()); 53 | } 54 | 55 | protected String tok(ParserContext context){ 56 | switch(context.currentToken().getType()){ 57 | case "SpaceToken": { 58 | return ""; 59 | } 60 | case "HrToken": { 61 | return renderer.hr(); 62 | } 63 | case "HeadingToken": { 64 | HeadingToken t = (HeadingToken) context.currentToken(); 65 | return renderer.heading(context.getInlineLexer().output(t.getText()), t.getDepth(), t.getText()); 66 | } 67 | case "CodeToken": { 68 | CodeToken t = (CodeToken) context.currentToken(); 69 | return renderer.code(t.getCode(), t.getLang(), t.isEscaped()); 70 | } 71 | case "TableToken": { 72 | TableToken t = (TableToken) context.currentToken(); 73 | StringBuilder outCell = new StringBuilder(); 74 | StringBuilder outHeader = new StringBuilder(); 75 | StringBuilder outBody = new StringBuilder(); 76 | 77 | for(int i = 0; i < t.getHeader().size(); i++){ 78 | outCell.append(renderer.tablecell( 79 | context.getInlineLexer().output(t.getHeader().get(i)), new Renderer.TableCellFlags(true, t.getAlign().get(i)))); 80 | } 81 | outHeader.append(renderer.tablerow(outCell.toString())); 82 | 83 | for(int i = 0; i < t.getCells().size(); i++){ 84 | outCell.setLength(0); 85 | for(int j = 0; j < t.getCells().get(i).size(); j++){ 86 | outCell.append(renderer.tablecell( 87 | context.getInlineLexer().output(t.getCells().get(i).get(j)), new Renderer.TableCellFlags(false, t.getAlign().get(j)))); 88 | } 89 | outBody.append(renderer.tablerow(outCell.toString())); 90 | } 91 | return renderer.table(outHeader.toString(), outBody.toString()); 92 | } 93 | case "BlockquoteStartToken": { 94 | StringBuilder body = new StringBuilder(); 95 | while(true){ 96 | Token n = context.nextToken(); 97 | if(n == null || n.getType().equals("BlockquoteEndToken")){ 98 | break; 99 | } 100 | body.append(tok(context)); 101 | } 102 | return renderer.blockquote(body.toString()); 103 | } 104 | case "ListStartToken": { 105 | ListStartToken t = (ListStartToken) context.currentToken(); 106 | StringBuilder out = new StringBuilder(); 107 | while(true){ 108 | Token n = context.nextToken(); 109 | if(n == null || n.getType().equals("ListEndToken")){ 110 | break; 111 | } 112 | out.append(tok(context)); 113 | } 114 | return renderer.list(out.toString(), t.isOrderd()); 115 | } 116 | case "ListItemStartToken": { 117 | StringBuilder out = new StringBuilder(); 118 | while(true){ 119 | Token n = context.nextToken(); 120 | if(n == null || n.getType().equals("ListItemEndToken")){ 121 | break; 122 | } 123 | if(context.currentToken().getType().equals("TextToken")){ 124 | out.append(parseText(context)); 125 | } else { 126 | out.append(tok(context)); 127 | } 128 | } 129 | return renderer.listitem(out.toString()); 130 | } 131 | case "LooseItemStartToken": { 132 | StringBuilder out = new StringBuilder(); 133 | while(true){ 134 | Token n = context.nextToken(); 135 | if(n == null || n.getType().equals("ListItemEndToken")){ 136 | break; 137 | } 138 | out.append(tok(context)); 139 | } 140 | return renderer.listitem(out.toString()); 141 | } 142 | case "HtmlToken": { 143 | HtmlToken t = (HtmlToken) context.currentToken(); 144 | return renderer.html(context.getInlineLexer().output(t.getText())); 145 | } 146 | case "ParagraphToken": { 147 | ParagraphToken t = (ParagraphToken) context.currentToken(); 148 | return renderer.paragraph(context.getInlineLexer().output(t.getText())); 149 | } 150 | case "TextToken": { 151 | return renderer.paragraph(parseText(context)); 152 | } 153 | default: { 154 | // try to find extension 155 | String tokenType = context.currentToken().getType(); 156 | Optional extension = options.getExtensions().stream().filter(ext -> ext.handlesToken(tokenType)).findFirst(); 157 | if (extension.isPresent()) { 158 | return extension.get().parse(context, this::tok); 159 | } 160 | throw new RuntimeException("Unexpected token: " + context.currentToken()); 161 | } 162 | } 163 | } 164 | 165 | public class ParserContext { 166 | 167 | private Stack tokens = new Stack<>(); 168 | private Token token = null; 169 | private InlineLexer inline; 170 | 171 | public ParserContext(Stack src, Map links, Map rules){ 172 | // reverse 173 | tokens = new Stack<>(); 174 | while(!src.isEmpty()){ 175 | tokens.push(src.pop()); 176 | } 177 | 178 | inline = new InlineLexer(rules, links, options, renderer); 179 | } 180 | 181 | public Token currentToken(){ 182 | return token; 183 | } 184 | 185 | public Token nextToken(){ 186 | if(tokens.isEmpty()){ 187 | return null; 188 | } else { 189 | token = tokens.pop(); 190 | return token; 191 | } 192 | } 193 | 194 | public Token peekToken(){ 195 | if(tokens.isEmpty()){ 196 | return null; 197 | } else { 198 | return tokens.get(tokens.size() - 1); 199 | } 200 | } 201 | 202 | public InlineLexer getInlineLexer(){ 203 | return inline; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Renderer.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import static io.github.gitbucket.markedj.Utils.*; 4 | 5 | public class Renderer { 6 | 7 | protected Options options; 8 | 9 | public Renderer(Options options){ 10 | this.options = options; 11 | } 12 | 13 | public String code(String code, String lang, boolean escaped){ 14 | if(!isEmpty(lang)){ 15 | StringBuilder sb = new StringBuilder(); 16 | sb.append("
");
 17 |             if(escaped){
 18 |                 sb.append(code);
 19 |             } else {
 20 |                 sb.append(escape(code, true));
 21 |             }
 22 |             sb.append("\n
\n"); 23 | return sb.toString(); 24 | } else { 25 | StringBuilder sb = new StringBuilder(); 26 | sb.append("
");
 27 |             if(escaped){
 28 |                 sb.append(code);
 29 |             } else {
 30 |                 sb.append(escape(code, true));
 31 |             }
 32 |             sb.append("\n
\n"); 33 | return sb.toString(); 34 | } 35 | } 36 | 37 | public String blockquote(String quote){ 38 | return "
\n" + quote + "
\n"; 39 | } 40 | 41 | public String html(String html){ 42 | return html; 43 | } 44 | 45 | public String heading(String text, int level, String raw){ 46 | return "" + text + "\n"; 48 | } 49 | 50 | public String hr() { 51 | return "
\n"; 52 | } 53 | 54 | public String list(String body, boolean ordered){ 55 | String listType; 56 | if(ordered){ 57 | listType = "ol"; 58 | } else { 59 | listType = "ul"; 60 | } 61 | return "<" + listType + ">\n" + body + "\n"; 62 | } 63 | 64 | public String listitem(String text){ 65 | return "
  • " + text + "
  • \n"; 66 | } 67 | 68 | public String paragraph(String text){ 69 | return "

    " + text + "

    \n"; 70 | } 71 | 72 | public String table(String header, String body){ 73 | return "\n\n" + header + "\n\n" + body + "\n
    \n"; 74 | } 75 | 76 | public String tablerow(String content){ 77 | return "\n" + content + "\n"; 78 | } 79 | 80 | public String tablecell(String content, TableCellFlags flags){ 81 | String cellType; 82 | if(flags.isHeader()){ 83 | cellType = "th"; 84 | } else { 85 | cellType = "td"; 86 | } 87 | 88 | String align = flags.getAlign(); 89 | if(align != null){ 90 | return "<" + cellType + " style=\"text-align: " + align + "\">" + content + "\n"; 91 | } else { 92 | return "<" + cellType + ">" + content + "\n"; 93 | } 94 | } 95 | 96 | public String strong(String text){ 97 | return "" + text + ""; 98 | } 99 | 100 | public String em(String text){ 101 | return "" + text + ""; 102 | } 103 | 104 | public String codespan(String text){ 105 | return "" + text + ""; 106 | } 107 | 108 | public String br(){ 109 | return "
    "; 110 | } 111 | 112 | public String del(String text){ 113 | return "" + text + ""; 114 | } 115 | 116 | public String link(String href, String title, String text){ 117 | String titleAttr = ""; 118 | if(title != null){ 119 | titleAttr = " title=\"" + title + "\""; 120 | } 121 | 122 | return "
    " + text + ""; 123 | } 124 | 125 | public String image(String href, String title, String text){ 126 | String titleAttr = ""; 127 | if(title != null){ 128 | titleAttr = " title=\"" + title + "\""; 129 | } 130 | 131 | return "\"""; 132 | } 133 | 134 | public String nolink(String text){ 135 | return escape(text); 136 | } 137 | 138 | public String text(String text){ 139 | return text; 140 | } 141 | 142 | public static class TableCellFlags { 143 | private boolean header; 144 | private String align; 145 | 146 | public TableCellFlags(boolean header, String align){ 147 | this.header = header; 148 | this.align = align; 149 | } 150 | 151 | public boolean isHeader() { 152 | return header; 153 | } 154 | 155 | public String getAlign() { 156 | return align; 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/Utils.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Utils { 7 | 8 | public static String escape(String html){ 9 | return escape(html, false); 10 | } 11 | 12 | public static String escape(String html, boolean encode){ 13 | if(!encode){ 14 | html = html.replaceAll("&(?!#?\\w+;)", "&"); 15 | } else { 16 | html = html.replace("&", "&"); 17 | } 18 | return html.replace("<", "<").replace(">", ">").replace("\"", """).replace("'", "'"); 19 | } 20 | 21 | public static String or(String str1, String str2){ 22 | if(str1 != null && str1.length() > 0){ 23 | return str1; 24 | } else { 25 | return str2; 26 | } 27 | } 28 | 29 | public static boolean isEmpty(String str){ 30 | return str == null || str.length() == 0; 31 | } 32 | 33 | public static void fillList(List list, int length, String value){ 34 | while(list.size() < length){ 35 | list.add(value); 36 | } 37 | } 38 | 39 | public static List array2list(String[] array){ 40 | List list = new ArrayList<>(array.length); 41 | for(String value: array){ 42 | list.add(value); 43 | } 44 | return list; 45 | } 46 | 47 | public static String trim(String str){ 48 | if(str == null){ 49 | return null; 50 | } else { 51 | return str.trim(); 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/Extension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension; 17 | 18 | import io.github.gitbucket.markedj.Lexer; 19 | import io.github.gitbucket.markedj.Parser; 20 | import java.util.function.Function; 21 | 22 | /** 23 | * 24 | * @author t.marx 25 | */ 26 | public interface Extension { 27 | 28 | public LexResult lex(String source, final Lexer.LexerContext context, final TokenConsumer consumer); 29 | 30 | public boolean handlesToken (final String token); 31 | 32 | public String parse (Parser.ParserContext context, Function tok); 33 | 34 | public static class LexResult { 35 | private final String source; 36 | private final boolean matches; 37 | 38 | public LexResult(String source, boolean matches) { 39 | this.source = source; 40 | this.matches = matches; 41 | } 42 | 43 | public String getSource() { 44 | return source; 45 | } 46 | 47 | public boolean matches() { 48 | return matches; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/TokenConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension; 17 | 18 | import io.github.gitbucket.markedj.Lexer; 19 | 20 | /** 21 | * 22 | * @author t.marx 23 | */ 24 | @FunctionalInterface 25 | public interface TokenConsumer { 26 | public void token (String src, boolean top, boolean bq, Lexer.LexerContext context); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/DefaultGFMAlertRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | import java.util.Locale; 19 | 20 | /** 21 | * 22 | * @author t.marx 23 | */ 24 | public class DefaultGFMAlertRenderer implements GFMAlertRenderer { 25 | 26 | @Override 27 | public String render(GFMAlertOptions options, String message, GFMAlerts.Alert alert) { 28 | if (!message.startsWith("

    ")) { 29 | message = String.format("

    %s

    ", message); 30 | } 31 | 32 | return String.format("

    %s

    \n%s
    ", 33 | alert.name().toLowerCase(Locale.ENGLISH), 34 | options.getTitle(alert), 35 | message 36 | ); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlertEndToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | import io.github.gitbucket.markedj.token.Token; 19 | 20 | /** 21 | * 22 | * @author t.marx 23 | */ 24 | public class GFMAlertEndToken implements Token { 25 | 26 | public static String TYPE = "GFMAlertEndToken"; 27 | 28 | @Override 29 | public String getType() { 30 | return TYPE; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlertExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | import io.github.gitbucket.markedj.Lexer; 19 | import io.github.gitbucket.markedj.Parser; 20 | import io.github.gitbucket.markedj.extension.Extension; 21 | import io.github.gitbucket.markedj.extension.TokenConsumer; 22 | import io.github.gitbucket.markedj.rule.FindFirstRule; 23 | import io.github.gitbucket.markedj.rule.Rule; 24 | import io.github.gitbucket.markedj.token.Token; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Locale; 28 | import java.util.Map; 29 | import java.util.function.Function; 30 | 31 | /** 32 | * 33 | * @author t.marx 34 | */ 35 | public class GFMAlertExtension implements Extension { 36 | 37 | public static String EXPRESSION = "(?s)(?m)\\A^> \\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\](.+?)(^\n|\\Z)"; 38 | 39 | private static final Rule RULE = new FindFirstRule(EXPRESSION); 40 | 41 | private GFMAlertRenderer renderer = new DefaultGFMAlertRenderer(); 42 | 43 | private GFMAlertOptions options; 44 | 45 | public GFMAlertExtension () { 46 | this(new GFMAlertOptions()); 47 | } 48 | 49 | public GFMAlertExtension (GFMAlertOptions options) { 50 | this.options = options; 51 | } 52 | 53 | public void setRenderer (GFMAlertRenderer renderer) { 54 | this.renderer = renderer; 55 | } 56 | 57 | public void setOptions (GFMAlertOptions options) { 58 | this.options = options; 59 | } 60 | 61 | 62 | @Override 63 | public LexResult lex(String source, final Lexer.LexerContext context, final TokenConsumer consumer) { 64 | List cap = RULE.exec(source); 65 | boolean tokenFound = false; 66 | if (!cap.isEmpty()) { 67 | // we have detected several contiguous lines of notifications 68 | // ensure that all are of same kind 69 | String allNotificationsLines = cap.get(0); 70 | 71 | String content = cap.get(2); 72 | 73 | content = content.replaceAll("(?m)^ *> ?", ""); 74 | 75 | source = source.substring(allNotificationsLines.length()); 76 | context.pushToken(new GFMAlertStartToken(cap.get(1))); 77 | consumer.token(content, false, false, context); 78 | context.pushToken(new GFMAlertEndToken()); 79 | 80 | tokenFound = true; 81 | } 82 | return new LexResult(source, tokenFound); 83 | } 84 | 85 | @Override 86 | public boolean handlesToken(String token) { 87 | return GFMAlertStartToken.TYPE.equals(token); 88 | } 89 | 90 | @Override 91 | public String parse(Parser.ParserContext context, Function tok) { 92 | GFMAlertStartToken t = (GFMAlertStartToken) context.currentToken(); 93 | StringBuilder body = new StringBuilder(); 94 | while (true) { 95 | Token n = context.nextToken(); 96 | if (n == null || n.getType().equals(GFMAlertEndToken.TYPE)) { 97 | break; 98 | } 99 | body.append(tok.apply(context)); 100 | } 101 | return render(body.toString(), t.getNotification()); 102 | } 103 | 104 | private String render(String message, GFMAlerts.Alert alert) { 105 | return renderer.render(options, message, alert); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlertOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * 23 | * @author t.marx 24 | */ 25 | public class GFMAlertOptions { 26 | 27 | private final Map titles = new HashMap<>(); 28 | 29 | /** 30 | * Creates the options with default titles. 31 | */ 32 | public GFMAlertOptions () { 33 | titles.put(GFMAlerts.Alert.TIP, "Tip"); 34 | titles.put(GFMAlerts.Alert.NOTE, "Note"); 35 | titles.put(GFMAlerts.Alert.IMPORTANT, "Important"); 36 | titles.put(GFMAlerts.Alert.WARNING, "Warning"); 37 | titles.put(GFMAlerts.Alert.CAUTION, "Caution"); 38 | } 39 | 40 | public String getTitle (GFMAlerts.Alert alert) { 41 | return titles.get(alert); 42 | } 43 | 44 | public void setTitle (GFMAlerts.Alert alert, String title) { 45 | titles.put(alert, title); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlertRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | 19 | /** 20 | * 21 | * @author t.marx 22 | */ 23 | public interface GFMAlertRenderer { 24 | String render (final GFMAlertOptions options, final String message, GFMAlerts.Alert alert); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlertStartToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | import io.github.gitbucket.markedj.token.Token; 19 | 20 | /** 21 | * 22 | * @author t.marx 23 | */ 24 | public class GFMAlertStartToken implements Token { 25 | protected static String TYPE = "GFMAlertStartToken"; 26 | 27 | private GFMAlerts.Alert alert; 28 | 29 | public GFMAlertStartToken(String type) { 30 | this.alert = GFMAlerts.Alert.fromString(type); 31 | } 32 | 33 | @Override 34 | public String getType() { 35 | return TYPE; 36 | } 37 | 38 | public GFMAlerts.Alert getNotification() { 39 | return alert; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/extension/gfm/alert/GFMAlerts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj.extension.gfm.alert; 17 | 18 | /** 19 | * 20 | * @author t.marx 21 | */ 22 | public class GFMAlerts { 23 | 24 | public enum Alert { 25 | NOTE, TIP, IMPORTANT, WARNING, CAUTION; 26 | 27 | public static Alert fromString(String s) { 28 | switch (s) { 29 | case "TIP": 30 | return Alert.TIP; 31 | case "IMPORTANT": 32 | return Alert.IMPORTANT; 33 | case "WARNING": 34 | return Alert.WARNING; 35 | case "CAUTION": 36 | return Alert.CAUTION; 37 | default: 38 | return Alert.NOTE; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/rule/FindAllRule.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.rule; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class FindAllRule implements Rule { 10 | 11 | private Pattern pattern; 12 | 13 | public FindAllRule(String regex){ 14 | this.pattern = Pattern.compile(regex); 15 | } 16 | 17 | public List exec(String src) { 18 | try { 19 | Matcher matcher = pattern.matcher(src); 20 | List result = new ArrayList<>(); 21 | while (matcher.find()) { 22 | result.add(matcher.group(0)); 23 | } 24 | if (!result.isEmpty()) { 25 | return result; 26 | } 27 | } catch(StackOverflowError e){ 28 | // ignore 29 | } 30 | return Collections.emptyList(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/rule/FindFirstRule.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.rule; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | public class FindFirstRule implements Rule { 10 | 11 | private Pattern pattern; 12 | 13 | public FindFirstRule(String regex){ 14 | this.pattern = Pattern.compile(regex); 15 | } 16 | 17 | public List exec(String src) { 18 | try { 19 | Matcher matcher = pattern.matcher(src); 20 | if (matcher.find()) { 21 | List result = new ArrayList<>(); 22 | for (int i = 0; i <= matcher.groupCount(); i++) { 23 | result.add(matcher.group(i)); 24 | } 25 | return result; 26 | } 27 | } catch(StackOverflowError e){ 28 | // ignore 29 | } 30 | return Collections.emptyList(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/rule/NoopRule.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.rule; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | public class NoopRule implements Rule { 7 | public List exec(String src) { 8 | return Collections.emptyList(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/rule/Rule.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.rule; 2 | 3 | import java.util.List; 4 | 5 | public interface Rule { 6 | List exec(String src); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/BlockquoteEndToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class BlockquoteEndToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "BlockquoteEndToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/BlockquoteStartToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class BlockquoteStartToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "BlockquoteStartToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/CodeToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class CodeToken implements Token { 4 | 5 | private String code; 6 | private String lang; 7 | private boolean escaped; 8 | 9 | public CodeToken(String code, String lang, boolean escaped){ 10 | this.code = code; 11 | this.lang = lang; 12 | this.escaped = escaped; 13 | } 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getLang() { 20 | return lang; 21 | } 22 | 23 | public boolean isEscaped() { 24 | return escaped; 25 | } 26 | 27 | @Override 28 | public String getType() { 29 | return "CodeToken"; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/HeadingToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class HeadingToken implements Token { 4 | 5 | private int depth; 6 | private String text; 7 | 8 | public HeadingToken(int depth, String text){ 9 | this.depth = depth; 10 | this.text = text; 11 | } 12 | 13 | public int getDepth() { 14 | return depth; 15 | } 16 | 17 | public String getText() { 18 | return text; 19 | } 20 | 21 | @Override 22 | public String getType() { 23 | return "HeadingToken"; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/HrToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class HrToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "HrToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/HtmlToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class HtmlToken implements Token { 4 | 5 | private String text; 6 | 7 | public HtmlToken(String text){ 8 | this.text = text; 9 | } 10 | 11 | public String getText() { 12 | return text; 13 | } 14 | 15 | @Override 16 | public String getType() { 17 | return "HtmlToken"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/ListEndToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class ListEndToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "ListEndToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/ListItemEndToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class ListItemEndToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "ListItemEndToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/ListItemStartToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class ListItemStartToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "ListItemStartToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/ListStartToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class ListStartToken implements Token { 4 | 5 | private boolean orderd; 6 | 7 | public ListStartToken(boolean orderd){ 8 | this.orderd = orderd; 9 | } 10 | 11 | public boolean isOrderd() { 12 | return orderd; 13 | } 14 | 15 | @Override 16 | public String getType() { 17 | return "ListStartToken"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/LooseItemStartToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class LooseItemStartToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "LooseItemStartToken"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/ParagraphToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class ParagraphToken implements Token { 4 | 5 | private String text; 6 | 7 | public ParagraphToken(String text){ 8 | this.text = text; 9 | } 10 | 11 | public String getText() { 12 | return text; 13 | } 14 | 15 | @Override 16 | public String getType() { 17 | return "ParagraphToken"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/SpaceToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class SpaceToken implements Token { 4 | @Override 5 | public String getType() { 6 | return "SpaceToken"; 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/TableToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | import java.util.List; 4 | 5 | public class TableToken implements Token { 6 | 7 | private List header; 8 | private List align; 9 | private List> cells; 10 | 11 | public TableToken(List header, List align, List> cells){ 12 | this.header = header; 13 | this.align = align; 14 | this.cells = cells; 15 | } 16 | 17 | public List getHeader() { 18 | return header; 19 | } 20 | 21 | public List getAlign() { 22 | return align; 23 | } 24 | 25 | public List> getCells() { 26 | return cells; 27 | } 28 | 29 | @Override 30 | public String getType() { 31 | return "TableToken"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/TextToken.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public class TextToken implements Token { 4 | 5 | private String text; 6 | 7 | public TextToken(String text){ 8 | this.text = text; 9 | } 10 | 11 | public String getText() { 12 | return text; 13 | } 14 | 15 | @Override 16 | public String getType() { 17 | return "TextToken"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/gitbucket/markedj/token/Token.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj.token; 2 | 3 | public interface Token { 4 | 5 | public String getType(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/io/github/gitbucket/markedj/BlockquotesTest.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import static io.github.gitbucket.markedj.Resources.loadResourceAsString; 4 | import org.assertj.core.api.Assertions; 5 | 6 | import org.junit.Test; 7 | 8 | public class BlockquotesTest { 9 | 10 | @Test 11 | public void testMultipleBlockQuotes1() throws Exception { 12 | String md = loadResourceAsString("multiple_blockquotes_1.md"); 13 | String result = Marked.marked(md, new Options()); 14 | String expect = loadResourceAsString("multiple_blockquotes_1.html"); 15 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 16 | 17 | } 18 | 19 | @Test 20 | public void testMultipleBlockQuotes2() throws Exception { 21 | String md = loadResourceAsString("multiple_blockquotes_2.md"); 22 | String result = Marked.marked(md, new Options()); 23 | String expect = loadResourceAsString("multiple_blockquotes_2.html"); 24 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/io/github/gitbucket/markedj/GFMAlertsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 GitBucket. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.gitbucket.markedj; 17 | 18 | import static io.github.gitbucket.markedj.Resources.loadResourceAsString; 19 | import io.github.gitbucket.markedj.extension.gfm.alert.GFMAlertExtension; 20 | import io.github.gitbucket.markedj.extension.gfm.alert.GFMAlertOptions; 21 | import io.github.gitbucket.markedj.extension.gfm.alert.GFMAlerts; 22 | import java.util.Locale; 23 | import org.assertj.core.api.Assertions; 24 | import org.junit.Test; 25 | 26 | /** 27 | * 28 | * @author t.marx 29 | */ 30 | public class GFMAlertsTest { 31 | 32 | 33 | private Options createOptions () { 34 | Options options = new Options(); 35 | options.addExtension(new GFMAlertExtension()); 36 | return options; 37 | } 38 | 39 | @Test 40 | public void testNoteAlert() throws Exception { 41 | String md = loadResourceAsString("gfm/alerts/note.md"); 42 | String result = Marked.marked(md, createOptions()); 43 | String expect = loadResourceAsString("gfm/alerts/note.html"); 44 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 45 | } 46 | 47 | @Test 48 | public void testTipAlert() throws Exception { 49 | String md = loadResourceAsString("gfm/alerts/tip.md"); 50 | String result = Marked.marked(md, createOptions()); 51 | String expect = loadResourceAsString("gfm/alerts/tip.html"); 52 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 53 | } 54 | 55 | @Test 56 | public void testImportantAlert() throws Exception { 57 | String md = loadResourceAsString("gfm/alerts/important.md"); 58 | String result = Marked.marked(md, createOptions()); 59 | String expect = loadResourceAsString("gfm/alerts/important.html"); 60 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 61 | } 62 | 63 | @Test 64 | public void testCautionAlert() throws Exception { 65 | String md = loadResourceAsString("gfm/alerts/caution.md"); 66 | String result = Marked.marked(md, createOptions()); 67 | String expect = loadResourceAsString("gfm/alerts/caution.html"); 68 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 69 | } 70 | 71 | @Test 72 | public void testWarningAlert() throws Exception { 73 | String md = loadResourceAsString("gfm/alerts/warning.md"); 74 | String result = Marked.marked(md, createOptions()); 75 | String expect = loadResourceAsString("gfm/alerts/warning.html"); 76 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 77 | } 78 | 79 | @Test 80 | public void testWithCustomTitle() throws Exception { 81 | String md = loadResourceAsString("gfm/alerts/warning.md"); 82 | 83 | Options options = new Options(); 84 | 85 | 86 | GFMAlertOptions alertOptions = new GFMAlertOptions(); 87 | alertOptions.setTitle(GFMAlerts.Alert.WARNING, "Attention!!!"); 88 | final GFMAlertExtension gfmAlertExtension = new GFMAlertExtension(alertOptions); 89 | options.addExtension(gfmAlertExtension); 90 | 91 | String result = Marked.marked(md, options); 92 | String expect = loadResourceAsString("gfm/alerts/warning_custom_title.html"); 93 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 94 | } 95 | 96 | @Test 97 | public void testWithCustomRenderer() throws Exception { 98 | String md = loadResourceAsString("gfm/alerts/warning.md"); 99 | 100 | Options options = new Options(); 101 | final GFMAlertExtension gfmAlertExtension = new GFMAlertExtension(); 102 | gfmAlertExtension.setRenderer((alertOptions, message, alert) -> { 103 | return String.format("

    %s

    \n%s
    ", 104 | alert.name().toLowerCase(Locale.ENGLISH), 105 | alertOptions.getTitle(alert), 106 | message 107 | ); 108 | }); 109 | options.addExtension(gfmAlertExtension); 110 | 111 | String result = Marked.marked(md, options); 112 | String expect = loadResourceAsString("gfm/alerts/warning_custom_renderer.html"); 113 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 114 | } 115 | 116 | @Test 117 | public void testWithParagraphBeforeAndAfter() throws Exception { 118 | String md = loadResourceAsString("gfm/alerts/testWithParagraphBeforeAndAfter.md"); 119 | 120 | Options options = new Options(); 121 | final GFMAlertExtension gfmAlertExtension = new GFMAlertExtension(); 122 | options.addExtension(gfmAlertExtension); 123 | 124 | String result = Marked.marked(md, options); 125 | String expect = loadResourceAsString("gfm/alerts/testWithParagraphBeforeAndAfter.html"); 126 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 127 | } 128 | 129 | @Test 130 | public void testWithMultipleAlerts() throws Exception { 131 | String md = loadResourceAsString("gfm/alerts/testWithMultipleAlerts.md"); 132 | 133 | Options options = new Options(); 134 | final GFMAlertExtension gfmAlertExtension = new GFMAlertExtension(); 135 | options.addExtension(gfmAlertExtension); 136 | 137 | String result = Marked.marked(md, options); 138 | String expect = loadResourceAsString("gfm/alerts/testWithMultipleAlerts.html"); 139 | Assertions.assertThat(result).isEqualToIgnoringWhitespace(expect); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/io/github/gitbucket/markedj/MarkedTest.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Created by takezoe on 15/09/19. 13 | */ 14 | public class MarkedTest { 15 | @Test 16 | public void testQuote() throws Exception { 17 | String md = loadResourceAsString("quote.md"); 18 | String result = Marked.marked(md, new Options()); 19 | String expect = loadResourceAsString("quote.html"); 20 | assertEquals(expect, result); 21 | } 22 | 23 | @Test 24 | public void testMarked() throws Exception { 25 | String md = loadResourceAsString("ldap_settings.md"); 26 | String result = Marked.marked(md, new Options()); 27 | String expect = loadResourceAsString("ldap_settings.html"); 28 | assertEquals(expect, result); 29 | } 30 | 31 | @Test 32 | public void testMarked2() throws Exception { 33 | String md = loadResourceAsString("gitbucket.md"); 34 | String result = Marked.marked(md, new Options()); 35 | String expect = loadResourceAsString("gitbucket.html"); 36 | assertEquals(expect, result); 37 | } 38 | 39 | @Test 40 | public void testMarked3() throws Exception { 41 | String md = loadResourceAsString("wikilink.md"); 42 | String result = Marked.marked(md, new Options()); 43 | String expect = loadResourceAsString("wikilink.html"); 44 | assertEquals(expect, result); 45 | // Files.write(Paths.get("wikilink.html"), result.getBytes("UTF-8")); 46 | } 47 | 48 | 49 | @Test 50 | public void testAutolink() throws Exception { 51 | String result = Marked.marked("", new Options()); 52 | assertEquals("

    takezoe@gmail.com

    ", result); 53 | } 54 | 55 | @Test 56 | public void testReflink() throws Exception { 57 | String result = Marked.marked("[FOO], [bar][Foo], [Bar]\n\n[Foo]: http://example.com"); 58 | assertEquals("

    FOO, bar, [Bar]

    ", result); 59 | } 60 | 61 | @Test 62 | public void testIns() throws Exception { 63 | String result = Marked.marked("~~123~~"); 64 | assertEquals("

    \n 123

    ", result); 65 | } 66 | 67 | @Test 68 | public void testStrong() throws Exception { 69 | String result = Marked.marked("**123**"); 70 | assertEquals("

    123

    ", result); 71 | } 72 | 73 | @Test 74 | public void testEm() throws Exception { 75 | { 76 | String result = Marked.marked("_aa__a__aa_", new Options()); 77 | assertEquals("

    aaaaa

    ", result); 78 | } 79 | { 80 | String result = Marked.marked("*aa*o*aa*", new Options()); 81 | assertEquals("

    aaoaa

    ", result); 82 | } 83 | { 84 | String result = Marked.marked("_aa__aa_", new Options()); 85 | assertEquals("

    aa__aa

    ", result); 86 | } 87 | } 88 | 89 | @Test 90 | public void testStackoverFlow() throws Exception { 91 | Marked.marked(loadResourceAsString("stackoverflow.txt"), new Options()); 92 | } 93 | 94 | @Test 95 | public void testStackoverFlow2() throws Exception { 96 | Marked.marked(loadResourceAsString("stackoverflow2.txt"), new Options()); 97 | } 98 | 99 | @Test 100 | public void testNptable() throws Exception { 101 | String result = Marked.marked(loadResourceAsString("nptable.md"), new Options()); 102 | String expect = loadResourceAsString("nptable.html"); 103 | assertEquals(expect, result); 104 | } 105 | 106 | @Test 107 | public void testBreaks() throws Exception { 108 | { 109 | String md = "first line\nsecond line"; 110 | Options options = new Options(); 111 | //options.setBreaks(false); // default is false 112 | //options.setGfm(true); // default is true 113 | String result = Marked.marked(md, options); 114 | 115 | assertEquals("

    first line second line

    ", result); 116 | } 117 | { 118 | String md = "first line\nsecond line"; 119 | Options options = new Options(); 120 | options.setBreaks(true); 121 | //options.setGfm(true); // default is true 122 | String result = Marked.marked(md, options); 123 | 124 | assertEquals("

    first line
    \n second line

    ", result); 125 | } 126 | } 127 | 128 | @Test 129 | public void testInvalidColumnTable() throws Exception { 130 | { 131 | String result = Marked.marked(loadResourceAsString("table.md"), new Options()); 132 | assertEquals(loadResourceAsString("table.html"), result); 133 | } 134 | } 135 | 136 | @Test 137 | public void testCodeBlock() throws Exception { 138 | String result = Marked.marked( 139 | " public class HelloWorld {\n" + 140 | " }", new Options()); 141 | assertEquals( 142 | "
    public class HelloWorld {\n" +
    143 |                 "}\n" +
    144 |                 "
    ", result); 145 | } 146 | 147 | @Test 148 | public void testFencedCodeBlock() throws Exception { 149 | String md = "``` {#id .class1 attribute1=value1}\ntest\n```"; 150 | String result = Marked.marked(md, new Options()); 151 | String expect = "
    test\n
    "; 152 | assertEquals(expect, result); 153 | } 154 | 155 | @Test 156 | public void testEmptyItemOfList() throws Exception { 157 | String result = Marked.marked(loadResourceAsString("empty_item_of_list.md"), new Options()); 158 | assertEquals(loadResourceAsString("empty_item_of_list.html"), result); 159 | } 160 | 161 | @Test 162 | public void testParagraphSeparation() throws Exception { 163 | String result = Marked.marked( 164 | "Message A\n" + 165 | "- List A\n" + 166 | "- List B", new Options()); 167 | 168 | assertEquals( 169 | "

    Message A

    \n" + 170 | "
      \n" + 171 | "
    • List A
    • \n" + 172 | "
    • List B
    • \n" + 173 | "
    ", result); 174 | } 175 | 176 | @Test 177 | public void testNestedContentOfList() throws Exception { 178 | String result = Marked.marked(loadResourceAsString("nested_content_of_list.md"), new Options()); 179 | assertEquals(loadResourceAsString("nested_content_of_list.html"), result); 180 | } 181 | 182 | @Test 183 | public void testEmptyTableCell() throws Exception { 184 | String result = Marked.marked( 185 | "|ID|name|note|\n" + 186 | "|-|-|-|\n" + 187 | "|1|foo|This is foo|\n" + 188 | "|2|bar||"); 189 | 190 | 191 | assertEquals( 192 | "\n" + 193 | " \n" + 194 | " \n" + 195 | " \n" + 196 | " \n" + 197 | " \n" + 198 | " \n" + 199 | " \n" + 200 | " \n" + 201 | " \n" + 202 | " \n" + 203 | " \n" + 204 | " \n" + 205 | " \n" + 206 | " \n" + 207 | " \n" + 208 | " \n" + 209 | " \n" + 210 | " \n" + 211 | " \n" + 212 | "
    IDnamenote
    1fooThis is foo
    2bar
    ", result); 213 | } 214 | 215 | @Test 216 | public void testSanitize() throws Exception { 217 | { 218 | Options options = new Options(); 219 | options.setSanitize(true); 220 | String result = Marked.marked("bold", options); 221 | assertEquals("

    <b>bold</b><script>alert('test!');</script>

    ", result); 222 | } 223 | { 224 | Options options = new Options(); 225 | options.setSanitize(false); 226 | String result = Marked.marked("bold", options); 227 | assertEquals("

    bold

    ", result); 228 | } 229 | { 230 | Options options = new Options(); 231 | options.setSanitize(false); 232 | String result = Marked.marked("- test", options); 233 | // It's not clean but tag is closed at least. 234 | assertEquals("
      \n" + 235 | "
    • test
    • \n" + 236 | "
    ", result); 237 | } 238 | } 239 | 240 | @Test 241 | public void testHr() throws Exception { 242 | String result = Marked.marked( 243 | "This is a paragraph\n" + 244 | "\n" + 245 | "--------------------------\n" + 246 | "This is a paragraph after a horizontal rule"); 247 | 248 | assertEquals("

    This is a paragraph

    \n" + 249 | "
    \n" + 250 | "

    This is a paragraph after a horizontal rule

    ", result); 251 | } 252 | 253 | private String loadResourceAsString(String path) throws IOException { 254 | InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); 255 | try { 256 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 257 | byte[] buf = new byte[1024 * 8]; 258 | int length = 0; 259 | while((length = in.read(buf)) != -1){ 260 | out.write(buf, 0, length); 261 | } 262 | return new String(out.toByteArray(), "UTF-8"); 263 | } finally { 264 | in.close(); 265 | } 266 | } 267 | 268 | @Test 269 | public void testHardLineBreakWithSpaces() { 270 | String result = Marked.marked("Line 1 \n" + 271 | "Line 2"); 272 | assertEquals("

    Line 1
    \n" + 273 | " Line 2

    ", result); 274 | } 275 | 276 | @Test 277 | public void testHardLineBreakWithBackslash() { 278 | String result = Marked.marked("Line 1\\\n" + 279 | "Line 2"); 280 | assertEquals("

    Line 1
    \n" + 281 | " Line 2

    ", result); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/test/java/io/github/gitbucket/markedj/Resources.java: -------------------------------------------------------------------------------- 1 | package io.github.gitbucket.markedj; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class Resources { 8 | public static String loadResourceAsString(String path) throws IOException { 9 | InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); 10 | try { 11 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 12 | byte[] buf = new byte[1024 * 8]; 13 | int length = 0; 14 | while((length = in.read(buf)) != -1){ 15 | out.write(buf, 0, length); 16 | } 17 | return new String(out.toByteArray(), "UTF-8"); 18 | } finally { 19 | in.close(); 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/test/resources/empty_item_of_list.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • foo
    • 3 |
    • 4 |
    • bar
    • 5 |
    -------------------------------------------------------------------------------- /src/test/resources/empty_item_of_list.md: -------------------------------------------------------------------------------- 1 | * foo 2 | * 3 | * bar 4 | -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/caution.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Caution

    3 |

    Advises about risks or negative outcomes of certain actions.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/caution.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > Advises about risks or negative outcomes of certain actions. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/important.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Important

    3 |

    Key information users need to know to achieve their goal.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/important.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > Key information users need to know to achieve their goal. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/note.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Note

    3 |

    Useful information that users should know, even when skimming content.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/note.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > Useful information that users should know, 3 | > even when skimming content. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/testWithMultipleAlerts.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Note

    3 |

    Useful information that users should know, even when skimming content.

    4 |
    5 |
    6 |

    Tip

    7 |

    A tip!

    8 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/testWithMultipleAlerts.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > Useful information that users should know, 3 | > even when skimming content. 4 | 5 | > [!TIP] 6 | > A tip! -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/testWithParagraphBeforeAndAfter.html: -------------------------------------------------------------------------------- 1 |

    A simple paragraph before.

    2 |
    3 |

    Note

    4 |

    Useful information that users should know, even when skimming content.

    5 |
    6 |

    Another paragraph after.

    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/testWithParagraphBeforeAndAfter.md: -------------------------------------------------------------------------------- 1 | A simple paragraph before. 2 | 3 | > [!NOTE] 4 | > Useful information that users should know, 5 | > even when skimming content. 6 | 7 | Another paragraph after. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/tip.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Tip

    3 |

    Helpful advice for doing things better or more easily.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/tip.md: -------------------------------------------------------------------------------- 1 | > [!TIP] 2 | > Helpful advice for doing things better or more easily. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/warning.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Warning

    3 |

    Urgent info that needs immediate user attention to avoid problems.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/warning.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > Urgent info that needs immediate user attention to avoid problems. -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/warning_custom_renderer.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Warning

    3 |

    Urgent info that needs immediate user attention to avoid problems.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gfm/alerts/warning_custom_title.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Attention!!!

    3 |

    Urgent info that needs immediate user attention to avoid problems.

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/gitbucket.html: -------------------------------------------------------------------------------- 1 |

    GitBucket Gitter chat Build Status

    2 |

    GitBucket is the easily installable GitHub clone powered by Scala.

    3 |

    Features

    4 |

    The current version of GitBucket provides a basic features below:

    5 |
      6 |
    • Public / Private Git repository (http and ssh access)
    • 7 |
    • Repository viewer and online file editing
    • 8 |
    • Repository search (Code and Issues)
    • 9 |
    • Wiki
    • 10 |
    • Issues
    • 11 |
    • Fork / Pull request
    • 12 |
    • Email notification
    • 13 |
    • Activity timeline
    • 14 |
    • Simple user and group management with LDAP integration
    • 15 |
    • Gravatar support
    • 16 |
    • Plug-in system
    • 17 |
    18 |

    Following features are not implemented, but we will make them in the future release!

    19 |
      20 |
    • Network graph
    • 21 |
    • Statistics
    • 22 |
    • Watch / Star
    • 23 |
    24 |

    If you want to try the development version of GitBucket, see the documentation for developers at Wiki.

    25 |

    Installation

    26 |
      27 |
    1. Download latest gitbucket.war from the release page.
    2. 28 |
    3. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
    4. 29 |
    5. Access http://[hostname]:[port]/gitbucket/ using your web browser.
    6. 30 |
    31 |

    If you are using Gitbucket behind a webserver please make sure you have increased the client_max_body_size (on nginx)

    32 |

    The default administrator account is root and password is root.

    33 |

    or you can start GitBucket by java -jar gitbucket.war without servlet container. In this case, GitBucket URL is http://[hostname]:8080/. You can specify following options.

    34 |
      35 |
    • --port=[NUMBER]
    • 36 |
    • --prefix=[CONTEXTPATH]
    • 37 |
    • --host=[HOSTNAME]
    • 38 |
    • --gitbucket.home=[DATA_DIR]
    • 39 |
    40 |

    To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.

    41 |

    For Installation on Windows Server with IIS see this wiki page

    42 |

    Mac OS X

    43 |

    Installing Via Homebrew

    44 |
    $ brew install gitbucket
     45 | ==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
     46 | ######################################################################## 100.0%
     47 | ==> Caveats
     48 | Note: When using launchctl the port will be 8080.
     49 | 
     50 | To have launchd start gitbucket at login:
     51 |     ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
     52 | Then to load gitbucket now:
     53 |     launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
     54 | Or, if you don't want/need launchctl, you can just run:
     55 |     java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
     56 | ==> Summary
     57 | /usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
     58 | 
    59 |

    Manual Installation

    60 |

    On OS X, copy the gitbucket.plist file to ~/Library/LaunchAgents/

    61 |

    Run the following commands in Terminal to

    62 |
      63 |
    • start gitbucket: launchctl load ~/Library/LaunchAgents/gitbucket.plist
    • 64 |
    • stop gitbucket: launchctl unload ~/Library/LaunchAgents/gitbucket.plist
    • 65 |
    66 |

    Plug-ins

    67 |

    GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now:

    68 | 74 |

    You can find community plugins other than them at gitbucket community plugins.

    75 |

    Release Notes

    76 |

    3.6 - 30 Aug 2015

    77 |
      78 |
    • User interface Improvements: Especially, commit list, issues and pull request have been updated largely.
    • 79 |
    • Installed plugins list has been available at the system administration console.
    • 80 |
    • Pages and repository list in the sidebar have been limited and more pages and repositories link is available.
    • 81 |
    • More reference link notation in Markdown has been supported.
    • 82 |
    83 |

    3.5 - 1 Aug 2015

    84 |
      85 |
    • Octicons has been applied
    • 86 |
    • Global header has been enhanced. Now it's further similar to GitHub.
    • 87 |
    • Default compare / pull request target has been changed to the parent repository
    • 88 |
    • A lot of updates for gitbucket-gist-plugin
    • 89 |
    90 |

    3.4 - 27 Jun 2015

    91 |
      92 |
    • Declarative style plug-in definition
    • 93 |
    • New extension point to add markup render
    • 94 |
    • go-import support
    • 95 |
    96 |

    3.3 - 31 May 2015

    97 |
      98 |
    • Rich graphical diff for images
    • 99 |
    • File finder is available in the repository viewer
    • 100 |
    • Blame is displayed at the source viewer
    • 101 |
    • Remain user data and repositories even if user is disabled
    • 102 |
    • Mobile view improvement
    • 103 |
    104 |

    3.2 - 3 May 2015

    105 |
      106 |
    • Directory history button
    • 107 |
    • Compare / pull request button
    • 108 |
    • Limit of activity log
    • 109 |
    110 |

    3.1.1 - 4 Apr 2015

    111 |
      112 |
    • Rolled back H2 version to avoid version compatibility issue
    • 113 |
    • Plug-ins became possible to access ServletContext
    • 114 |
    115 |

    3.1 - 28 Mar 2015

    116 |
      117 |
    • Web APIs for Jenkins github pull-request builder
    • 118 |
    • Improved diff view
    • 119 |
    • Bump Scalatra to 2.3.1, sbt to 0.13.8
    • 120 |
    121 |

    3.0 - 3 Mar 2015

    122 |
      123 |
    • New plug-in system is available
    • 124 |
    • Connection pooling by c3p0
    • 125 |
    • New branch UI
    • 126 |
    • Compare between specified commit ids
    • 127 |
    128 |

    2.8 - 1 Feb 2015

    129 |
      130 |
    • New logo and icons
    • 131 |
    • New system setting options to control visibility
    • 132 |
    • Comment on side-by-side diff
    • 133 |
    • Information message on sign-in page
    • 134 |
    • Fork repository by group account
    • 135 |
    136 |

    2.7 - 29 Dec 2014

    137 |
      138 |
    • Comment for commit and diff
    • 139 |
    • Fix security issue in markdown rendering
    • 140 |
    • Some bug fix and improvements
    • 141 |
    142 |

    2.6 - 24 Nov 2014

    143 |
      144 |
    • Search box at issues and pull requests
    • 145 |
    • Information from administrator
    • 146 |
    • Pull request UI has been updated
    • 147 |
    • Move to TravisCI from Buildhive
    • 148 |
    • Some bug fix and improvements
    • 149 |
    150 |

    2.5 - 4 Nov 2014

    151 |
      152 |
    • New Dashboard
    • 153 |
    • Change datetime format
    • 154 |
    • Create branch from Web UI
    • 155 |
    • Task list in Markdown
    • 156 |
    • Some bug fix and improvements
    • 157 |
    158 |

    2.4.1 - 6 Oct 2014

    159 |
      160 |
    • Bug fix
    • 161 |
    162 |

    2.4 - 6 Oct 2014

    163 |
      164 |
    • New UI is applied to Issues and Pull requests
    • 165 |
    • Side-by-side diff is available
    • 166 |
    • Fix relative path problem in Markdown links and images
    • 167 |
    • Plugin System is disabled in default
    • 168 |
    • Some bug fix and improvements
    • 169 |
    170 |

    2.3 - 1 Sep 2014

    171 |
      172 |
    • Scala based plugin system
    • 173 |
    • Embedded Jetty war extraction directory moved to GITBUCKET_HOME/tmp
    • 174 |
    • Some bug fix and improvements
    • 175 |
    176 |

    2.2.1 - 5 Aug 2014

    177 |
      178 |
    • Bug fix
    • 179 |
    180 |

    2.2 - 4 Aug 2014

    181 |
      182 |
    • Plug-in system is available
    • 183 |
    • Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
    • 184 |
    • tar.gz export for repository contents
    • 185 |
    • LDAP authentication improvement (mail address became optional)
    • 186 |
    • Show news feed of a private repository to members
    • 187 |
    • Some bug fix and improvements
    • 188 |
    189 |

    2.1 - 6 Jul 2014

    190 |
      191 |
    • Upgrade to Slick 2.0 from 1.9
    • 192 |
    • Base part of the plug-in system is merged
    • 193 |
    • Many bug fix and improvements
    • 194 |
    195 |

    2.0 - 31 May 2014

    196 |
      197 |
    • Modern Github UI
    • 198 |
    • Preview in AceEditor
    • 199 |
    • Select lines by clicking line number in blob view
    • 200 |
    201 |

    1.13 - 29 Apr 2014

    202 |
      203 |
    • Direct file editing in the repository viewer using AceEditor
    • 204 |
    • File attachment for issues
    • 205 |
    • Atom feed of user activity
    • 206 |
    • Fix some bugs
    • 207 |
    208 |

    1.12 - 29 Mar 2014

    209 |
      210 |
    • SSH repository access is available
    • 211 |
    • Allow users can create and management their groups
    • 212 |
    • Git submodule support
    • 213 |
    • Close issues via commit messages
    • 214 |
    • Show repository description below the name on repository page
    • 215 |
    • Fix presentation of the source viewer
    • 216 |
    • Upgrade to sbt 0.13
    • 217 |
    • Fix some bugs
    • 218 |
    219 |

    1.11.1 - 06 Mar 2014

    220 |
      221 |
    • Bug fix
    • 222 |
    223 |

    1.11 - 01 Mar 2014

    224 |
      225 |
    • Base URL for redirection, notification and repository URL box is configurable
    • 226 |
    • Remove --https option because it's possible to substitute in the base url
    • 227 |
    • Headline anchor is available for Markdown contents such as Wiki page
    • 228 |
    • Improve H2 connectivity
    • 229 |
    • Label is available for pull requests not only issues
    • 230 |
    • Delete branch button is added
    • 231 |
    • Repository icons are updated
    • 232 |
    • Select lines of source code by URL hash like #L10 or #L10-L15 in repository viewer
    • 233 |
    • Display reference to issue from others in comment list
    • 234 |
    • Fix some bugs
    • 235 |
    236 |

    1.10 - 01 Feb 2014

    237 |
      238 |
    • Rename repository
    • 239 |
    • Transfer repository owner
    • 240 |
    • Change default data directory to HOME/.gitbucket from HOME/gitbucket to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
    • 241 |
    • Add LDAP display name attribute
    • 242 |
    • Response performance improvement
    • 243 |
    • Fix some bugs
    • 244 |
    245 |

    1.9 - 28 Dec 2013

    246 |
      247 |
    • Display GITBUCKET_HOME on the system settings page
    • 248 |
    • Fix some bugs
    • 249 |
    250 |

    1.8 - 30 Nov 2013

    251 |
      252 |
    • Add user and group deletion
    • 253 |
    • Improve pull request performance
    • 254 |
    • Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request)
    • 255 |
    • LDAP StartTLS support
    • 256 |
    • Enable hard wrapping in Markdown
    • 257 |
    • Add new some options to specify the data directory. See details in Wiki.
    • 258 |
    • Fix some bugs
    • 259 |
    260 |

    1.7 - 26 Oct 2013

    261 |
      262 |
    • Support working on Java6 in embedded Jetty mode
    • 263 |
    • Add --host option to bind specified host name in embedded Jetty mode
    • 264 |
    • Add --https=true option to force https scheme when using embedded Jetty mode at the back of https proxy
    • 265 |
    • Add full name as user property
    • 266 |
    • Change link color for absent Wiki pages
    • 267 |
    • Add ZIP download button to the repository viewer tab
    • 268 |
    • Improve ZIP exporting performance
    • 269 |
    • Expand issue and comment textarea for long text automatically
    • 270 |
    • Add conflict detection in Wiki
    • 271 |
    • Add reverting wiki page from history
    • 272 |
    • Match committer to user name by email address
    • 273 |
    • Mail notification sender is customizable
    • 274 |
    • Add link to changeset in refs comment for issues
    • 275 |
    • Fix some bugs
    • 276 |
    277 |

    1.6 - 1 Oct 2013

    278 |
      279 |
    • Web hook
    • 280 |
    • Performance improvement for pull request
    • 281 |
    • Executable war file
    • 282 |
    • Specify suitable Content-Type for downloaded files in the repository viewer
    • 283 |
    • Fix some bugs
    • 284 |
    285 |

    1.5 - 4 Sep 2013

    286 |
      287 |
    • Fork and pull request
    • 288 |
    • LDAP authentication
    • 289 |
    • Mail notification
    • 290 |
    • Add an option to turn off the gravatar support
    • 291 |
    • Add the branch tab in the repository viewer
    • 292 |
    • Encoding auto detection for the file content in the repository viewer
    • 293 |
    • Add favicon, header logo and icons for the timeline
    • 294 |
    • Specify data directory via environment variable GITBUCKET_HOME
    • 295 |
    • Fix some bugs
    • 296 |
    297 |

    1.4 - 31 Jul 2013

    298 |
      299 |
    • Group management
    • 300 |
    • Repository search for code and issues
    • 301 |
    • Display user related issues on the dashboard
    • 302 |
    • Display participants avatar of issues on the issue page
    • 303 |
    • Performance improvement for repository viewer
    • 304 |
    • Alert by milestone due date
    • 305 |
    • H2 database administration console
    • 306 |
    • Fix some bugs
    • 307 |
    308 |

    1.3 - 18 Jul 2013

    309 |
      310 |
    • Batch updating for issues
    • 311 |
    • Display assigned user on issue list
    • 312 |
    • User icon and Gravatar support
    • 313 |
    • Convert @xxxx to link to the account page
    • 314 |
    • Add copy to clipboard button for git clone URL
    • 315 |
    • Allow multi-byte characters as wiki page name
    • 316 |
    • Allow to create the empty repository
    • 317 |
    • Fix some bugs
    • 318 |
    319 |

    1.2 - 09 Jul 2013

    320 |
      321 |
    • Add activity timeline
    • 322 |
    • Bugfix for Git 1.8.1.5 or later
    • 323 |
    • Allow multi-byte characters as label
    • 324 |
    • Fix some bugs
    • 325 |
    326 |

    1.1 - 05 Jul 2013

    327 |
      328 |
    • Fix some bugs
    • 329 |
    • Upgrade to JGit 3.0
    • 330 |
    331 |

    1.0 - 04 Jul 2013

    332 |
      333 |
    • This is a first public release
    • 334 |
    335 |

    Sponsors

    336 |

    IntelliJ IDEA

    -------------------------------------------------------------------------------- /src/test/resources/gitbucket.md: -------------------------------------------------------------------------------- 1 | GitBucket [![Gitter chat](https://badges.gitter.im/takezoe/gitbucket.png)](https://gitter.im/takezoe/gitbucket) [![Build Status](https://travis-ci.org/takezoe/gitbucket.svg?branch=master)](https://travis-ci.org/takezoe/gitbucket) 2 | ========= 3 | 4 | GitBucket is the easily installable GitHub clone powered by Scala. 5 | 6 | 7 | Features 8 | -------- 9 | The current version of GitBucket provides a basic features below: 10 | 11 | - Public / Private Git repository (http and ssh access) 12 | - Repository viewer and online file editing 13 | - Repository search (Code and Issues) 14 | - Wiki 15 | - Issues 16 | - Fork / Pull request 17 | - Email notification 18 | - Activity timeline 19 | - Simple user and group management with LDAP integration 20 | - Gravatar support 21 | - Plug-in system 22 | 23 | Following features are not implemented, but we will make them in the future release! 24 | 25 | - Network graph 26 | - Statistics 27 | - Watch / Star 28 | 29 | If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki). 30 | 31 | Installation 32 | -------- 33 | 34 | 1. Download latest **gitbucket.war** from [the release page](https://github.com/takezoe/gitbucket/releases). 35 | 2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher. 36 | 3. Access **http://[hostname]:[port]/gitbucket/** using your web browser. 37 | 38 | If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nginx) 39 | 40 | The default administrator account is **root** and password is **root**. 41 | 42 | or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options. 43 | 44 | - --port=[NUMBER] 45 | - --prefix=[CONTEXTPATH] 46 | - --host=[HOSTNAME] 47 | - --gitbucket.home=[DATA_DIR] 48 | 49 | To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk. 50 | 51 | For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo) 52 | 53 | ### Mac OS X 54 | #### Installing Via Homebrew 55 | 56 | ``` 57 | $ brew install gitbucket 58 | ==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war 59 | ######################################################################## 100.0% 60 | ==> Caveats 61 | Note: When using launchctl the port will be 8080. 62 | 63 | To have launchd start gitbucket at login: 64 | ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents 65 | Then to load gitbucket now: 66 | launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist 67 | Or, if you don't want/need launchctl, you can just run: 68 | java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war 69 | ==> Summary 70 | /usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds 71 | ``` 72 | 73 | #### Manual Installation 74 | On OS X, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file to `~/Library/LaunchAgents/` 75 | 76 | Run the following commands in `Terminal` to 77 | 78 | - start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist` 79 | - stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist` 80 | 81 | Plug-ins 82 | -------- 83 | GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now: 84 | 85 | - [gitbucket-gist-plugin](https://github.com/takezoe/gitbucket-gist-plugin) 86 | - [gitbucket-announce-plugin](https://github.com/McFoggy/gitbucket-announce-plugin) 87 | - [gitbucket-h2-backup-plugin](https://github.com/McFoggy/gitbucket-h2-backup-plugin) 88 | - [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin) 89 | 90 | You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/). 91 | 92 | Release Notes 93 | -------- 94 | ### 3.6 - 30 Aug 2015 95 | - User interface Improvements: Especially, commit list, issues and pull request have been updated largely. 96 | - Installed plugins list has been available at the system administration console. 97 | - Pages and repository list in the sidebar have been limited and more pages and repositories link is available. 98 | - More reference link notation in Markdown has been supported. 99 | 100 | ### 3.5 - 1 Aug 2015 101 | - Octicons has been applied 102 | - Global header has been enhanced. Now it's further similar to GitHub. 103 | - Default compare / pull request target has been changed to the parent repository 104 | - A lot of updates for [gitbucket-gist-plugin](https://github.com/takezoe/gitbucket-gist-plugin) 105 | 106 | ### 3.4 - 27 Jun 2015 107 | - Declarative style plug-in definition 108 | - New extension point to add markup render 109 | - go-import support 110 | 111 | ### 3.3 - 31 May 2015 112 | - Rich graphical diff for images 113 | - File finder is available in the repository viewer 114 | - Blame is displayed at the source viewer 115 | - Remain user data and repositories even if user is disabled 116 | - Mobile view improvement 117 | 118 | ### 3.2 - 3 May 2015 119 | - Directory history button 120 | - Compare / pull request button 121 | - Limit of activity log 122 | 123 | ### 3.1.1 - 4 Apr 2015 124 | - Rolled back H2 version to avoid version compatibility issue 125 | - Plug-ins became possible to access ServletContext 126 | 127 | ### 3.1 - 28 Mar 2015 128 | - Web APIs for Jenkins github pull-request builder 129 | - Improved diff view 130 | - Bump Scalatra to 2.3.1, sbt to 0.13.8 131 | 132 | ### 3.0 - 3 Mar 2015 133 | - New plug-in system is available 134 | - Connection pooling by c3p0 135 | - New branch UI 136 | - Compare between specified commit ids 137 | 138 | ### 2.8 - 1 Feb 2015 139 | - New logo and icons 140 | - New system setting options to control visibility 141 | - Comment on side-by-side diff 142 | - Information message on sign-in page 143 | - Fork repository by group account 144 | 145 | ### 2.7 - 29 Dec 2014 146 | - Comment for commit and diff 147 | - Fix security issue in markdown rendering 148 | - Some bug fix and improvements 149 | 150 | ### 2.6 - 24 Nov 2014 151 | - Search box at issues and pull requests 152 | - Information from administrator 153 | - Pull request UI has been updated 154 | - Move to TravisCI from Buildhive 155 | - Some bug fix and improvements 156 | 157 | ### 2.5 - 4 Nov 2014 158 | - New Dashboard 159 | - Change datetime format 160 | - Create branch from Web UI 161 | - Task list in Markdown 162 | - Some bug fix and improvements 163 | 164 | ### 2.4.1 - 6 Oct 2014 165 | - Bug fix 166 | 167 | ### 2.4 - 6 Oct 2014 168 | - New UI is applied to Issues and Pull requests 169 | - Side-by-side diff is available 170 | - Fix relative path problem in Markdown links and images 171 | - Plugin System is disabled in default 172 | - Some bug fix and improvements 173 | 174 | ### 2.3 - 1 Sep 2014 175 | - Scala based plugin system 176 | - Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp` 177 | - Some bug fix and improvements 178 | 179 | ### 2.2.1 - 5 Aug 2014 180 | - Bug fix 181 | 182 | ### 2.2 - 4 Aug 2014 183 | - Plug-in system is available 184 | - Move to Scala 2.11, Scalatra 2.3 and Slick 2.1 185 | - tar.gz export for repository contents 186 | - LDAP authentication improvement (mail address became optional) 187 | - Show news feed of a private repository to members 188 | - Some bug fix and improvements 189 | 190 | ### 2.1 - 6 Jul 2014 191 | - Upgrade to Slick 2.0 from 1.9 192 | - Base part of the plug-in system is merged 193 | - Many bug fix and improvements 194 | 195 | ### 2.0 - 31 May 2014 196 | - Modern Github UI 197 | - Preview in AceEditor 198 | - Select lines by clicking line number in blob view 199 | 200 | ### 1.13 - 29 Apr 2014 201 | - Direct file editing in the repository viewer using AceEditor 202 | - File attachment for issues 203 | - Atom feed of user activity 204 | - Fix some bugs 205 | 206 | ### 1.12 - 29 Mar 2014 207 | - SSH repository access is available 208 | - Allow users can create and management their groups 209 | - Git submodule support 210 | - Close issues via commit messages 211 | - Show repository description below the name on repository page 212 | - Fix presentation of the source viewer 213 | - Upgrade to sbt 0.13 214 | - Fix some bugs 215 | 216 | ### 1.11.1 - 06 Mar 2014 217 | - Bug fix 218 | 219 | ### 1.11 - 01 Mar 2014 220 | - Base URL for redirection, notification and repository URL box is configurable 221 | - Remove ```--https``` option because it's possible to substitute in the base url 222 | - Headline anchor is available for Markdown contents such as Wiki page 223 | - Improve H2 connectivity 224 | - Label is available for pull requests not only issues 225 | - Delete branch button is added 226 | - Repository icons are updated 227 | - Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer 228 | - Display reference to issue from others in comment list 229 | - Fix some bugs 230 | 231 | ### 1.10 - 01 Feb 2014 232 | - Rename repository 233 | - Transfer repository owner 234 | - Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used. 235 | - Add LDAP display name attribute 236 | - Response performance improvement 237 | - Fix some bugs 238 | 239 | ### 1.9 - 28 Dec 2013 240 | - Display GITBUCKET_HOME on the system settings page 241 | - Fix some bugs 242 | 243 | ### 1.8 - 30 Nov 2013 244 | - Add user and group deletion 245 | - Improve pull request performance 246 | - Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request) 247 | - LDAP StartTLS support 248 | - Enable hard wrapping in Markdown 249 | - Add new some options to specify the data directory. See details in [Wiki](https://github.com/takezoe/gitbucket/wiki/DirectoryStructure). 250 | - Fix some bugs 251 | 252 | ### 1.7 - 26 Oct 2013 253 | - Support working on Java6 in embedded Jetty mode 254 | - Add `--host` option to bind specified host name in embedded Jetty mode 255 | - Add `--https=true` option to force https scheme when using embedded Jetty mode at the back of https proxy 256 | - Add full name as user property 257 | - Change link color for absent Wiki pages 258 | - Add ZIP download button to the repository viewer tab 259 | - Improve ZIP exporting performance 260 | - Expand issue and comment textarea for long text automatically 261 | - Add conflict detection in Wiki 262 | - Add reverting wiki page from history 263 | - Match committer to user name by email address 264 | - Mail notification sender is customizable 265 | - Add link to changeset in refs comment for issues 266 | - Fix some bugs 267 | 268 | ### 1.6 - 1 Oct 2013 269 | - Web hook 270 | - Performance improvement for pull request 271 | - Executable war file 272 | - Specify suitable Content-Type for downloaded files in the repository viewer 273 | - Fix some bugs 274 | 275 | ### 1.5 - 4 Sep 2013 276 | - Fork and pull request 277 | - LDAP authentication 278 | - Mail notification 279 | - Add an option to turn off the gravatar support 280 | - Add the branch tab in the repository viewer 281 | - Encoding auto detection for the file content in the repository viewer 282 | - Add favicon, header logo and icons for the timeline 283 | - Specify data directory via environment variable GITBUCKET_HOME 284 | - Fix some bugs 285 | 286 | ### 1.4 - 31 Jul 2013 287 | - Group management 288 | - Repository search for code and issues 289 | - Display user related issues on the dashboard 290 | - Display participants avatar of issues on the issue page 291 | - Performance improvement for repository viewer 292 | - Alert by milestone due date 293 | - H2 database administration console 294 | - Fix some bugs 295 | 296 | ### 1.3 - 18 Jul 2013 297 | - Batch updating for issues 298 | - Display assigned user on issue list 299 | - User icon and Gravatar support 300 | - Convert @xxxx to link to the account page 301 | - Add copy to clipboard button for git clone URL 302 | - Allow multi-byte characters as wiki page name 303 | - Allow to create the empty repository 304 | - Fix some bugs 305 | 306 | ### 1.2 - 09 Jul 2013 307 | - Add activity timeline 308 | - Bugfix for Git 1.8.1.5 or later 309 | - Allow multi-byte characters as label 310 | - Fix some bugs 311 | 312 | ### 1.1 - 05 Jul 2013 313 | - Fix some bugs 314 | - Upgrade to JGit 3.0 315 | 316 | ### 1.0 - 04 Jul 2013 317 | - This is a first public release 318 | 319 | Sponsors 320 | -------- 321 | [![IntelliJ IDEA](https://www.jetbrains.com/idea/docs/logo_intellij_idea.png)](https://www.jetbrains.com/idea/) 322 | -------------------------------------------------------------------------------- /src/test/resources/ldap_settings.html: -------------------------------------------------------------------------------- 1 |

    GitBucket added support for LDAP Authentication in the version 1.5. This Wiki page describes how to configure and troubleshoot it.

    2 |

    Configuration

    3 |
      4 |
    1. Login as Admin
    2. 5 |
    3. Go to Administration and click System Settings
    4. 6 |
    5. Check LDAP and enter the LDAP configuration:
    6. 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
    NameDescriptionExample Value
    LDAP HostLDAP host name192.168.32.1
    LDAP PortLDAP port389 (default 389)
    Bind DNUsername that has read access to the LDAPuid=binduser,cn=bindclients,dc=example,dc=com
    Bind PasswordPassword for Bind DN accountpassword
    Base DNTop level DN of your LDAP directory tree (used for user search)dc=example,dc=com
    User name attributeName of the LDAP attribute. This is used as the GitBucket usernameuid
    Mail address attributeEmail address of LDAP attributemail
    Enable TLSWhether to use encrypted connectionchecked
    KeystorePath to the Java keystore/etc/pki/java/cacerts
    64 |

    Keystore

    65 |

    Default system keystore is in:

    66 |
    ${JAVA_HOME}/lib/security/jssecacerts
     67 | 
    68 |

    or in:

    69 |
    ${JAVA_HOME}/lib/security/cacerts
     70 | 
    71 |

    Custom keystore can be set either in /etc/sysconfig/gitbucket by specifying the following option:

    72 |
    GITBUCKET_JVM_OPTS="-Djavax.net.ssl.trustStore=/path/to/your/keystore"
     73 | 
    74 |

    or in Gitbucket's System Settings as described above.

    75 |

    You can use the following command to add your CA certificate to the keystore:

    76 |
    $ keytool -import \
     77 | -file /path/to/your/cacert.pem \
     78 | -alias <your_cert_alias> \
     79 | -keystore /path/to/your/keystore
     80 | 
    81 |

    Older versions of Java don't know how to import certificate if it contains anything else than the certificate itself. Then it's necessary to strip everything else out like this:

    82 |
    $ cat /path/to/your/cacert.pem | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/cacert.pem
     83 | $ keytool -import -trustcacerts -alias <your_cert_alias> -file /tmp/cacert.pem -keystore /path/to/your/keystore
     84 | 
    85 |

    You can list all keys from the keystore like this:

    86 |
    $ keytool -list -keystore /path/to/your/keystore
     87 | 
    88 |

    Troubleshooting

    89 |

    LDAP debugging was enabled in the version 1.7. So if something goes wrong, you should see the error in the log file:

    90 |
    15:43:56.529 [qtp1820788751-15] INFO  service.AccountService - LDAP Authentication Failed: System LDAP authentication failed.
     91 | 15:43:37.386 [qtp1820788751-15] INFO  service.AccountService - LDAP Authentication Failed: User does not exist
     92 | 15:49:34.565 [qtp1820788751-18] INFO  service.AccountService - LDAP Authentication Failed: User LDAP Authentication Failed.
     93 | 15:41:09.370 [qtp1820788751-16] INFO  service.AccountService - LDAP Authentication Failed: Can't find mail address.
     94 | 
    95 |

    The following is the explanation for the above error messages:

    96 |
      97 |
    • System LDAP authentication failed 98 |
        99 |
      • Failed to access LDAP server by using Bind DN and Bind Password
      • 100 |
      • Wrong LDAP Host, LDAP Port, Bind DN or Bind Password
      • 101 |
    • 102 |
    • User does not exists 103 |
        104 |
      • LDAP searched the user account from Base DN but did not find it
      • 105 |
    • 106 |
    • User LDAP Authentication Failed 107 |
        108 |
      • Found user but failed to login (maybe wrong Bind Password)
      • 109 |
    • 110 |
    • Can't find mail address 111 |
        112 |
      • Found user and user authentication succeed but did not find email address (wrong Mail address attribute)
      • 113 |
    • 114 |
    -------------------------------------------------------------------------------- /src/test/resources/ldap_settings.md: -------------------------------------------------------------------------------- 1 | GitBucket added support for LDAP Authentication in the version 1.5. 2 | This Wiki page describes how to configure and troubleshoot it. 3 | 4 | ## Configuration 5 | 6 | 1. Login as Admin 7 | 2. Go to **Administration** and click **System Settings** 8 | 3. Check **LDAP** and enter the LDAP configuration: 9 | 10 | | Name | Description | Example Value | 11 | |:-----|:------------|:--------------| 12 | | LDAP Host | LDAP host name | 192.168.32.1 | 13 | | LDAP Port | LDAP port | 389 (default 389) | 14 | | Bind DN | Username that has read access to the LDAP | uid=binduser,cn=bindclients,dc=example,dc=com | 15 | | Bind Password | Password for Bind DN account | password | 16 | | Base DN | Top level DN of your LDAP directory tree (used for user search) | dc=example,dc=com | 17 | | User name attribute | Name of the LDAP attribute. This is used as the GitBucket username | uid | 18 | | Mail address attribute | Email address of LDAP attribute | mail | 19 | | Enable TLS | Whether to use encrypted connection | checked | 20 | | Keystore | Path to the Java keystore | /etc/pki/java/cacerts | 21 | 22 | 23 | ### Keystore 24 | 25 | Default system keystore is in: 26 | 27 | ``` 28 | ${JAVA_HOME}/lib/security/jssecacerts 29 | ``` 30 | 31 | or in: 32 | 33 | ``` 34 | ${JAVA_HOME}/lib/security/cacerts 35 | ``` 36 | 37 | Custom keystore can be set either in `/etc/sysconfig/gitbucket` by 38 | specifying the following option: 39 | 40 | ``` 41 | GITBUCKET_JVM_OPTS="-Djavax.net.ssl.trustStore=/path/to/your/keystore" 42 | ``` 43 | 44 | or in Gitbucket's **System Settings** as described above. 45 | 46 | You can use the following command to add your CA certificate to the 47 | keystore: 48 | 49 | ``` 50 | $ keytool -import \ 51 | -file /path/to/your/cacert.pem \ 52 | -alias \ 53 | -keystore /path/to/your/keystore 54 | ``` 55 | 56 | Older versions of Java don't know how to import certificate if it 57 | contains anything else than the certificate itself. Then it's necessary 58 | to strip everything else out like this: 59 | 60 | ``` 61 | $ cat /path/to/your/cacert.pem | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/cacert.pem 62 | $ keytool -import -trustcacerts -alias -file /tmp/cacert.pem -keystore /path/to/your/keystore 63 | ``` 64 | 65 | You can list all keys from the keystore like this: 66 | 67 | ``` 68 | $ keytool -list -keystore /path/to/your/keystore 69 | ``` 70 | 71 | 72 | ## Troubleshooting 73 | 74 | LDAP debugging was enabled in the version 1.7. So if something goes 75 | wrong, you should see the error in the log file: 76 | 77 | ``` 78 | 15:43:56.529 [qtp1820788751-15] INFO service.AccountService - LDAP Authentication Failed: System LDAP authentication failed. 79 | 15:43:37.386 [qtp1820788751-15] INFO service.AccountService - LDAP Authentication Failed: User does not exist 80 | 15:49:34.565 [qtp1820788751-18] INFO service.AccountService - LDAP Authentication Failed: User LDAP Authentication Failed. 81 | 15:41:09.370 [qtp1820788751-16] INFO service.AccountService - LDAP Authentication Failed: Can't find mail address. 82 | ``` 83 | 84 | The following is the explanation for the above error messages: 85 | 86 | * **System LDAP authentication failed** 87 | * Failed to access LDAP server by using _Bind DN_ and _Bind Password_ 88 | * Wrong _LDAP Host_, _LDAP Port_, _Bind DN_ or _Bind Password_ 89 | * **User does not exists** 90 | * LDAP searched the user account from _Base DN_ but did not find it 91 | * **User LDAP Authentication Failed** 92 | * Found user but failed to login (maybe wrong _Bind Password_) 93 | * **Can't find mail address** 94 | * Found user and user authentication succeed but did not find email address (wrong _Mail address attribute_) -------------------------------------------------------------------------------- /src/test/resources/multiple_blockquotes_1.html: -------------------------------------------------------------------------------- 1 |
    2 |

    P1. Line1. 3 | P1. Line2.

    4 |

    P2. Line1

    5 |
    6 | -------------------------------------------------------------------------------- /src/test/resources/multiple_blockquotes_1.md: -------------------------------------------------------------------------------- 1 | > P1. Line1. 2 | > P1. Line2. 3 | > 4 | > P2. Line1 5 | -------------------------------------------------------------------------------- /src/test/resources/multiple_blockquotes_2.html: -------------------------------------------------------------------------------- 1 |
    2 |

    P1. Line1. 3 | P1. Line2.

    4 |

    P2. Line1

    5 |
    6 | -------------------------------------------------------------------------------- /src/test/resources/multiple_blockquotes_2.md: -------------------------------------------------------------------------------- 1 | > P1. Line1. 2 | > P1. Line2. 3 | 4 | > P2. Line1 5 | -------------------------------------------------------------------------------- /src/test/resources/nested_content_of_list.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • 3.4 Use computed property names when creating objects with dynamic property names. 3 |
      4 |

      Why? They allow you to define all the properties of an object in one place.

      5 |
      6 |
      
       7 | function getKey(k) {
       8 |   return `a key named ${k}`;
       9 | }
      10 | 
      11 | // bad
      12 | const obj = {
      13 |   id: 5,
      14 |   name: 'San Francisco',
      15 | };
      16 | obj[getKey('enabled')] = true;
      17 | 
      18 | // good
      19 | const obj = {
      20 |   id: 5,
      21 |   name: 'San Francisco',
      22 |   [getKey('enabled')]: true,
      23 | };
      24 | 
    • 25 |
    -------------------------------------------------------------------------------- /src/test/resources/nested_content_of_list.md: -------------------------------------------------------------------------------- 1 | - [3.4](#es6-computed-properties) Use computed property names when creating objects with dynamic property names. 2 | 3 | > Why? They allow you to define all the properties of an object in one place. 4 | 5 | ```javascript 6 | 7 | function getKey(k) { 8 | return `a key named ${k}`; 9 | } 10 | 11 | // bad 12 | const obj = { 13 | id: 5, 14 | name: 'San Francisco', 15 | }; 16 | obj[getKey('enabled')] = true; 17 | 18 | // good 19 | const obj = { 20 | id: 5, 21 | name: 'San Francisco', 22 | [getKey('enabled')]: true, 23 | }; 24 | ``` -------------------------------------------------------------------------------- /src/test/resources/nptable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    6.x3.3.5
    6.x Build Status3.3.5 Build Status
    Coverity Scan Build StatusCoverity Scan Build Status
    -------------------------------------------------------------------------------- /src/test/resources/nptable.md: -------------------------------------------------------------------------------- 1 | 6.x | 3.3.5 2 | :------------: | :------------: 3 | [![6.x Build Status](https://travis-ci.org/TrinityCore/TrinityCore.svg?branch=6.x)](https://travis-ci.org/TrinityCore/TrinityCore) | [![3.3.5 Build Status](https://travis-ci.org/TrinityCore/TrinityCore.svg?branch=3.3.5)](https://travis-ci.org/TrinityCore/TrinityCore) 4 | [![Coverity Scan Build Status](https://scan.coverity.com/projects/435/badge.svg)](https://scan.coverity.com/projects/435) | [![Coverity Scan Build Status](https://scan.coverity.com/projects/4656/badge.svg)](https://scan.coverity.com/projects/4656) 5 | -------------------------------------------------------------------------------- /src/test/resources/quote.html: -------------------------------------------------------------------------------- 1 |
    2 |

    This is a quoting message That spans over several lines

    3 |

    File > Settings > Install JetBrains plugin

    4 |
    -------------------------------------------------------------------------------- /src/test/resources/quote.md: -------------------------------------------------------------------------------- 1 | > This is a quoting message 2 | > That spans over several lines 3 | 4 | > **File > Settings > Install JetBrains plugin** -------------------------------------------------------------------------------- /src/test/resources/stackoverflow.txt: -------------------------------------------------------------------------------- 1 | 2015-10-10 08:25:23,915 INFO [org.jboss.modules] (main) JBoss Modules version 1.4.3.Final 2 | 2015-10-10 08:25:24,262 INFO [org.jboss.msc] (main) JBoss MSC version 1.2.6.Final 3 | 2015-10-10 08:25:24,359 INFO [org.jboss.as] (MSC service thread 1-7) WFLYSRV0049: WildFly Full 9.0.1.Final (WildFly Core 1.0.1.Final) starting 4 | 2015-10-10 08:25:24,362 DEBUG [org.jboss.as.config] (MSC service thread 1-7) Configured system properties: 5 | awt.toolkit = sun.awt.windows.WToolkit 6 | file.encoding = MS932 7 | file.encoding.pkg = sun.io 8 | file.separator = \ 9 | java.awt.graphicsenv = sun.awt.Win32GraphicsEnvironment 10 | java.awt.printerjob = sun.awt.windows.WPrinterJob 11 | java.class.path = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\jboss-modules.jar 12 | java.class.version = 52.0 13 | java.endorsed.dirs = C:\work\Java\jdk1.8.0_51\jre\lib\endorsed 14 | java.ext.dirs = C:\work\Java\jdk1.8.0_51\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext 15 | java.home = C:\work\Java\jdk1.8.0_51\jre 16 | java.io.tmpdir = C:\Users\testuser\AppData\Local\Temp\ 17 | java.library.path = C:\work\Java\jdk1.8.0_51\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\ProgramData\Oracle\Java\javapath;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\ATI Stream\bin\x86_64;C:\Program Files (x86)\ATI Stream\bin\x86;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\ATI Technologies\ATI.ACE\Core-Static;C:\Program Files\WIDCOMM\Bluetooth Software\;C:\Program Files\WIDCOMM\Bluetooth Software\syswow64;C:\Program Files\Intel\WiFi\bin\;C:\Program Files\Common Files\Intel\WirelessCommon\;C:\Program Files\Sony\VAIO Improvement;C:\Program Files (x86)\Justsystems\JSLIB32\;C:\Program Files (x86)\Sony\VAIO Startup Setting Tool;C:\Program Files (x86)\Common Files\Roxio Shared\10.0\DLLShared\;C:\Program Files (x86)\Common Files\Roxio Shared\DLLShared\;C:\Program Files (x86)\Windows Live\Shared;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\work\Java\jdk1.8.0_51\bin;C:\work\gradle\gradle-2.3\bin;C:\WINDOWS\system32\config\systemprofile\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;C:\Program Files (x86)\nodejs\;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\Program Files (x86)\Microsoft SQL Server\130\DTS\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x86;C:\Program Files (x86)\Intel\OpenCL SDK\2.0\bin\x64;C:\Users\testuser\AppData\Roaming\npm;C:\Users\testuser\AppData\Local\atom\bin;. 18 | java.net.preferIPv4Stack = true 19 | java.runtime.name = Java(TM) SE Runtime Environment 20 | java.runtime.version = 1.8.0_51-b16 21 | java.specification.name = Java Platform API Specification 22 | java.specification.vendor = Oracle Corporation 23 | java.specification.version = 1.8 24 | java.util.logging.manager = org.jboss.logmanager.LogManager 25 | java.vendor = Oracle Corporation 26 | java.vendor.url = http://java.oracle.com/ 27 | java.vendor.url.bug = http://bugreport.sun.com/bugreport/ 28 | java.version = 1.8.0_51 29 | java.vm.info = mixed mode 30 | java.vm.name = Java HotSpot(TM) 64-Bit Server VM 31 | java.vm.specification.name = Java Virtual Machine Specification 32 | java.vm.specification.vendor = Oracle Corporation 33 | java.vm.specification.version = 1.8 34 | java.vm.vendor = Oracle Corporation 35 | java.vm.version = 25.51-b03 36 | javax.management.builder.initial = org.jboss.as.jmx.PluggableMBeanServerBuilder 37 | javax.xml.datatype.DatatypeFactory = __redirected.__DatatypeFactory 38 | javax.xml.parsers.DocumentBuilderFactory = __redirected.__DocumentBuilderFactory 39 | javax.xml.parsers.SAXParserFactory = __redirected.__SAXParserFactory 40 | javax.xml.stream.XMLEventFactory = __redirected.__XMLEventFactory 41 | javax.xml.stream.XMLInputFactory = __redirected.__XMLInputFactory 42 | javax.xml.stream.XMLOutputFactory = __redirected.__XMLOutputFactory 43 | javax.xml.transform.TransformerFactory = __redirected.__TransformerFactory 44 | javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema = __redirected.__SchemaFactory 45 | javax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom = __redirected.__XPathFactory 46 | jboss.home.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final 47 | jboss.host.name = vaioz 48 | jboss.modules.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\modules 49 | jboss.modules.system.pkgs = org.jboss.byteman 50 | jboss.node.name = vaioz 51 | jboss.qualified.host.name = vaioz 52 | jboss.server.base.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone 53 | jboss.server.config.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\configuration 54 | jboss.server.data.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\data 55 | jboss.server.deploy.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\data\content 56 | jboss.server.log.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\log 57 | jboss.server.name = vaioz 58 | jboss.server.persist.config = true 59 | jboss.server.temp.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\tmp 60 | line.separator = 61 | 62 | logging.configuration = file:C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\configuration/logging.properties 63 | module.path = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\modules 64 | org.jboss.boot.log.file = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\log\server.log 65 | org.jboss.resolver.warning = true 66 | org.xml.sax.driver = __redirected.__XMLReaderFactory 67 | os.arch = amd64 68 | os.name = Windows 8.1 69 | os.version = 6.3 70 | path.separator = ; 71 | program.name = standalone.bat 72 | sun.arch.data.model = 64 73 | sun.boot.class.path = C:\work\Java\jdk1.8.0_51\jre\lib\resources.jar;C:\work\Java\jdk1.8.0_51\jre\lib\rt.jar;C:\work\Java\jdk1.8.0_51\jre\lib\sunrsasign.jar;C:\work\Java\jdk1.8.0_51\jre\lib\jsse.jar;C:\work\Java\jdk1.8.0_51\jre\lib\jce.jar;C:\work\Java\jdk1.8.0_51\jre\lib\charsets.jar;C:\work\Java\jdk1.8.0_51\jre\lib\jfr.jar;C:\work\Java\jdk1.8.0_51\jre\classes 74 | sun.boot.library.path = C:\work\Java\jdk1.8.0_51\jre\bin 75 | sun.cpu.endian = little 76 | sun.cpu.isalist = amd64 77 | sun.desktop = windows 78 | sun.io.unicode.encoding = UnicodeLittle 79 | sun.java.command = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\jboss-modules.jar -mp C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\modules org.jboss.as.standalone -Djboss.home.dir=C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final 80 | sun.java.launcher = SUN_STANDARD 81 | sun.jnu.encoding = MS932 82 | sun.management.compiler = HotSpot 64-Bit Tiered Compilers 83 | sun.os.patch.level = 84 | sun.stderr.encoding = ms932 85 | sun.stdout.encoding = ms932 86 | user.country = JP 87 | user.dir = C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\bin 88 | user.home = C:\Users\testuser 89 | user.language = ja 90 | user.name = testuser 91 | user.script = 92 | user.timezone = Asia/Tokyo 93 | user.variant = 94 | 2015-10-10 08:25:24,364 DEBUG [org.jboss.as.config] (MSC service thread 1-7) VM Arguments: -Dprogram.name=standalone.bat -Xms64M -Xmx512M -XX:MaxPermSize=256M -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Dorg.jboss.boot.log.file=C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\log\server.log -Dlogging.configuration=file:C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\configuration/logging.properties 95 | 2015-10-10 08:25:25,994 INFO [org.jboss.as.controller.management-deprecated] (ServerService Thread Pool -- 14) WFLYCTL0028: Attribute 'job-repository-type' in the resource at address '/subsystem=batch' is deprecated, and may be removed in future version. See the attribute description in the output of the read-resource-description operation to learn more about the deprecation. 96 | 2015-10-10 08:25:25,995 INFO [org.jboss.as.controller.management-deprecated] (ServerService Thread Pool -- 21) WFLYCTL0028: Attribute 'enabled' in the resource at address '/subsystem=datasources/data-source=ExampleDS' is deprecated, and may be removed in future version. See the attribute description in the output of the read-resource-description operation to learn more about the deprecation. 97 | 2015-10-10 08:25:26,063 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0039: Creating http management service using socket-binding (management-http) 98 | 2015-10-10 08:25:26,090 INFO [org.xnio] (MSC service thread 1-1) XNIO version 3.3.1.Final 99 | 2015-10-10 08:25:26,100 INFO [org.xnio.nio] (MSC service thread 1-1) XNIO NIO Implementation Version 3.3.1.Final 100 | 2015-10-10 08:25:26,167 INFO [org.wildfly.extension.io] (ServerService Thread Pool -- 37) WFLYIO001: Worker 'default' has auto-configured to 8 core threads with 64 task threads based on your 4 available processors 101 | 2015-10-10 08:25:26,206 INFO [org.jboss.as.connector] (MSC service thread 1-5) WFLYJCA0009: Starting JCA Subsystem (IronJacamar 1.2.4.Final) 102 | 2015-10-10 08:25:26,220 INFO [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 38) WFLYCLINF0001: Activating Infinispan subsystem. 103 | 2015-10-10 08:25:26,238 INFO [org.jboss.as.security] (ServerService Thread Pool -- 53) WFLYSEC0002: Activating Security Subsystem 104 | 2015-10-10 08:25:26,251 WARN [org.jboss.as.txn] (ServerService Thread Pool -- 54) WFLYTX0013: Node identifier property is set to the default value. Please make sure it is unique. 105 | 2015-10-10 08:25:26,254 INFO [org.jboss.as.webservices] (ServerService Thread Pool -- 56) WFLYWS0002: Activating WebServices Extension 106 | 2015-10-10 08:25:26,268 INFO [org.jboss.as.naming] (ServerService Thread Pool -- 46) WFLYNAM0001: Activating Naming Subsystem 107 | 2015-10-10 08:25:26,295 INFO [org.jboss.as.jsf] (ServerService Thread Pool -- 44) WFLYJSF0007: Activated the following JSF Implementations: [main] 108 | 2015-10-10 08:25:26,318 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 33) WFLYJCA0004: Deploying JDBC-compliant driver class org.h2.Driver (version 1.3) 109 | 2015-10-10 08:25:26,365 INFO [org.jboss.as.security] (MSC service thread 1-8) WFLYSEC0001: Current PicketBox version=4.9.2.Final 110 | 2015-10-10 08:25:26,405 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 55) WFLYUT0003: Undertow 1.2.9.Final starting 111 | 2015-10-10 08:25:26,406 INFO [org.wildfly.extension.undertow] (MSC service thread 1-4) WFLYUT0003: Undertow 1.2.9.Final starting 112 | 2015-10-10 08:25:26,628 INFO [org.jboss.remoting] (MSC service thread 1-1) JBoss Remoting version 4.0.9.Final 113 | 2015-10-10 08:25:26,748 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-1) WFLYJCA0018: Started Driver service with driver-name = h2 114 | 2015-10-10 08:25:26,752 INFO [org.jboss.as.naming] (MSC service thread 1-2) WFLYNAM0003: Starting Naming Service 115 | 2015-10-10 08:25:26,756 INFO [org.jboss.as.mail.extension] (MSC service thread 1-7) WFLYMAIL0001: Bound mail session [java:jboss/mail/Default] 116 | 2015-10-10 08:25:26,858 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool -- 55) WFLYUT0014: Creating file handler for path C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final/welcome-content 117 | 2015-10-10 08:25:26,871 INFO [org.wildfly.extension.undertow] (MSC service thread 1-4) WFLYUT0012: Started server default-server. 118 | 2015-10-10 08:25:26,992 INFO [org.wildfly.extension.undertow] (MSC service thread 1-8) WFLYUT0018: Host default-host starting 119 | 2015-10-10 08:25:27,123 INFO [org.wildfly.extension.undertow] (MSC service thread 1-4) WFLYUT0006: Undertow HTTP listener default listening on /127.0.0.1:8080 120 | 2015-10-10 08:25:27,270 INFO [org.jboss.as.connector.subsystems.datasources] (MSC service thread 1-3) WFLYJCA0001: Bound data source [java:jboss/datasources/ExampleDS] 121 | 2015-10-10 08:25:27,283 INFO [org.jboss.as.server.deployment.scanner] (MSC service thread 1-8) WFLYDS0013: Started FileSystemDeploymentService for directory C:\work\wildfly-9.0.1.Final\wildfly-9.0.1.Final\standalone\deployments 122 | 2015-10-10 08:25:27,608 INFO [org.jboss.ws.common.management] (MSC service thread 1-5) JBWS022052: Starting JBoss Web Services - Stack CXF Server 5.0.0.Final 123 | 2015-10-10 08:25:27,807 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management 124 | 2015-10-10 08:25:27,810 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990 125 | 2015-10-10 08:25:27,811 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 9.0.1.Final (WildFly Core 1.0.1.Final) started in 4401ms - Started 203 of 379 services (210 services are lazy, passive or on-demand) -------------------------------------------------------------------------------- /src/test/resources/stackoverflow2.txt: -------------------------------------------------------------------------------- 1 | ["/path/to/rails/lib/active_record/associations/has_many_association.rb:80:in `cached_counter_attribute_name'", 2 | "/path/to/rails/lib/active_record/associations/has_many_association.rb:103:in `inverse_updates_counter_cache?'", 3 | "/path/to/rails/lib/active_record/associations/has_many_association.rb:113:in `delete_records'", 4 | "/path/to/rails/lib/active_record/associations/collection_association.rb:493:in `remove_records'", 5 | "/path/to/rails/lib/active_record/associations/collection_association.rb:486:in `block in delete_or_destroy'", 6 | "/path/to/rails/lib/active_record/associations/collection_association.rb:152:in `block in transaction'", 7 | "/path/to/rails/lib/active_record/connection_adapters/abstract/database_statements.rb:200:in `transaction'", 8 | "/path/to/rails/lib/active_record/transactions.rb:209:in `transaction'", 9 | "/path/to/rails/lib/active_record/associations/collection_association.rb:151:in `transaction'", 10 | "/path/to/rails/lib/active_record/associations/collection_association.rb:486:in `delete_or_destroy'", 11 | "/path/to/rails/lib/active_record/associations/collection_association.rb:247:in `destroy'", 12 | "/path/to/rails/lib/active_record/associations/collection_association.rb:170:in `destroy_all'", 13 | "/path/to/rails/lib/active_record/associations/has_many_association.rb:26:in `handle_dependency'", 14 | "/path/to/rails/lib/active_record/associations/builder/association.rb:97:in `has_many_dependent_for_ary_royalty_details'", 15 | "/path/to/rails/lib/active_support/callbacks.rb:377:in `_run__901941141290416734__destroy__callbacks'", 16 | "/path/to/rails/lib/active_support/callbacks.rb:80:in `run_callbacks'", 17 | "/path/to/rails/lib/active_record/callbacks.rb:289:in `destroy'", 18 | "/path/to/rails/lib/active_record/transactions.rb:265:in `block in destroy'", 19 | "/path/to/rails/lib/active_record/transactions.rb:326:in `block in with_transaction_returning_status'", 20 | "/path/to/rails/lib/active_record/connection_adapters/abstract/database_statements.rb:200:in `transaction'", 21 | "/path/to/rails/lib/active_record/transactions.rb:209:in `transaction'", 22 | "/path/to/rails/lib/active_record/transactions.rb:323:in `with_transaction_returning_status'", 23 | "/path/to/rails/lib/active_record/transactions.rb:265:in `destroy'", 24 | "/path/to/rails/lib/active_record/connection_adapters/abstract/database_statements.rb:202:in `block in transaction'", 25 | "/path/to/rails/lib/active_record/connection_adapters/abstract/database_statements.rb:210:in `within_new_transaction'", 26 | "/path/to/rails/lib/active_record/connection_adapters/abstract/database_statements.rb:202:in `transaction'", 27 | "/path/to/rails/lib/active_record/transactions.rb:209:in `transaction'", 28 | "/path/to/rails/lib/action_controller/metal/implicit_render.rb:4:in `send_action'", 29 | "/path/to/rails/lib/abstract_controller/base.rb:189:in `process_action'", 30 | "/path/to/rails/lib/action_controller/metal/rendering.rb:10:in `process_action'", 31 | "/path/to/rails/lib/abstract_controller/callbacks.rb:18:in `block in process_action'", 32 | "/path/to/rails/lib/active_support/callbacks.rb:423:in `_run__1216070983823669037__process_action__callbacks'", 33 | "/path/to/rails/lib/active_support/callbacks.rb:80:in `run_callbacks'", -------------------------------------------------------------------------------- /src/test/resources/table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    aaaa
    bbbbcc
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
    TablesAreCool
    col 1 isleft-aligned$1600
    col 2 iscentered$12
    col 3 isright-aligned$1
    -------------------------------------------------------------------------------- /src/test/resources/table.md: -------------------------------------------------------------------------------- 1 | | aa | aa | 2 | | -----------|-----------| 3 | | bb | bb | cc 4 | 5 | 6 | | Tables | Are | Cool | 7 | |:----------|:-------------:| 8 | | col 1 is | left-aligned | $1600 | 9 | | col 2 is | centered | $12 | 10 | | col 3 is | right-aligned | $1 | -------------------------------------------------------------------------------- /src/test/resources/wikilink.html: -------------------------------------------------------------------------------- 1 |

    Welcome to the GitBucket wiki!

    2 |
      3 |
    • [[<script>Enabling</script> SSH access to repository]]
    • 4 |
    • [[LDAP Authentication Settings]]
    • 5 |
    • [[Installation on IIS and Helicontech Zoo]]
    • 6 |
    • [[Reverse proxy with Nginx]]
    • 7 |
    • [[Reverse proxy with Apache]]
    • 8 |
    • [[Setup Jenkins GitHub pull request builder plugin]]
    • 9 |
    • [[Connect to H2 database]]
    • 10 |
    • [[How to Close/Reference issues & pull request]]
    • 11 |
    • [[Backup]]
    • 12 |
    • [[API/WebHook]]
    • 13 |
    14 |

    If you are interested in GitBucket development, you can find documents for GitBucket development at: https://github.com/takezoe/gitbucket/blob/master/doc/readme.md

    -------------------------------------------------------------------------------- /src/test/resources/wikilink.md: -------------------------------------------------------------------------------- 1 | Welcome to the GitBucket wiki! 2 | ==== 3 | * [[ SSH access to repository]] 4 | * [[LDAP Authentication Settings]] 5 | * [[Installation on IIS and Helicontech Zoo]] 6 | * [[Reverse proxy with Nginx]] 7 | * [[Reverse proxy with Apache]] 8 | * [[Setup Jenkins GitHub pull request builder plugin]] 9 | * [[Connect to H2 database]] 10 | * [[How to Close/Reference issues & pull request]] 11 | * [[Backup]] 12 | * [[API/WebHook]] 13 | 14 | If you are interested in GitBucket development, you can find documents for GitBucket development at: https://github.com/takezoe/gitbucket/blob/master/doc/readme.md --------------------------------------------------------------------------------