├── src └── main │ ├── resources │ └── application.properties │ ├── java │ └── io │ │ └── github │ │ └── ludovicianul │ │ ├── Sanitize.java │ │ └── HtmlCommand.java │ └── docker │ ├── Dockerfile.native-distroless │ ├── Dockerfile.native │ ├── Dockerfile.legacy-jar │ └── Dockerfile.jvm ├── .dockerignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── .gitignore ├── LICENSE ├── pom.xml ├── README.md ├── mvnw.cmd ├── hq_autocomplete └── mvnw /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.level=ERROR 2 | quarkus.banner.enabled=false -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludovicianul/hq/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | hq-linux 2 | hq-macos 3 | hq-uberjar.jar 4 | #Maven 5 | target/ 6 | pom.xml.tag 7 | pom.xml.releaseBackup 8 | pom.xml.versionsBackup 9 | release.properties 10 | 11 | # Eclipse 12 | .project 13 | .classpath 14 | .settings/ 15 | bin/ 16 | 17 | # IntelliJ 18 | .idea 19 | *.ipr 20 | *.iml 21 | *.iws 22 | 23 | # NetBeans 24 | nb-configuration.xml 25 | 26 | # Visual Studio Code 27 | .vscode 28 | .factorypath 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # Vim 34 | *.swp 35 | *.swo 36 | 37 | # patch 38 | *.orig 39 | *.rej 40 | 41 | # Local environment 42 | .env 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/ludovicianul/Sanitize.java: -------------------------------------------------------------------------------- 1 | package io.github.ludovicianul; 2 | 3 | import org.jsoup.safety.Safelist; 4 | 5 | public enum Sanitize { 6 | NONE(Safelist.none()), 7 | BASIC(Safelist.basic()), 8 | SIMPLE_TEXT(Safelist.simpleText()), 9 | BASIC_WITH_IMAGES(Safelist.basicWithImages()), 10 | RELAXED(Safelist.relaxed()); 11 | 12 | private final Safelist safelist; 13 | 14 | Sanitize(Safelist safelist) { 15 | this.safelist = safelist; 16 | } 17 | 18 | public Safelist safelist() { 19 | return this.safelist; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native-distroless: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Pnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/qhtml . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/qhtml 15 | # 16 | ### 17 | FROM quay.io/quarkus/quarkus-distroless-image:1.0 18 | COPY target/*-runner /application 19 | 20 | EXPOSE 8080 21 | USER nonroot 22 | 23 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 24 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Pnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/qhtml . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/qhtml 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Madalin Ilie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.legacy-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dquarkus.package.type=legacy-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/qhtml-legacy-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/qhtml-legacy-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/qhtml-legacy-jar 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 29 | # Install java and the run-java script 30 | # Also set up permissions for user `1001` 31 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 32 | && microdnf update \ 33 | && microdnf clean all \ 34 | && mkdir /deployments \ 35 | && chown 1001 /deployments \ 36 | && chmod "g+rwX" /deployments \ 37 | && chown 1001:root /deployments \ 38 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 39 | && chown 1001 /deployments/run-java.sh \ 40 | && chmod 540 /deployments/run-java.sh \ 41 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security 42 | 43 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 44 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 45 | COPY target/lib/* /deployments/lib/ 46 | COPY target/*-runner.jar /deployments/app.jar 47 | 48 | EXPOSE 8080 49 | USER 1001 50 | 51 | ENTRYPOINT [ "/deployments/run-java.sh" ] 52 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/qhtml-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/qhtml-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/qhtml-jvm 22 | # 23 | ### 24 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 25 | 26 | ARG JAVA_PACKAGE=java-11-openjdk-headless 27 | ARG RUN_JAVA_VERSION=1.3.8 28 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 29 | # Install java and the run-java script 30 | # Also set up permissions for user `1001` 31 | RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 32 | && microdnf update \ 33 | && microdnf clean all \ 34 | && mkdir /deployments \ 35 | && chown 1001 /deployments \ 36 | && chmod "g+rwX" /deployments \ 37 | && chown 1001:root /deployments \ 38 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 39 | && chown 1001 /deployments/run-java.sh \ 40 | && chmod 540 /deployments/run-java.sh \ 41 | && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security 42 | 43 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 44 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 45 | # We make four distinct layers so if there are application changes the library layers can be re-used 46 | COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ 47 | COPY --chown=1001 target/quarkus-app/*.jar /deployments/ 48 | COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ 49 | COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ 50 | 51 | EXPOSE 8080 52 | USER 1001 53 | 54 | ENTRYPOINT [ "/deployments/run-java.sh" ] 55 | 56 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 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 | * https://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 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | io.github.ludovicianul 5 | hq 6 | 1.3.3-SNAPSHOT 7 | 8 | 3.8.1 9 | true 10 | 17 11 | 17 12 | UTF-8 13 | UTF-8 14 | quarkus-bom 15 | io.quarkus.platform 16 | 3.4.1 17 | 3.0.0-M5 18 | 0.3.7 19 | 1.16.1 20 | 21 | 22 | 23 | 24 | ${quarkus.platform.group-id} 25 | ${quarkus.platform.artifact-id} 26 | ${quarkus.platform.version} 27 | pom 28 | import 29 | 30 | 31 | 32 | 33 | 34 | io.quarkus 35 | quarkus-picocli 36 | 37 | 38 | io.quarkus 39 | quarkus-arc 40 | 41 | 42 | org.jsoup 43 | jsoup 44 | ${jsoup.version} 45 | 46 | 47 | us.codecraft 48 | xsoup 49 | ${xsoup.version} 50 | 51 | 52 | io.quarkus 53 | quarkus-junit5 54 | test 55 | 56 | 57 | 58 | 59 | 60 | ${quarkus.platform.group-id} 61 | quarkus-maven-plugin 62 | ${quarkus.platform.version} 63 | true 64 | 65 | 66 | 67 | build 68 | generate-code 69 | generate-code-tests 70 | 71 | 72 | 73 | 74 | 75 | maven-compiler-plugin 76 | ${compiler-plugin.version} 77 | 78 | ${maven.compiler.parameters} 79 | 80 | 81 | 82 | maven-surefire-plugin 83 | ${surefire-plugin.version} 84 | 85 | 86 | org.jboss.logmanager.LogManager 87 | ${maven.home} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | native 96 | 97 | 98 | native 99 | 100 | 101 | 102 | 103 | 104 | maven-failsafe-plugin 105 | ${surefire-plugin.version} 106 | 107 | 108 | 109 | integration-test 110 | verify 111 | 112 | 113 | 114 | 115 | ${project.build.directory}/${project.build.finalName}-runner 116 | 117 | org.jboss.logmanager.LogManager 118 | 119 | ${maven.home} 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | native 129 | 130 | 131 | 132 | 133 | https://ludovicianul@github.com/ludovicianul/hq.git 134 | scm:git:https://ludovicianul@github.com/ludovicianul/hq.git 135 | scm:git:https://ludovicianul@github.com/ludovicianul/hq.git 136 | 137 | HEAD 138 | 139 | 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hq 2 | 3 | Small utility to parse and grep HTML files. It 4 | uses [CSS selectors](https://www.w3schools.com/cssref/css_selectors.asp) or [XPath Selectors](https://www.w3schools.com/xml/xpath_intro.asp) to extract HTML elements. 5 | 6 | # Usage 7 | 8 | ```bash 9 | hq - command line HTML elements finder; version 1.0.0 10 | 11 | Usage: hq [-hptV] [-a=] [-f=] [-o=] [-s=] [-x=] 12 | [COMMAND] 13 | The CSS selector 14 | -a, --attribute= 15 | Return only this attribute from the selected HTML elements 16 | -f, --file= The HTML input file. If not supplied it will default to stdin 17 | -h, --help Show this help message and exit. 18 | -o, --output= The output file. If not supplied it will default to stdout 19 | -p, --pretty Force pretty printing the output 20 | -r, --remove= Remove nodes matching given selector 21 | -s, --sanitize= Sanitizes the html input according to the given policy 22 | -t, --text Display only the inner text of the selected HTML top element 23 | -V, --version Print version information and exit. 24 | -x, --xpath= Supply an XPath selector instead of CSS 25 | Commands: 26 | generate-completion Generate bash/zsh completion script for hq. 27 | 28 | ``` 29 | 30 | # Installation 31 | 32 | ## Homebrew 33 | 34 | ``` 35 | > brew tap ludovicianul/tap 36 | > brew install ludovicianul/tap/hq 37 | ``` 38 | 39 | ## Manual 40 | 41 | `hq` is compiled to native code using GraalVM. Check 42 | the [release page](https://github.com/ludovicianul/hq/releases/) for binaries (Linux, 43 | MacOS, uberjar). 44 | 45 | After download, you can make `hq` globally available: 46 | 47 | ```bash 48 | sudo cp hq-macos /usr/local/bin/hq 49 | ``` 50 | 51 | The uberjar can be run using `java -jar hq`. Requires Java 11+. 52 | 53 | # Autocomplete 54 | Run the following commands to get autocomplete: 55 | 56 | ```bash 57 | hq generate-completion >> hq_autocomplete 58 | 59 | source hq_autocomplete 60 | ``` 61 | 62 | # HTML Sanitizing 63 | `hq` can sanitize html output. Supported modes are: `NONE, BASIC, SIMPLE_TEXT, BASIC_WITH_IMAGES, RELAXED`. 64 | 65 | This is how sanitization works: 66 | 67 | | Policy | Details | 68 | | ------- | ------- | 69 | | `NONE` | Allows only text nodes: all HTML will be stripped. | 70 | | `BASIC` | Allows a fuller range of text nodes: `a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul`, and appropriate attributes. Does not allow images.| 71 | | `SIMPLE_TEXT` | Allows only simple text formatting: `b, em, i, strong, u`. All other HTML (tags and attributes) will be removed.| 72 | | `BASIC_WITH_IMAGES` | Allows the same text tags as `BASIC`, and also allows `img` tags, with appropriate attributes, with `src` pointing to `http` or `https`. 73 | | `RELAXES` | Allows a full range of text and structural body HTML: `a, b, blockquote, br, caption, cite, code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, u, ul`.| 74 | 75 | # Examples 76 | 77 | Get the `div` with id `mainLeaderboard`: 78 | 79 | ``` 80 | ➜ curl -s https://www.w3schools.com/cssref/css_selectors.php | hq "#main > p:nth-child(6)" -t 81 | 82 | In CSS, selectors are patterns used to select the element(s) you want to style. 83 | 84 | ``` 85 | 86 | Get the text inside an article: 87 | 88 | ``` 89 | ➜ curl -s https://ludovicianul.github.io/2021/07/16/unicode_language_version/ | hq '.post' -t 90 | 91 | Make sure you know which Unicode version is supported by your programming language version 16 Jul 2021 While enhancing CATS I recently added a feature to send requests that include 92 | single and multi code point emojis. This is a single code point emoji: 🥶, which can be represented in Java as the \uD83E\uDD76 string. The test case is simple: inject emojis within 93 | strings and expect that the REST endpoint will sanitize the input and remove them entirely (I appreciate this might not be a valid case for all APIs, this is why the behaviour is 94 | configurable in CATS, but not the focus of this article). I usually recommend that any REST endpoint should sanitize input before validating it and remove special characters. 95 | A typical regex for this would be [\p{C}\p{Z}\p{So}]+ (although you should enhance it to allow spaces between words), which means: p{C} - match Unicode invisible Control 96 | Chars (\u000D - carriage return for example) ... 97 | ... 98 | ``` 99 | 100 | Sanitize the html according to the [specified policy](#html-sanitizing): 101 | ``` 102 | ➜ curl -s https://ludovicianul.github.io/2021/07/16/unicode_language_version/ | hq html -s=BASIC -p 103 | 104 | 105 | 106 | 107 | m's blog 108 |

practical thoughts about software engineering

109 | Home 110 | About 111 | GitHub 112 |

© 2021. All rights reserved.

113 | Make sure you know which Unicode version is supported by your programming language version 114 | 16 Jul 2021 115 |

116 | ... 117 | 118 | 119 | ``` 120 | 121 | Get all `href` attributes from a given page: 122 | 123 | ```shell 124 | ➜ curl -s https://ludovicianul.github.io | hq "*" -a "href" 125 | http://gmpg.org/xfn/11 126 | https://ludovicianul.github.io/public/css/poole.css 127 | https://ludovicianul.github.io/public/css/syntax.css 128 | https://ludovicianul.github.io/public/css/hyde.css 129 | https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface 130 | https://ludovicianul.github.io/public/apple-touch-icon-144-precomposed.png 131 | https://ludovicianul.github.io/public/favicon.ico 132 | /atom.xml 133 | https://ludovicianul.github.io/ 134 | https://ludovicianul.github.io/ 135 | /about/ 136 | ... 137 | ``` 138 | 139 | # Resources 140 | 141 | - [Universal selector in CSS](https://www.scaler.com/topics/universal-selector-in-css/) 142 | - [HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) 143 | -------------------------------------------------------------------------------- /src/main/java/io/github/ludovicianul/HtmlCommand.java: -------------------------------------------------------------------------------- 1 | package io.github.ludovicianul; 2 | 3 | import io.quarkus.runtime.QuarkusApplication; 4 | import io.quarkus.runtime.annotations.QuarkusMain; 5 | import jakarta.inject.Inject; 6 | import java.io.BufferedReader; 7 | import java.io.FileReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.Reader; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | import org.jsoup.Jsoup; 16 | import org.jsoup.nodes.Document; 17 | import org.jsoup.parser.Parser; 18 | import org.jsoup.safety.Cleaner; 19 | import org.jsoup.select.Elements; 20 | import picocli.AutoComplete; 21 | import picocli.CommandLine; 22 | import picocli.CommandLine.Command; 23 | import picocli.CommandLine.Parameters; 24 | import us.codecraft.xsoup.Xsoup; 25 | 26 | @QuarkusMain 27 | @Command( 28 | name = "hq", 29 | mixinStandardHelpOptions = true, 30 | version = "hq 1.3.1", 31 | usageHelpWidth = 100, 32 | header = "hq - command line HTML and XML elements finder and sanitizer; version 1.3.1\n", 33 | subcommands = AutoComplete.GenerateCompletion.class) 34 | public class HtmlCommand implements Runnable, QuarkusApplication { 35 | 36 | @Inject 37 | CommandLine.IFactory factory; 38 | 39 | @Parameters( 40 | index = "0", 41 | paramLabel = "", 42 | defaultValue = "*", 43 | description = "The CSS selector") 44 | String selector; 45 | 46 | @CommandLine.Option( 47 | names = {"-a", "--attribute"}, 48 | paramLabel = "", 49 | description = "Return only this attribute from the selected HTML elements") 50 | String attribute; 51 | 52 | @CommandLine.Option( 53 | names = {"-f", "--file"}, 54 | paramLabel = "", 55 | description = "The HTML input file. If not supplied it will default to stdin") 56 | String file; 57 | 58 | @CommandLine.Option( 59 | names = {"-o", "--output"}, 60 | paramLabel = "", 61 | description = "The output file. If not supplied it will default to stdout") 62 | String output; 63 | 64 | @CommandLine.Option( 65 | names = {"-x", "--xpath"}, 66 | paramLabel = "", 67 | description = "Supply an XPath selector instead of CSS") 68 | String xpath; 69 | 70 | @CommandLine.Option( 71 | names = {"-t", "--text"}, 72 | description = "Display only the inner text of the selected HTML top element") 73 | boolean text; 74 | 75 | @CommandLine.Option( 76 | names = {"-p", "--pretty"}, 77 | description = "Force pretty printing the output") 78 | boolean prettyPrint; 79 | 80 | @CommandLine.Option( 81 | names = {"-s", "--sanitize"}, 82 | paramLabel = "", 83 | description = "Sanitize the html input according to the given policy") 84 | Sanitize sanitize; 85 | 86 | @CommandLine.Option( 87 | names = {"-r", "--remove"}, 88 | paramLabel = "", 89 | description = "Remove nodes matching given selector") 90 | String remove; 91 | 92 | @CommandLine.Option( 93 | names = {"-D", "--debug"}, 94 | description = "Debug") 95 | boolean debug; 96 | 97 | @Override 98 | public void run() { 99 | try { 100 | String html; 101 | if (file == null) { 102 | html = this.parseSystemIn(); 103 | } else { 104 | html = this.parseFile(); 105 | } 106 | if (debug) { 107 | System.out.println("Received file: \n" + html); 108 | } 109 | if (html != null && !html.trim().isBlank()) { 110 | this.processHtml(html); 111 | } 112 | } catch (Exception e) { 113 | throw new RuntimeException(e); 114 | } 115 | } 116 | 117 | private void processHtml(String html) throws IOException { 118 | Elements elements; 119 | Parser parser = isXML(html) ? Parser.xmlParser() : Parser.htmlParser(); 120 | Document document = Jsoup.parse(html, parser); 121 | 122 | if (sanitize != null) { 123 | document = this.sanitize(document); 124 | } 125 | 126 | this.setPrettyPrint(document); 127 | elements = this.evaluateSelector(document); 128 | this.removeIfNeeded(elements); 129 | 130 | this.printResult(elements); 131 | } 132 | 133 | private boolean isXML(String text) { 134 | return text.startsWith(" elementsWithAttribute = elements.eachAttr(attribute); 169 | this.writeToOutput( 170 | elementsWithAttribute.stream().collect(Collectors.joining(System.lineSeparator()))); 171 | } else if (text) { 172 | this.writeToOutput(elements.text()); 173 | } else { 174 | this.writeToOutput(elements.outerHtml()); 175 | } 176 | } 177 | 178 | private void writeToOutput(String string) throws IOException { 179 | if (output == null) { 180 | System.out.println(string); 181 | } else { 182 | Path path = Path.of(output); 183 | Files.writeString(path, string); 184 | } 185 | } 186 | 187 | private String parseInput(Reader reader) throws IOException { 188 | try (BufferedReader in = new BufferedReader(reader)) { 189 | return in.lines().collect(Collectors.joining(System.lineSeparator())); 190 | } 191 | } 192 | 193 | private String parseFile() throws IOException { 194 | return this.parseInput(new FileReader(file)); 195 | } 196 | 197 | private String parseSystemIn() throws IOException { 198 | return this.parseInput(new InputStreamReader(System.in)); 199 | } 200 | 201 | @Override 202 | public int run(String... args) { 203 | return new CommandLine(this, factory).execute(args); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /hq_autocomplete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # hq Bash Completion 4 | # ======================= 5 | # 6 | # Bash completion support for the `hq` command, 7 | # generated by [picocli](https://picocli.info/) version 4.7.1. 8 | # 9 | # Installation 10 | # ------------ 11 | # 12 | # 1. Source all completion scripts in your .bash_profile 13 | # 14 | # cd $YOUR_APP_HOME/bin 15 | # for f in $(find . -name "*_completion"); do line=". $(pwd)/$f"; grep "$line" ~/.bash_profile || echo "$line" >> ~/.bash_profile; done 16 | # 17 | # 2. Open a new bash console, and type `hq [TAB][TAB]` 18 | # 19 | # 1a. Alternatively, if you have [bash-completion](https://github.com/scop/bash-completion) installed: 20 | # Place this file in a `bash-completion.d` folder: 21 | # 22 | # * /etc/bash-completion.d 23 | # * /usr/local/etc/bash-completion.d 24 | # * ~/bash-completion.d 25 | # 26 | # Documentation 27 | # ------------- 28 | # The script is called by bash whenever [TAB] or [TAB][TAB] is pressed after 29 | # 'hq (..)'. By reading entered command line parameters, 30 | # it determines possible bash completions and writes them to the COMPREPLY variable. 31 | # Bash then completes the user input if only one entry is listed in the variable or 32 | # shows the options if more than one is listed in COMPREPLY. 33 | # 34 | # References 35 | # ---------- 36 | # [1] http://stackoverflow.com/a/12495480/1440785 37 | # [2] http://tiswww.case.edu/php/chet/bash/FAQ 38 | # [3] https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html 39 | # [4] http://zsh.sourceforge.net/Doc/Release/Options.html#index-COMPLETE_005fALIASES 40 | # [5] https://stackoverflow.com/questions/17042057/bash-check-element-in-array-for-elements-in-another-array/17042655#17042655 41 | # [6] https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html#Programmable-Completion 42 | # [7] https://stackoverflow.com/questions/3249432/can-a-bash-tab-completion-script-be-used-in-zsh/27853970#27853970 43 | # 44 | 45 | if [ -n "$BASH_VERSION" ]; then 46 | # Enable programmable completion facilities when using bash (see [3]) 47 | shopt -s progcomp 48 | elif [ -n "$ZSH_VERSION" ]; then 49 | # Make alias a distinct command for completion purposes when using zsh (see [4]) 50 | setopt COMPLETE_ALIASES 51 | alias compopt=complete 52 | 53 | # Enable bash completion in zsh (see [7]) 54 | # Only initialize completions module once to avoid unregistering existing completions. 55 | if ! type compdef > /dev/null; then 56 | autoload -U +X compinit && compinit 57 | fi 58 | autoload -U +X bashcompinit && bashcompinit 59 | fi 60 | 61 | # CompWordsContainsArray takes an array and then checks 62 | # if all elements of this array are in the global COMP_WORDS array. 63 | # 64 | # Returns zero (no error) if all elements of the array are in the COMP_WORDS array, 65 | # otherwise returns 1 (error). 66 | function CompWordsContainsArray() { 67 | declare -a localArray 68 | localArray=("$@") 69 | local findme 70 | for findme in "${localArray[@]}"; do 71 | if ElementNotInCompWords "$findme"; then return 1; fi 72 | done 73 | return 0 74 | } 75 | function ElementNotInCompWords() { 76 | local findme="$1" 77 | local element 78 | for element in "${COMP_WORDS[@]}"; do 79 | if [[ "$findme" = "$element" ]]; then return 1; fi 80 | done 81 | return 0 82 | } 83 | 84 | # The `currentPositionalIndex` function calculates the index of the current positional parameter. 85 | # 86 | # currentPositionalIndex takes three parameters: 87 | # the command name, 88 | # a space-separated string with the names of options that take a parameter, and 89 | # a space-separated string with the names of boolean options (that don't take any params). 90 | # When done, this function echos the current positional index to std_out. 91 | # 92 | # Example usage: 93 | # local currIndex=$(currentPositionalIndex "mysubcommand" "$ARG_OPTS" "$FLAG_OPTS") 94 | function currentPositionalIndex() { 95 | local commandName="$1" 96 | local optionsWithArgs="$2" 97 | local booleanOptions="$3" 98 | local previousWord 99 | local result=0 100 | 101 | for i in $(seq $((COMP_CWORD - 1)) -1 0); do 102 | previousWord=${COMP_WORDS[i]} 103 | if [ "${previousWord}" = "$commandName" ]; then 104 | break 105 | fi 106 | if [[ "${optionsWithArgs}" =~ ${previousWord} ]]; then 107 | ((result-=2)) # Arg option and its value not counted as positional param 108 | elif [[ "${booleanOptions}" =~ ${previousWord} ]]; then 109 | ((result-=1)) # Flag option itself not counted as positional param 110 | fi 111 | ((result++)) 112 | done 113 | echo "$result" 114 | } 115 | 116 | # compReplyArray generates a list of completion suggestions based on an array, ensuring all values are properly escaped. 117 | # 118 | # compReplyArray takes a single parameter: the array of options to be displayed 119 | # 120 | # The output is echoed to std_out, one option per line. 121 | # 122 | # Example usage: 123 | # local options=("foo", "bar", "baz") 124 | # local IFS=$'\n' 125 | # COMPREPLY=($(compReplyArray "${options[@]}")) 126 | function compReplyArray() { 127 | declare -a options 128 | options=("$@") 129 | local curr_word=${COMP_WORDS[COMP_CWORD]} 130 | local i 131 | local quoted 132 | local optionList=() 133 | 134 | for (( i=0; i<${#options[@]}; i++ )); do 135 | # Double escape, since we want escaped values, but compgen -W expands the argument 136 | printf -v quoted %q "${options[i]}" 137 | quoted=\'${quoted//\'/\'\\\'\'}\' 138 | 139 | optionList[i]=$quoted 140 | done 141 | 142 | # We also have to add another round of escaping to $curr_word. 143 | curr_word=${curr_word//\\/\\\\} 144 | curr_word=${curr_word//\'/\\\'} 145 | 146 | # Actually generate completions. 147 | local IFS=$'\n' 148 | echo -e "$(compgen -W "${optionList[*]}" -- "$curr_word")" 149 | } 150 | 151 | # Bash completion entry point function. 152 | # _complete_hq finds which commands and subcommands have been specified 153 | # on the command line and delegates to the appropriate function 154 | # to generate possible options and subcommands for the last specified subcommand. 155 | function _complete_hq() { 156 | # Edge case: if command line has no space after subcommand, then don't assume this subcommand is selected (remkop/picocli#1468). 157 | if [ "${COMP_LINE}" = "${COMP_WORDS[0]} generate-completion" ]; then _picocli_hq; return $?; fi 158 | 159 | # Find the longest sequence of subcommands and call the bash function for that subcommand. 160 | local cmds0=(generate-completion) 161 | 162 | if CompWordsContainsArray "${cmds0[@]}"; then _picocli_hq_generatecompletion; return $?; fi 163 | 164 | # No subcommands were specified; generate completions for the top-level command. 165 | _picocli_hq; return $?; 166 | } 167 | 168 | # Generates completions for the options and subcommands of the `hq` command. 169 | function _picocli_hq() { 170 | # Get completion data 171 | local curr_word=${COMP_WORDS[COMP_CWORD]} 172 | local prev_word=${COMP_WORDS[COMP_CWORD-1]} 173 | 174 | local commands="generate-completion" 175 | local flag_opts="-t --text -p --pretty -D --debug -h --help -V --version" 176 | local arg_opts="-a --attribute -f --file -o --output -x --xpath -s --sanitize -r --remove" 177 | local POLICY_option_args=("NONE" "BASIC" "SIMPLE_TEXT" "BASIC_WITH_IMAGES" "RELAXED") # --sanitize values 178 | 179 | type compopt &>/dev/null && compopt +o default 180 | 181 | case ${prev_word} in 182 | -a|--attribute) 183 | return 184 | ;; 185 | -f|--file) 186 | return 187 | ;; 188 | -o|--output) 189 | return 190 | ;; 191 | -x|--xpath) 192 | return 193 | ;; 194 | -s|--sanitize) 195 | local IFS=$'\n' 196 | COMPREPLY=( $( compReplyArray "${POLICY_option_args[@]}" ) ) 197 | return $? 198 | ;; 199 | -r|--remove) 200 | return 201 | ;; 202 | esac 203 | 204 | if [[ "${curr_word}" == -* ]]; then 205 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 206 | else 207 | local positionals="" 208 | local IFS=$'\n' 209 | COMPREPLY=( $(compgen -W "${commands// /$'\n'}${IFS}${positionals}" -- "${curr_word}") ) 210 | fi 211 | } 212 | 213 | # Generates completions for the options and subcommands of the `generate-completion` subcommand. 214 | function _picocli_hq_generatecompletion() { 215 | # Get completion data 216 | local curr_word=${COMP_WORDS[COMP_CWORD]} 217 | 218 | local commands="" 219 | local flag_opts="-h --help -V --version" 220 | local arg_opts="" 221 | 222 | if [[ "${curr_word}" == -* ]]; then 223 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 224 | else 225 | local positionals="" 226 | local IFS=$'\n' 227 | COMPREPLY=( $(compgen -W "${commands// /$'\n'}${IFS}${positionals}" -- "${curr_word}") ) 228 | fi 229 | } 230 | 231 | # Define a completion specification (a compspec) for the 232 | # `hq`, `hq.sh`, and `hq.bash` commands. 233 | # Uses the bash `complete` builtin (see [6]) to specify that shell function 234 | # `_complete_hq` is responsible for generating possible completions for the 235 | # current word on the command line. 236 | # The `-o default` option means that if the function generated no matches, the 237 | # default Bash completions and the Readline default filename completions are performed. 238 | complete -F _complete_hq -o default hq hq.sh hq.bash 239 | 240 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | --------------------------------------------------------------------------------