├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── pom.xml └── src └── main └── java └── org └── editorconfig ├── EditorConfigCLI.java └── core ├── DefaultParserCallback.java ├── EditorConfig.java ├── EditorConfigException.java ├── ParserCallback.java ├── ParsingException.java ├── VersionException.java └── package-info.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | # CMake stuff 15 | CMakeCache.txt 16 | CMakeFiles 17 | Makefile 18 | cmake_install.cmake 19 | install_manifest.txt 20 | CTestTestfile.cmake 21 | Testing/ 22 | 23 | # Eclipse 24 | .project 25 | .classpath 26 | .settings/ 27 | bin/ 28 | 29 | # IDEA 30 | .idea 31 | *.ipr 32 | *.iml 33 | *.iws 34 | 35 | # NetBeans 36 | nb-configuration.xml 37 | 38 | # KDE 39 | .directory 40 | 41 | # OSX 42 | .DS_Store 43 | 44 | # Maven 45 | target/ 46 | pom.xml.tag 47 | pom.xml.releaseBackup 48 | pom.xml.versionsBackup 49 | pom.xml.next 50 | release.properties -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "editorconfig-core-test"] 2 | path = editorconfig-core-test 3 | url = https://github.com/editorconfig/editorconfig-core-test 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | sudo: false 4 | 5 | jdk: 6 | - openjdk8 7 | - oraclejdk8 8 | - openjdk11 9 | 10 | cache: 11 | directories: 12 | - $HOME/.m2/repository 13 | 14 | install: 15 | - cmake --version 16 | - git submodule init 17 | - git submodule update 18 | 19 | script: 20 | - mvn -Pcore-test clean install 21 | - cmake . 22 | - ctest -E "(max_property_|max_section_name_|escaped_octothorpe_in_property)" . 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is used for testing only 2 | 3 | # To perform the test, run `cmake .` at the root of the project tree followed 4 | # by ctest . 5 | 6 | cmake_minimum_required(VERSION 2.6) 7 | 8 | # Do not check any compiler 9 | project(editorconfig-core-java NONE) 10 | 11 | find_package(Java 1.6) 12 | 13 | if(NOT JAVA_FOUND) 14 | message(FATAL_ERROR 15 | "Java not found. If you have Java installed, please run: 16 | cmake -DJAVA_HOME=/path/to/java .") 17 | endif() 18 | 19 | enable_testing() 20 | set(EDITORCONFIG_CMD ${Java_JAVA_EXECUTABLE} -jar ${PROJECT_SOURCE_DIR}/target/editorconfig-core.jar) 21 | add_subdirectory(editorconfig-core-test) -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EditorConfig Java Binding 2 | 3 | This directory is for [EditorConfig][] Core Java Binding. 4 | 5 | ## EditorConfig Project 6 | 7 | EditorConfig makes it easy to maintain the correct coding style when switching 8 | between different text editors and between different projects. The 9 | EditorConfig project maintains a file format and plugins for various text 10 | editors which allow this file format to be read and used by those editors. For 11 | information on the file format and supported text editors, see the 12 | [EditorConfig website][EditorConfig]. 13 | 14 | ## How to use EditorConfig Core in Java 15 | 16 | Add the `editorconfig-core` dependency to your `pom.xml` file: 17 | 18 | ```xml 19 | ... 20 | 21 | 22 | org.editorconfig 23 | editorconfig-core 24 | 25 | 26 | 27 | ``` 28 | 29 | A basic example: 30 | 31 | ```java 32 | EditorConfig ec = new EditorConfig(); 33 | List l = null; 34 | try { 35 | l = ec.getProperties("/home/user/src/editorconfig-core-py/a.py"); 36 | } catch(EditorConfigException e) { 37 | System.out.println(e); 38 | System.exit(1); 39 | } 40 | 41 | for(int i = 0; i < l.size(); ++i) { 42 | System.out.println(l.get(i).getKey() + "=" + l.get(i).getVal()); 43 | } 44 | ``` 45 | There is an [online documentation][] for API details. 46 | 47 | ## Build EditorConfig Core Java librarary 48 | 49 | Prerequisistes: Java 6, [Maven][], Git, cmake 2.6+ (optional for tests) 50 | 51 | Checkout the code 52 | 53 | git clone https://github.com/editorconfig/editorconfig-core-java.git 54 | 55 | Build the library with [Maven][]: 56 | 57 | cd editorconfig-core-java 58 | mvn clean install 59 | 60 | The built jar file is in the `target` directory. 61 | 62 | ## Run the testsuite 63 | 64 | First make sure that the submodule is initialized: 65 | 66 | cd /path/to/editorconfig-core-java 67 | git submodule init 68 | git submodule update 69 | 70 | Then prepare and run the tests using `cmake`: 71 | 72 | cmake . 73 | ctest . 74 | 75 | ## How to Contribute 76 | 77 | Pull requests are welcome on [GitHub](https://github.com/editorconfig/editorconfig-core-java). 78 | 79 | 80 | ## License 81 | 82 | All source files of the Java binding are distributed under the Apache license. See 83 | LICENSE for details. 84 | 85 | Copyright (C) 2012-2013, EditorConfig Team 86 | 87 | [Maven]: https://maven.apache.org 88 | [EditorConfig]: https://editorconfig.org 89 | [online documentation]: http://javadocs.editorconfig.org 90 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | org.editorconfig 4 | editorconfig-core 5 | 0.12.1.Final-SNAPSHOT 6 | 7 | EditorConfig Core 8 | https://editorconfig.org 9 | 2014 10 | 11 | 12 | Apache License, Version 2.0 13 | http://www.apache.org/licenses/LICENSE-2.0.txt 14 | repo 15 | 16 | 17 | 18 | 19 | 20 | EditorConfig Group 21 | https://groups.google.com/forum/#!forum/editorconfig 22 | https://groups.google.com/forum/#!forum/editorconfig 23 | https://groups.google.com/forum/#!forum/editorconfig 24 | 25 | 26 | 27 | 28 | scm:git:git@github.com:editorconfig/editorconfig-core-java.git 29 | scm:git:git@github.com:editorconfig/editorconfig-core-java.git 30 | https://github.com/editorconfig/editorconfig-core-java 31 | head 32 | 33 | 34 | GitHub Issues 35 | https://github.com/editorconfig/editorconfig-core-java/issues 36 | 37 | 38 | 39 | 40 | 41 | UTF-8 42 | UTF-8 43 | 44 | 45 | 1.7 46 | 1.7 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-clean-plugin 56 | 2.6.1 57 | 58 | 59 | 60 | ${project.basedir} 61 | 62 | Testing/** 63 | CMakeFiles/** 64 | cmake_install.cmake 65 | CMakeCache.txt 66 | CTestTestfile.cmake 67 | Makefile 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-compiler-plugin 76 | 3.3 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-jar-plugin 85 | 2.5 86 | 87 | 88 | 89 | org.editorconfig.EditorConfigCLI 90 | 91 | 92 | 93 | 94 | 95 | org.sonatype.plugins 96 | nexus-staging-maven-plugin 97 | 1.6.5 98 | true 99 | 100 | ossrh 101 | https://oss.sonatype.org/ 102 | true 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | core-test 112 | 113 | ${project.artifactId} 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/EditorConfigCLI.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig; 2 | 3 | import org.editorconfig.core.EditorConfig; 4 | 5 | import java.io.PrintStream; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Dennis.Ushakov 11 | */ 12 | public class EditorConfigCLI { 13 | public static void main(String[] args) throws Exception { 14 | List filePaths = new ArrayList(); 15 | String configFilename = null; 16 | String version = EditorConfig.VERSION; 17 | for (int i = 0; i < args.length; i++) { 18 | String arg = args[i]; 19 | if ("-v".equals(arg) || "--version".equals(arg)) { 20 | System.out.println("EditorConfig Java Version " + version); 21 | System.exit(0); 22 | } 23 | if ("-h".equals(arg) || "--help".equals(arg)) { 24 | printUsage(false); 25 | } 26 | if ("-b".equals(arg)) { 27 | if (i + 1 < args.length) { 28 | version = args[++i]; 29 | continue; 30 | } else { 31 | printUsage(true); 32 | } 33 | } 34 | if ("-f".equals(arg)) { 35 | if (i + 1 < args.length) { 36 | configFilename = args[++i]; 37 | continue; 38 | } else { 39 | printUsage(true); 40 | } 41 | } 42 | filePaths.add(arg); 43 | } 44 | if (filePaths.isEmpty()) { 45 | printUsage(true); 46 | } 47 | for (String filePath : filePaths) { 48 | List properties = new EditorConfig(configFilename, version).getProperties(filePath); 49 | if (filePaths.size() > 1) { 50 | System.out.println("[" + filePath + "]"); 51 | } 52 | for (EditorConfig.OutPair property : properties) { 53 | System.out.println(property.getKey() + "=" + property.getVal()); 54 | } 55 | } 56 | } 57 | 58 | private static void printUsage(boolean error) { 59 | PrintStream out = error ? System.err : System.out; 60 | out.println("[OPTIONS] filename"); 61 | out.println("-f Specify conf filename other than \".editorconfig\"."); 62 | out.println("-b Specify version (used by devs to test compatibility)."); 63 | out.println("-h OR --help Print this help message."); 64 | out.println("-v OR --version Display version information."); 65 | System.exit(error ? 2 : 0); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/DefaultParserCallback.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Default callback which does nothing and doesn't influence .editorconfig handling. 7 | */ 8 | public class DefaultParserCallback implements ParserCallback { 9 | 10 | @Override 11 | public boolean processFile(File file) { 12 | return true; 13 | } 14 | 15 | @Override 16 | public boolean processDir(File dir) { 17 | return true; 18 | } 19 | 20 | @Override 21 | public boolean processEditorConfig(File configFile) throws EditorConfigException { 22 | return true; 23 | } 24 | 25 | @Override 26 | public boolean processLine(String line) throws EditorConfigException { 27 | return true; 28 | } 29 | 30 | @Override 31 | public boolean processOption(String key, String value) throws EditorConfigException { 32 | return true; 33 | } 34 | 35 | @Override 36 | public void processingFinished(File file) { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/EditorConfig.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | import java.io.*; 4 | import java.util.*; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | import java.util.regex.PatternSyntaxException; 8 | 9 | /** 10 | * EditorConfig handler 11 | * 12 | * @author Dennis.Ushakov 13 | */ 14 | public class EditorConfig { 15 | private static boolean DEBUG = System.getProperty("editorconfig.debug") != null; 16 | 17 | public static String VERSION = "0.12.0-final"; 18 | 19 | private static final Pattern SECTION_PATTERN = Pattern.compile("\\s*\\[(([^#;]|\\\\#|\\\\;)+)]" + 20 | ".*"); // Python match searches from the line start 21 | private static final int HEADER = 1; 22 | 23 | private static final Pattern OPTION_PATTERN = Pattern.compile("\\s*([^:=\\s][^:=]*)\\s*[:=]\\s*(.*)"); 24 | private static final int OPTION = 1; 25 | private static final int VAL = 2; 26 | 27 | private static final Pattern OPENING_BRACES = Pattern.compile("(?:^|[^\\\\])\\{"); 28 | private static final Pattern CLOSING_BRACES = Pattern.compile("(?:^|[^\\\\])}"); 29 | 30 | private final String configFilename; 31 | private final String version; 32 | 33 | /** 34 | * Creates EditorConfig handler with default configuration filename (.editorconfig) and 35 | * version {@link EditorConfig#VERSION} 36 | */ 37 | public EditorConfig() { 38 | this(".editorconfig", VERSION); 39 | } 40 | 41 | /** 42 | * Creates EditorConfig handler with specified configuration filename and version. 43 | * Used mostly for debugging/testing. 44 | * @param configFilename configuration file name to be searched for instead of .editorconfig 45 | * @param version required version 46 | */ 47 | public EditorConfig(String configFilename, String version) { 48 | this.configFilename = configFilename; 49 | this.version = version; 50 | } 51 | 52 | /** 53 | * Parse editorconfig files corresponding to the file path given by filename, and return the parsing result. 54 | * 55 | * @param filePath The full path to be parsed. The path is usually the path of the file which is currently edited 56 | * by the editor. 57 | * @return The parsing result stored in a list of {@link EditorConfig.OutPair}. 58 | * @throws org.editorconfig.core.ParsingException If an {@code .editorconfig} file could not be parsed 59 | * @throws org.editorconfig.core.VersionException If version greater than actual is specified in constructor 60 | * @throws org.editorconfig.core.EditorConfigException If an EditorConfig exception occurs. Usually one of 61 | * {@link ParsingException} or {@link VersionException} 62 | */ 63 | public List getProperties(String filePath) throws EditorConfigException { 64 | return getProperties(filePath, Collections.emptySet()); 65 | } 66 | 67 | /** 68 | * Parse editorconfig files corresponding to the file path given by filename, and return the parsing result. 69 | * 70 | * @param filePath The full path to be parsed. The path is usually the path of the file which is currently edited 71 | * by the editor. 72 | * @param explicitRootDirs Set set of directories where search should stop even if no .editorconfig file with 73 | * root=true is found 74 | * @return The parsing result stored in a list of {@link EditorConfig.OutPair}. 75 | * @throws org.editorconfig.core.ParsingException If an {@code .editorconfig} file could not be parsed 76 | * @throws org.editorconfig.core.VersionException If version greater than actual is specified in constructor 77 | * @throws org.editorconfig.core.EditorConfigException If an EditorConfig exception occurs. Usually one of 78 | * {@link ParsingException} or {@link VersionException} 79 | */ 80 | public List getProperties(String filePath, Set explicitRootDirs) throws EditorConfigException { 81 | return getProperties(filePath, explicitRootDirs, null); 82 | } 83 | 84 | 85 | /** 86 | * Parse editorconfig files corresponding to the file path given by filename, and return the parsing result. 87 | * 88 | * @param filePath The full path to be parsed. The path is usually the path of the file which is currently edited 89 | * by the editor. 90 | * @param explicitRootDirs Set set of directories where search should stop even if no .editorconfig file with 91 | * root=true is found 92 | * @param callback A callback receiving control when a new EditorConfig file parsing starts, a line is 93 | * parsed in that file or an option is found. 94 | * @return The parsing result stored in a list of {@link EditorConfig.OutPair}. 95 | * @throws org.editorconfig.core.ParsingException If an {@code .editorconfig} file could not be parsed 96 | * @throws org.editorconfig.core.VersionException If version greater than actual is specified in constructor 97 | * @throws org.editorconfig.core.EditorConfigException If an EditorConfig exception occurs. Usually one of 98 | * {@link ParsingException} or {@link VersionException} 99 | * @see ParserCallback 100 | */ 101 | public List getProperties(String filePath, Set explicitRootDirs, ParserCallback callback) throws EditorConfigException { 102 | checkAssertions(); 103 | Map oldOptions = Collections.emptyMap(); 104 | Map options = new LinkedHashMap(); 105 | File sourceFile = new File(filePath); 106 | if (callback != null && !callback.processFile(sourceFile)) return Collections.emptyList(); 107 | try { 108 | boolean root = false; 109 | String dir = new File(filePath).getParent(); 110 | while (dir != null && !root && (callback == null || callback.processDir(new File(dir)))) { 111 | File configFile = new File(dir, configFilename); 112 | if (configFile.exists() && (callback == null || callback.processEditorConfig(configFile))) { 113 | BufferedReader bufferedReader = null; 114 | try { 115 | bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), "UTF-8")); 116 | root = parseFile(bufferedReader, dir + "/", filePath, options, callback); 117 | } finally { 118 | if (bufferedReader != null) { 119 | bufferedReader.close(); 120 | } 121 | } 122 | } 123 | options.putAll(oldOptions); 124 | oldOptions = options; 125 | options = new LinkedHashMap(); 126 | root |= explicitRootDirs.contains(dir); 127 | dir = new File(dir).getParent(); 128 | } 129 | } catch (IOException e) { 130 | throw new EditorConfigException(null, e); 131 | } 132 | 133 | preprocessOptions(oldOptions); 134 | 135 | final List result = new ArrayList(); 136 | for (Map.Entry keyValue : oldOptions.entrySet()) { 137 | result.add(new OutPair(keyValue.getKey(), keyValue.getValue())); 138 | } 139 | if (callback != null) callback.processingFinished(sourceFile); 140 | return result; 141 | } 142 | 143 | private void checkAssertions() throws VersionException { 144 | if (compareVersions(version, VERSION) > 0) { 145 | throw new VersionException("Required version is greater than the current version."); 146 | } 147 | } 148 | 149 | private static int compareVersions(String version1, String version2) { 150 | String[] version1Components = version1.split("([.-])"); 151 | String[] version2Components = version2.split("([.-])"); 152 | for (int i = 0; i < 3; i++) { 153 | String version1Component = version1Components[i]; 154 | String version2Component = version2Components[i]; 155 | int v1 = -1; 156 | int v2 = -1; 157 | try { 158 | v1 = Integer.parseInt(version1Component); 159 | } catch (NumberFormatException ignored) {} 160 | try { 161 | v2 = Integer.parseInt(version2Component); 162 | } catch (NumberFormatException ignored) {} 163 | if (v1 != v2) return v1 - v2; 164 | } 165 | return 0; 166 | } 167 | 168 | private void preprocessOptions(Map options) { 169 | // Lowercase option value for certain options 170 | for (String key : new String[]{"end_of_line", "indent_style", "indent_size", "insert_final_newline", 171 | "trim_trailing_whitespace", "charset"}) { 172 | String value = options.get(key); 173 | if (value != null) { 174 | options.put(key, value.toLowerCase(Locale.US)); 175 | } 176 | } 177 | 178 | // Set indent_size to "tab" if indent_size is unspecified and 179 | // indent_style is set to "tab". 180 | if ("tab".equals(options.get("indent_style")) && !options.containsKey("indent_size") && 181 | compareVersions(version, "0.10.0") >= 0) { 182 | options.put("indent_size", "tab"); 183 | } 184 | 185 | // Set tab_width to indent_size if indent_size is specified and 186 | // tab_width is unspecified 187 | String indent_size = options.get("indent_size"); 188 | if (indent_size != null && !"tab".equals(indent_size) && !options.containsKey("tab_width")) { 189 | options.put("tab_width", indent_size); 190 | } 191 | 192 | // Set indent_size to tab_width if indent_size is "tab" 193 | String tab_width = options.get("tab_width"); 194 | if ("tab".equals(indent_size) && tab_width != null) { 195 | options.put("indent_size", tab_width); 196 | } 197 | } 198 | 199 | private static boolean parseFile(BufferedReader bufferedReader, 200 | String dirName, 201 | String filePath, 202 | Map result, 203 | ParserCallback callback) throws IOException, EditorConfigException { 204 | final StringBuilder malformedLines = new StringBuilder(); 205 | boolean root = false; 206 | boolean inSection = false; 207 | boolean matchingSection = false; 208 | String line; 209 | while ((line = bufferedReader.readLine()) != null) { 210 | line = line.trim(); 211 | 212 | if (line.startsWith("\ufeff")) { 213 | line = line.substring(1); 214 | } 215 | 216 | if ((callback != null && !callback.processLine(line)) || line.isEmpty() || line.startsWith("#") || line.startsWith(";")) 217 | continue; 218 | 219 | Matcher matcher = SECTION_PATTERN.matcher(line); 220 | if (matcher.matches()) { 221 | inSection = true; 222 | try { 223 | matchingSection = filenameMatches(dirName, matcher.group(HEADER), filePath); 224 | } catch (PatternSyntaxException e) { 225 | malformedLines.append(line).append("\n"); 226 | } 227 | continue; 228 | } 229 | matcher = OPTION_PATTERN.matcher(line); 230 | if (matcher.matches()) { 231 | String key = matcher.group(OPTION).trim().toLowerCase(Locale.US); 232 | String value = matcher.group(VAL); 233 | value = value.equals("\"\"") ? "" : value; 234 | if (!inSection && "root".equals(key)) { 235 | root = true; 236 | } else if (matchingSection) { 237 | int commentPos = value.indexOf(" ;"); 238 | commentPos = commentPos < 0 ? value.indexOf(" #") : commentPos; 239 | value = commentPos >= 0 ? value.substring(0, commentPos) : value; 240 | if (callback == null || callback.processOption(key, value)) { 241 | result.put(key, value); 242 | } 243 | } 244 | continue; 245 | } 246 | malformedLines.append(line).append("\n"); 247 | } 248 | if (malformedLines.length() > 0) { 249 | throw new ParsingException(malformedLines.toString(), null); 250 | } 251 | return root; 252 | } 253 | 254 | public static boolean filenameMatches(String configDirname, String pattern, String filePath) { 255 | pattern = pattern.replace(File.separatorChar, '/'); 256 | pattern = pattern.replaceAll("\\\\#", "#"); 257 | pattern = pattern.replaceAll("\\\\;", ";"); 258 | int separator = pattern.indexOf("/"); 259 | if (separator >= 0) { 260 | pattern = configDirname.replace(File.separatorChar, '/') + (separator == 0 ? pattern.substring(1) : pattern); 261 | } else { 262 | pattern = "**/" + pattern; 263 | } 264 | final ArrayList ranges = new ArrayList(); 265 | final String regex = convertGlobToRegEx(pattern, ranges); 266 | if (DEBUG) { 267 | System.err.println(regex); 268 | for (int[] range : ranges) { 269 | System.err.println("numeric range: {" + range[0] + ".." + range[1] + "}"); 270 | } 271 | } 272 | final Matcher matcher = Pattern.compile(regex).matcher(filePath); 273 | if (matcher.matches()) { 274 | for (int i = 0; i < matcher.groupCount(); i++) { 275 | final int[] range = ranges.get(i); 276 | final String numberString = matcher.group(i + 1); 277 | if (numberString == null || numberString.startsWith("0")) return false; 278 | int number = Integer.parseInt(numberString); 279 | if (number < range[0] || number > range[1]) return false; 280 | } 281 | return true; 282 | } 283 | return false; 284 | } 285 | 286 | static String convertGlobToRegEx(String pattern, ArrayList ranges) { 287 | int length = pattern.length(); 288 | StringBuilder result = new StringBuilder(length); 289 | int i = 0; 290 | int braceLevel = 0; 291 | boolean matchingBraces = countAll(OPENING_BRACES, pattern) == countAll(CLOSING_BRACES, pattern); 292 | boolean escaped = false; 293 | boolean inBrackets = false; 294 | while (i < length) { 295 | char current = pattern.charAt(i); 296 | i++; 297 | if ('*' == current) { 298 | if (i < length && pattern.charAt(i) == '*') { 299 | result.append(".*"); 300 | i++; 301 | } else { 302 | result.append("[^/]*"); 303 | } 304 | } else if ('?' == current) { 305 | result.append("."); 306 | } else if ('[' == current) { 307 | boolean seenSlash = findChar('/', ']', pattern, length, i) >= 0; 308 | if (seenSlash || escaped) { 309 | result.append("\\["); 310 | } else if (i < length && "!^".indexOf(pattern.charAt(i)) >= 0) { 311 | i++; 312 | result.append("[^"); 313 | } else { 314 | result.append("["); 315 | } 316 | inBrackets = true; 317 | } else if (']' == current || ('-' == current && inBrackets)) { 318 | if (escaped) { 319 | result.append("\\"); 320 | } 321 | result.append(current); 322 | inBrackets = current != ']' || escaped; 323 | } else if ('{' == current) { 324 | int j = findChar(',', '}', pattern, length, i); 325 | if (j < 0 && -j < length) { 326 | final String choice = pattern.substring(i, -j); 327 | final int[] range = getNumericRange(choice); 328 | if (range != null) { 329 | result.append("(\\d+)"); 330 | ranges.add(range); 331 | } else { 332 | result = new StringBuilder(result); 333 | result.append("\\{"); 334 | result.append(convertGlobToRegEx(choice, ranges)); 335 | result.append("\\}"); 336 | } 337 | i = -j + 1; 338 | } else if (matchingBraces) { 339 | result.append("(?:"); 340 | braceLevel++; 341 | } else { 342 | result.append("\\{"); 343 | } 344 | } else if (',' == current) { 345 | if (braceLevel > 0 && !escaped) { 346 | result.append("|"); 347 | while (i < length && pattern.charAt(i) == ' ') { 348 | i++; 349 | } 350 | } else { 351 | result.append(","); 352 | } 353 | } else if ('/' == current) { 354 | if (i < length && pattern.charAt(i) == '*') { 355 | if (i + 1 < length && pattern.charAt(i + 1) == '*' && 356 | i + 2 < length && pattern.charAt(i + 2) == '/') { 357 | result.append("(?:/|/.*/)"); 358 | i += 3; 359 | } else { 360 | result.append(current); 361 | } 362 | } else { 363 | result.append(current); 364 | } 365 | } else if ('}' == current) { 366 | if (braceLevel > 0 && !escaped) { 367 | result.append(")"); 368 | braceLevel--; 369 | } else { 370 | result.append("}"); 371 | } 372 | } else if ('\\' != current) { 373 | result.append(escapeToRegex(String.valueOf(current))); 374 | } 375 | if ('\\' == current) { 376 | if (escaped) result.append("\\\\"); 377 | escaped = !escaped; 378 | } else { 379 | escaped = false; 380 | } 381 | } 382 | 383 | return result.toString(); 384 | } 385 | 386 | private static int[] getNumericRange(String choice) { 387 | final int separator = choice.indexOf(".."); 388 | if (separator < 0 ) return null; 389 | try { 390 | int start = Integer.parseInt(choice.substring(0, separator)); 391 | int end = Integer.parseInt(choice.substring(separator + 2)); 392 | return new int[] {start, end}; 393 | } catch (NumberFormatException ignored) {} 394 | return null; 395 | } 396 | 397 | private static int findChar(final char c, final char stopAt, String pattern, int length, int start) { 398 | int j = start; 399 | boolean escapedChar = false; 400 | while (j < length && (pattern.charAt(j) != stopAt || escapedChar)) { 401 | if (pattern.charAt(j) == c && !escapedChar) { 402 | return j; 403 | } 404 | escapedChar = pattern.charAt(j) == '\\' && !escapedChar; 405 | j++; 406 | } 407 | return -j; 408 | } 409 | 410 | private static String escapeToRegex(String group) { 411 | final StringBuilder builder = new StringBuilder(group.length()); 412 | for (char c : group.toCharArray()) { 413 | if (c == ' ' || Character.isLetter(c) || Character.isDigit(c) || c == '_' || c == '-') { 414 | builder.append(c); 415 | } else if (c == '\n') { 416 | builder.append("\\n"); 417 | } else { 418 | builder.append("\\").append(c); 419 | } 420 | } 421 | return builder.toString(); 422 | } 423 | 424 | private static int countAll(Pattern regex, String pattern) { 425 | final Matcher matcher = regex.matcher(pattern); 426 | int count = 0; 427 | while (matcher.find()) count++; 428 | return count; 429 | } 430 | 431 | /** 432 | * String-String pair to store the parsing result. 433 | */ 434 | public static class OutPair { 435 | private final String key; 436 | private final String val; 437 | 438 | public OutPair(String key, String val) { 439 | this.key = key; 440 | this.val = val; 441 | } 442 | 443 | public String getKey(){ 444 | return key; 445 | } 446 | 447 | public String getVal() { 448 | return val; 449 | } 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/EditorConfigException.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * The base class of all EditorConfig exceptions 7 | * 8 | * @author Dennis.Ushakov 9 | */ 10 | public class EditorConfigException extends Exception { 11 | public EditorConfigException(String s, IOException e) { 12 | super(s, e); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/ParserCallback.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Allows to handle an EditorConfig file, a line in it or an option. In every case {@code process...()} method may 7 | * return {@code false} to terminate processing of a certain part. 8 | */ 9 | public interface ParserCallback { 10 | /** 11 | * Called before EditorConfig files are processed for the given source file. 12 | * 13 | * @param file The file to process 14 | * @return False if EditorConfig processing should be completely skipped for the given source file. 15 | */ 16 | boolean processFile(File file); 17 | 18 | /** 19 | * Called before EditorConfig file is searched in a directory. The directory may not necessarily contain 20 | * .editorconfig file. 21 | * 22 | * @param dir The current directory to look for .editorconfig file. 23 | * @return False if no further processing should be done in the directory or any parent directories. 24 | */ 25 | boolean processDir(File dir); 26 | 27 | /** 28 | * Called when an EditorConfig file is found. 29 | * 30 | * @param configFile The config file that has been found. 31 | * @return True if the file should be further processed by EditorConfig library, true to skip any further 32 | * processing. 33 | * @throws EditorConfigException If the file is invalid for some reason. 34 | */ 35 | boolean processEditorConfig(File configFile) throws EditorConfigException; 36 | 37 | /** 38 | * Called when a line from EditorConfig file has been read. 39 | * 40 | * @param line The read line. The line is just any line including empty lines, comment lines etc. with spaces 41 | * trimmed. 42 | * @return False if the line should be further handled by EditorConfig library, true if the line is to be skipped. 43 | * @throws EditorConfigException If the line contains an error. 44 | */ 45 | boolean processLine(String line) throws EditorConfigException; 46 | 47 | /** 48 | * Called when an option has been parsed. 49 | * 50 | * @param key The option key. 51 | * @param value The option value. 52 | * @return False if the option should be added to the returned list of pairs, true if not (the option is consumed 53 | * by the callback object itself). 54 | * @throws EditorConfigException for improper key-value pair. 55 | */ 56 | boolean processOption(String key, String value) throws EditorConfigException; 57 | 58 | /** 59 | * Called when all .editorconfig files have been processed. 60 | * 61 | * @param file The initial source file. 62 | */ 63 | void processingFinished(File file); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/ParsingException.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Exception which is thrown by {@link EditorConfig#getProperties(String)} if an EditorConfig file could not be parsed 7 | * 8 | * @author Dennis.Ushakov 9 | */ 10 | public class ParsingException extends EditorConfigException { 11 | public ParsingException(String s, IOException e) { 12 | super(s, e); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/VersionException.java: -------------------------------------------------------------------------------- 1 | package org.editorconfig.core; 2 | 3 | /** 4 | * Exception which is thrown by {@link EditorConfig#getProperties(String)} if an invalid version number is specified 5 | * 6 | * @author Dennis.Ushakov 7 | */ 8 | public class VersionException extends EditorConfigException { 9 | public VersionException(String s) { 10 | super(s, null); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/editorconfig/core/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides the Java API for accessing 3 | * EditorConfig Core 4 | * (For the purpose and usage of EditorConfig, see 5 | * EditorConfig homepage for details). 6 | */ 7 | 8 | package org.editorconfig.core; --------------------------------------------------------------------------------