mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Initial code checkin
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,10 @@
|
|||||||
|
target/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
## Original contents from github template: ##
|
||||||
|
#############################################
|
||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
242
checkstyle.xml
Normal file
242
checkstyle.xml
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
|
||||||
|
<!--
|
||||||
|
See reference at https://checkstyle.sourceforge.io/checks.html
|
||||||
|
-->
|
||||||
|
<module name = "Checker">
|
||||||
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
|
||||||
|
<property name="fileExtensions" value="java, properties, xml"/>
|
||||||
|
<!-- Checks for whitespace -->
|
||||||
|
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<property name="eachLine" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="max" value="100"/>
|
||||||
|
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<module name="OuterTypeFilename"/>
|
||||||
|
<module name="IllegalTokenText">
|
||||||
|
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||||
|
<property name="format" value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||||
|
<property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidEscapedUnicodeCharacters">
|
||||||
|
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||||
|
<property name="allowByTailComment" value="true"/>
|
||||||
|
<property name="allowNonPrintableEscapes" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
<module name="OneTopLevelClass"/>
|
||||||
|
<module name="NoLineWrap"/>
|
||||||
|
<module name="EmptyBlock">
|
||||||
|
<property name="option" value="TEXT"/>
|
||||||
|
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
||||||
|
</module>
|
||||||
|
<module name="NeedBraces"/>
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="option" value="alone"/>
|
||||||
|
</module>
|
||||||
|
<!--
|
||||||
|
<module name="WhitespaceAround">
|
||||||
|
<property name="allowEmptyConstructors" value="true"/>
|
||||||
|
<property name="allowEmptyMethods" value="true"/>
|
||||||
|
<property name="allowEmptyTypes" value="true"/>
|
||||||
|
<property name="allowEmptyLoops" value="true"/>
|
||||||
|
<message key="ws.notFollowed"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||||
|
<message key="ws.notPreceded"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
<module name="OneStatementPerLine"/>
|
||||||
|
<module name="MultipleVariableDeclarations"/>
|
||||||
|
<module name="ArrayTypeStyle"/>
|
||||||
|
<module name="MissingSwitchDefault"/>
|
||||||
|
<module name="FallThrough"/>
|
||||||
|
<module name="UpperEll"/>
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
<module name="EmptyLineSeparator">
|
||||||
|
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapDot"/>
|
||||||
|
<property name="tokens" value="DOT"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapComma"/>
|
||||||
|
<property name="tokens" value="COMMA"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||||
|
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||||
|
<property name="tokens" value="ELLIPSIS"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||||
|
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||||
|
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapMethodRef"/>
|
||||||
|
<property name="tokens" value="METHOD_REF"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="PackageName">
|
||||||
|
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="TypeName">
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MemberName">
|
||||||
|
<property name="format" value="^[_a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern" value="Member name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ParameterName">
|
||||||
|
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern" value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="CatchParameterName">
|
||||||
|
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern" value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="LocalVariableName">
|
||||||
|
<property name="tokens" value="VARIABLE_DEF"/>
|
||||||
|
<property name="format" value="^[a-zA-Z]([a-z0-9A-Z][a-zA-Z0-9]*)?$"/>
|
||||||
|
<message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ClassTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
|
||||||
|
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
|
||||||
|
<message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="InterfaceTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][_A-Z0-9]*$)"/>
|
||||||
|
<message key="name.invalidPattern" value="Interface type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="NoFinalizer"/>
|
||||||
|
<module name="GenericWhitespace">
|
||||||
|
<message key="ws.followed" value="GenericWhitespace ''{0}'' is followed by whitespace."/>
|
||||||
|
<message key="ws.preceded" value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
|
||||||
|
<message key="ws.illegalFollow" value="GenericWhitespace ''{0}'' should followed by whitespace."/>
|
||||||
|
<message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
<module name="Indentation">
|
||||||
|
<property name="basicOffset" value="3"/>
|
||||||
|
<property name="braceAdjustment" value="0"/>
|
||||||
|
<property name="caseIndent" value="3"/>
|
||||||
|
<property name="throwsIndent" value="6"/>
|
||||||
|
<property name="lineWrappingIndentation" value="3"/>
|
||||||
|
<property name="arrayInitIndent" value="2"/>
|
||||||
|
</module>
|
||||||
|
<!--
|
||||||
|
<module name="AbbreviationAsWordInName">
|
||||||
|
<property name="ignoreFinal" value="false"/>
|
||||||
|
<property name="allowedAbbreviationLength" value="1"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
<module name="OverloadMethodsDeclarationOrder"/>
|
||||||
|
<module name="VariableDeclarationUsageDistance"/>
|
||||||
|
<!--
|
||||||
|
<module name="CustomImportOrder">
|
||||||
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
|
<property name="separateLineBetweenGroups" value="true"/>
|
||||||
|
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
<module name="CustomImportOrder">
|
||||||
|
<property name="customImportOrderRules" value="SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###STATIC"/>
|
||||||
|
<property name="specialImportsRegExp" value="^javax\."/>
|
||||||
|
<property name="standardPackageRegExp" value="^java\."/>
|
||||||
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
|
<property name="separateLineBetweenGroups" value="false"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodParamPad"/>
|
||||||
|
<module name="NoWhitespaceBefore">
|
||||||
|
<property name="tokens" value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="ParenPad"/>
|
||||||
|
<module name="OperatorWrap">
|
||||||
|
<property name="option" value="NL"/>
|
||||||
|
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationMostCases"/>
|
||||||
|
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationVariables"/>
|
||||||
|
<property name="tokens" value="VARIABLE_DEF"/>
|
||||||
|
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="NonEmptyAtclauseDescription"/>
|
||||||
|
<!-- <module name="JavadocTagContinuationIndentation"/> -->
|
||||||
|
<!--
|
||||||
|
<module name="SummaryJavadoc">
|
||||||
|
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||||
|
</module>
|
||||||
|
-->
|
||||||
|
<!-- <module name="JavadocParagraph"/> -->
|
||||||
|
<module name="AtclauseOrder">
|
||||||
|
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||||
|
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="JavadocMethod">
|
||||||
|
<property name="allowMissingParamTags" value="true"/>
|
||||||
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
|
</module>
|
||||||
|
<module name="MissingJavadocMethod">
|
||||||
|
<property name="scope" value="private"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodName">
|
||||||
|
<property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="SingleLineJavadoc">
|
||||||
|
<property name="ignoreInlineTags" value="false"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="MagicNumber">
|
||||||
|
<property name="severity" value="info"/>
|
||||||
|
<property name="tokens" value="NUM_DOUBLE, NUM_FLOAT, NUM_INT"/>
|
||||||
|
<property name="ignoreNumbers" value="0, 1, 2, 3, 4, 5, 6, 7, 8"/>
|
||||||
|
<property name="ignoreFieldDeclaration" value="true"/>
|
||||||
|
<property name="ignoreAnnotation" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="EmptyCatchBlock">
|
||||||
|
<property name="exceptionVariableName" value="expected"/>
|
||||||
|
</module>
|
||||||
|
<module name="CommentsIndentation"/>
|
||||||
|
|
||||||
|
<!-- this would be nice, but requires the the @inheritDoc javadoc tag... -->
|
||||||
|
<module name="MissingOverride"/>
|
||||||
|
|
||||||
|
</module>
|
||||||
|
</module>
|
129
pom.xml
Normal file
129
pom.xml
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.kingsrook.qqq</groupId>
|
||||||
|
<artifactId>qqq-middleware-picocli</artifactId>
|
||||||
|
<version>0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- props specifically to this module -->
|
||||||
|
<!-- none at this time -->
|
||||||
|
|
||||||
|
<!-- Common props for all qqq modules -->
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
|
||||||
|
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- other qqq modules deps -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.kingsrook.qqq</groupId>
|
||||||
|
<artifactId>qqq-backend-core</artifactId>
|
||||||
|
<version>0.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.kingsrook.qqq</groupId>
|
||||||
|
<artifactId>qqq-backend-module-rdbms</artifactId>
|
||||||
|
<version>0.0-SNAPSHOT</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 3rd party deps specifically for this module -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli</artifactId>
|
||||||
|
<version>4.6.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>1.4.197</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Common deps for all qqq modules -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-api</artifactId>
|
||||||
|
<version>2.14.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>2.14.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>5.8.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- plugins specifically for this module -->
|
||||||
|
<!-- none at this time -->
|
||||||
|
|
||||||
|
<!-- Common plugins for all qqq modules -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<compilerArgument>-Xlint:unchecked</compilerArgument>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M5</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<version>9.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>validate</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<configuration>
|
||||||
|
<configLocation>checkstyle.xml</configLocation>
|
||||||
|
<!-- <suppressionsLocation>checkstyle-suppressions.xml</suppressionsLocation> -->
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
<failsOnError>false</failsOnError>
|
||||||
|
<failOnViolation>true</failOnViolation>
|
||||||
|
<violationSeverity>warning</violationSeverity>
|
||||||
|
<excludes>**/target/generated-sources/*.*</excludes>
|
||||||
|
<!-- <linkXRef>false</linkXRef> -->
|
||||||
|
</configuration>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,489 @@
|
|||||||
|
package com.kingsrook.qqq.frontend.picocli;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.InsertAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.QueryAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
|
||||||
|
import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter;
|
||||||
|
import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.InsertRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.InsertResult;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.QCriteriaOperator;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.QFilterCriteria;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.QueryRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.QueryResult;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
import picocli.CommandLine.Model.CommandSpec;
|
||||||
|
import picocli.CommandLine.Model.OptionSpec;
|
||||||
|
import picocli.CommandLine.ParameterException;
|
||||||
|
import picocli.CommandLine.ParseResult;
|
||||||
|
import picocli.CommandLine.UnmatchedArgumentException;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Note: Please do not use System.out or .err here -- rather, use the CommandLine
|
||||||
|
** object's out & err members - so the unit test can see the output!
|
||||||
|
*
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QPicoCliImplementation
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(QPicoCliImplementation.class);
|
||||||
|
|
||||||
|
public static final int DEFAULT_LIMIT = 20;
|
||||||
|
|
||||||
|
private static QInstance qInstance;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
QInstance qInstance = new QInstance();
|
||||||
|
|
||||||
|
// todo - parse args to look up metaData and prime instance
|
||||||
|
// todo - authentication
|
||||||
|
// qInstance.addBackend(QMetaDataProvider.getQBackend());
|
||||||
|
|
||||||
|
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
||||||
|
int exitCode = qPicoCliImplementation.runCli("qapi", args);
|
||||||
|
System.exit(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QPicoCliImplementation(QInstance qInstance)
|
||||||
|
{
|
||||||
|
QPicoCliImplementation.qInstance = qInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public int runCli(String name, String[] args)
|
||||||
|
{
|
||||||
|
return (runCli(name, args, System.out, System.err));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** examples - todo, make docs complete!
|
||||||
|
** my-app-cli [--all] [--format=]
|
||||||
|
** my-app-cli $table meta-data [--format=]
|
||||||
|
** my-app-cli $table query [--filterId=]|[--filter=]|[--criteria=...]
|
||||||
|
** my-app-cli $table get (--primaryKey=|--$uc=...)
|
||||||
|
** my-app-cli $table delete (--primaryKey=|--$uc=...)
|
||||||
|
** my-app-cli $table insert (--body=|--$field=...)
|
||||||
|
** my-app-cli $table update (--primaryKey=|--$uc=...) (--body=|--$field=...)
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public int runCli(String name, String[] args, PrintStream out, PrintStream err)
|
||||||
|
{
|
||||||
|
//////////////////////////////////
|
||||||
|
// define the top-level command //
|
||||||
|
//////////////////////////////////
|
||||||
|
CommandSpec topCommandSpec = CommandSpec.create();
|
||||||
|
topCommandSpec.name(name);
|
||||||
|
topCommandSpec.version(name + " v1.0"); // todo... uh?
|
||||||
|
topCommandSpec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
|
||||||
|
topCommandSpec.addOption(OptionSpec.builder("-m", "--meta-data")
|
||||||
|
.type(boolean.class)
|
||||||
|
.description("Output the meta-data for this CLI")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// add each table as a sub-command //
|
||||||
|
/////////////////////////////////////
|
||||||
|
qInstance.getTables().keySet().stream().sorted().forEach(tableName ->
|
||||||
|
{
|
||||||
|
QTableMetaData table = qInstance.getTable(tableName);
|
||||||
|
|
||||||
|
CommandSpec tableCommand = CommandSpec.create();
|
||||||
|
topCommandSpec.addSubcommand(table.getName(), tableCommand);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
// add table-specific sub-commands for the table //
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table));
|
||||||
|
tableCommand.addSubcommand("query", defineQueryCommand(table));
|
||||||
|
tableCommand.addSubcommand("insert", defineInsertCommand(table));
|
||||||
|
});
|
||||||
|
|
||||||
|
CommandLine commandLine = new CommandLine(topCommandSpec);
|
||||||
|
commandLine.setOut(new PrintWriter(out, true));
|
||||||
|
commandLine.setErr(new PrintWriter(err, true));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ParseResult parseResult = commandLine.parseArgs(args);
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// Did user request usage help (--help)? //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
if(commandLine.isUsageHelpRequested())
|
||||||
|
{
|
||||||
|
commandLine.usage(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||||
|
}
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// Did user request version help (--version)? //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
else if(commandLine.isVersionHelpRequested())
|
||||||
|
{
|
||||||
|
commandLine.printVersionHelp(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnVersionHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// else, run the command //
|
||||||
|
///////////////////////////
|
||||||
|
return run(commandLine, parseResult);
|
||||||
|
}
|
||||||
|
catch(ParameterException ex)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
// handle command-line/param parsing exceptions //
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
commandLine.getErr().println(ex.getMessage());
|
||||||
|
UnmatchedArgumentException.printSuggestions(ex, commandLine.getErr());
|
||||||
|
ex.getCommandLine().usage(commandLine.getErr());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnInvalidInput();
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// handle exceptions from business logic //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
commandLine.getErr().println("Error: " + ex.getMessage());
|
||||||
|
return (commandLine.getCommandSpec().exitCodeOnExecutionException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private CommandSpec defineMetaDataCommand(QTableMetaData table)
|
||||||
|
{
|
||||||
|
return CommandSpec.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private CommandSpec defineQueryCommand(QTableMetaData table)
|
||||||
|
{
|
||||||
|
CommandSpec queryCommand = CommandSpec.create();
|
||||||
|
queryCommand.addOption(OptionSpec.builder("-l", "--limit")
|
||||||
|
.type(int.class)
|
||||||
|
.build());
|
||||||
|
queryCommand.addOption(OptionSpec.builder("-s", "--skip")
|
||||||
|
.type(int.class)
|
||||||
|
.build());
|
||||||
|
queryCommand.addOption(OptionSpec.builder("-c", "--criteria")
|
||||||
|
.type(String[].class)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// todo - add the fields as explicit params?
|
||||||
|
|
||||||
|
return queryCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("checkstyle:Indentation")
|
||||||
|
private CommandSpec defineInsertCommand(QTableMetaData table)
|
||||||
|
{
|
||||||
|
CommandSpec insertCommand = CommandSpec.create();
|
||||||
|
|
||||||
|
insertCommand.addOption(OptionSpec.builder("--jsonBody")
|
||||||
|
.type(String.class)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
insertCommand.addOption(OptionSpec.builder("--jsonFile")
|
||||||
|
.type(String.class)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
insertCommand.addOption(OptionSpec.builder("--csvFile")
|
||||||
|
.type(String.class)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
insertCommand.addOption(OptionSpec.builder("--mapping")
|
||||||
|
.type(String.class)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
for(QFieldMetaData field : table.getFields().values())
|
||||||
|
{
|
||||||
|
insertCommand.addOption(OptionSpec.builder("--field-" + field.getName())
|
||||||
|
.type(
|
||||||
|
switch(field.getType())
|
||||||
|
{
|
||||||
|
case STRING, TEXT, HTML, PASSWORD -> String.class;
|
||||||
|
case INTEGER -> Integer.class;
|
||||||
|
case DECIMAL -> BigDecimal.class;
|
||||||
|
case DATE -> LocalDate.class;
|
||||||
|
case TIME -> LocalTime.class;
|
||||||
|
case DATE_TIME -> LocalDateTime.class;
|
||||||
|
default -> throw new IllegalStateException("Unsupported field type: " + field.getType());
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
return insertCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int run(CommandLine commandLine, ParseResult parseResult) throws QException
|
||||||
|
{
|
||||||
|
if(!parseResult.hasSubcommand())
|
||||||
|
{
|
||||||
|
return runTopLevelCommand(commandLine, parseResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String subCommandName = parseResult.subcommand().commandSpec().name();
|
||||||
|
CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName);
|
||||||
|
return runTableLevelCommand(subCommandLine, parseResult.subcommand());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int runTableLevelCommand(CommandLine commandLine, ParseResult tableParseResult) throws QException
|
||||||
|
{
|
||||||
|
String tableName = tableParseResult.commandSpec().name();
|
||||||
|
|
||||||
|
if(tableParseResult.hasSubcommand())
|
||||||
|
{
|
||||||
|
ParseResult subParseResult = tableParseResult.subcommand();
|
||||||
|
switch(subParseResult.commandSpec().name())
|
||||||
|
{
|
||||||
|
case "meta-data":
|
||||||
|
{
|
||||||
|
return runTableMetaData(commandLine, tableName, subParseResult);
|
||||||
|
}
|
||||||
|
case "query":
|
||||||
|
{
|
||||||
|
return runTableQuery(commandLine, tableName, subParseResult);
|
||||||
|
}
|
||||||
|
case "insert":
|
||||||
|
{
|
||||||
|
return runTableInsert(commandLine, tableName, subParseResult);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
commandLine.getErr().println("Unknown command: " + subParseResult.commandSpec().name());
|
||||||
|
commandLine.usage(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandLine.usage(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int runTableMetaData(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException
|
||||||
|
{
|
||||||
|
TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance);
|
||||||
|
tableMetaDataRequest.setTableName(tableName);
|
||||||
|
TableMetaDataAction tableMetaDataAction = new TableMetaDataAction();
|
||||||
|
TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest);
|
||||||
|
commandLine.getOut().println(JsonUtils.toPrettyJson(tableMetaDataResult));
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int runTableQuery(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException
|
||||||
|
{
|
||||||
|
QueryRequest queryRequest = new QueryRequest(qInstance);
|
||||||
|
queryRequest.setTableName(tableName);
|
||||||
|
queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null));
|
||||||
|
queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT));
|
||||||
|
|
||||||
|
QQueryFilter filter = new QQueryFilter();
|
||||||
|
queryRequest.setFilter(filter);
|
||||||
|
|
||||||
|
String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {});
|
||||||
|
for(String criterion : criteria)
|
||||||
|
{
|
||||||
|
// todo - parse!
|
||||||
|
String[] parts = criterion.split(" ");
|
||||||
|
QFilterCriteria qQueryCriteria = new QFilterCriteria();
|
||||||
|
qQueryCriteria.setFieldName(parts[0]);
|
||||||
|
qQueryCriteria.setOperator(QCriteriaOperator.valueOf(parts[1]));
|
||||||
|
qQueryCriteria.setValues(List.of(parts[2]));
|
||||||
|
filter.addCriteria(qQueryCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryAction queryAction = new QueryAction();
|
||||||
|
QueryResult queryResult = queryAction.execute(queryRequest);
|
||||||
|
commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult));
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int runTableInsert(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException
|
||||||
|
{
|
||||||
|
InsertRequest insertRequest = new InsertRequest(qInstance);
|
||||||
|
insertRequest.setTableName(tableName);
|
||||||
|
QTableMetaData table = qInstance.getTable(tableName);
|
||||||
|
|
||||||
|
AbstractQFieldMapping<?> mapping = null;
|
||||||
|
|
||||||
|
if(subParseResult.hasMatchedOption("--mapping"))
|
||||||
|
{
|
||||||
|
String json = subParseResult.matchedOptionValue("--mapping", "");
|
||||||
|
mapping = new JsonToQFieldMappingAdapter().buildMappingFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// get the records that the user specified //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
List<QRecord> recordList = null;
|
||||||
|
if(subParseResult.hasMatchedOption("--jsonBody"))
|
||||||
|
{
|
||||||
|
String json = subParseResult.matchedOptionValue("--jsonBody", "");
|
||||||
|
recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping);
|
||||||
|
}
|
||||||
|
else if(subParseResult.hasMatchedOption("--jsonFile"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String path = subParseResult.matchedOptionValue("--jsonFile", "");
|
||||||
|
String json = FileUtils.readFileToString(new File(path));
|
||||||
|
recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error building records from file:" + e.getMessage(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(subParseResult.hasMatchedOption("--csvFile"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String path = subParseResult.matchedOptionValue("--csvFile", "");
|
||||||
|
String csv = FileUtils.readFileToString(new File(path));
|
||||||
|
recordList = new CsvToQRecordAdapter().buildRecordsFromCsv(csv, table, mapping);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
throw (new QException("Error building records from file:" + e.getMessage(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QRecord record = new QRecord();
|
||||||
|
recordList = new ArrayList<>();
|
||||||
|
recordList.add(record);
|
||||||
|
|
||||||
|
boolean anyFields = false;
|
||||||
|
for(OptionSpec matchedOption : subParseResult.matchedOptions())
|
||||||
|
{
|
||||||
|
if(matchedOption.longestName().startsWith("--field-"))
|
||||||
|
{
|
||||||
|
anyFields = true;
|
||||||
|
String fieldName = matchedOption.longestName().substring(8);
|
||||||
|
record.setValue(fieldName, matchedOption.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!anyFields)
|
||||||
|
{
|
||||||
|
CommandLine subCommandLine = commandLine.getSubcommands().get("insert");
|
||||||
|
subCommandLine.usage(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insertRequest.setRecords(recordList);
|
||||||
|
|
||||||
|
InsertAction insertAction = new InsertAction();
|
||||||
|
InsertResult insertResult = insertAction.execute(insertRequest);
|
||||||
|
commandLine.getOut().println(JsonUtils.toPrettyJson(insertResult));
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private int runTopLevelCommand(CommandLine commandLine, ParseResult parseResult) throws QException
|
||||||
|
{
|
||||||
|
if(parseResult.hasMatchedOption("--meta-data"))
|
||||||
|
{
|
||||||
|
MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance);
|
||||||
|
MetaDataAction metaDataAction = new MetaDataAction();
|
||||||
|
MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest);
|
||||||
|
commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataResult));
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
commandLine.usage(commandLine.getOut());
|
||||||
|
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,308 @@
|
|||||||
|
package com.kingsrook.qqq.frontend.picocli;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
class QPicoCliImplementationTest
|
||||||
|
{
|
||||||
|
private static final boolean VERBOSE = true;
|
||||||
|
private static final String CLI_NAME = "cli-unit-test";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() throws Exception
|
||||||
|
{
|
||||||
|
TestUtils.primeTestDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_noArgs()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli();
|
||||||
|
assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_help()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("--help");
|
||||||
|
assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME));
|
||||||
|
assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*person.*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_version()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("--version");
|
||||||
|
assertTrue(testOutput.getOutput().contains(CLI_NAME + " v1.0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_badOption()
|
||||||
|
{
|
||||||
|
String badOption = "--asdf";
|
||||||
|
TestOutput testOutput = testCli(badOption);
|
||||||
|
assertTrue(testOutput.getError().contains("Unknown option: '" + badOption + "'"));
|
||||||
|
assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_metaData()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("--meta-data");
|
||||||
|
JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||||
|
assertNotNull(metaData);
|
||||||
|
assertEquals(1, metaData.keySet().size(), "Number of top-level keys");
|
||||||
|
assertTrue(metaData.has("tables"));
|
||||||
|
JSONObject tables = metaData.getJSONObject("tables");
|
||||||
|
JSONObject personTable = tables.getJSONObject("person");
|
||||||
|
assertEquals("person", personTable.getString("name"));
|
||||||
|
assertEquals("Person", personTable.getString("label"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_table()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("person");
|
||||||
|
assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person [COMMAND]"));
|
||||||
|
assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*query.*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_tableMetaData()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("person", "meta-data");
|
||||||
|
JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||||
|
assertNotNull(metaData);
|
||||||
|
assertEquals(1, metaData.keySet().size(), "Number of top-level keys");
|
||||||
|
JSONObject table = metaData.getJSONObject("table");
|
||||||
|
assertEquals(4, table.keySet().size(), "Number of mid-level keys");
|
||||||
|
assertEquals("person", table.getString("name"));
|
||||||
|
assertEquals("Person", table.getString("label"));
|
||||||
|
assertEquals("id", table.getString("primaryKeyField"));
|
||||||
|
JSONObject fields = table.getJSONObject("fields");
|
||||||
|
JSONObject field0 = fields.getJSONObject("id");
|
||||||
|
assertEquals("id", field0.getString("name"));
|
||||||
|
assertEquals("INTEGER", field0.getString("type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void test_tableQuery()
|
||||||
|
{
|
||||||
|
TestOutput testOutput = testCli("person", "query", "--skip=1", "--limit=2", "--criteria", "id NOT_EQUALS 3");
|
||||||
|
JSONObject queryResult = JsonUtils.toJSONObject(testOutput.getOutput());
|
||||||
|
assertNotNull(queryResult);
|
||||||
|
assertEquals(2, queryResult.getJSONArray("records").length());
|
||||||
|
// query for id != 3, and skipping 1, expect to get back rows 2 & 4
|
||||||
|
assertEquals(2, queryResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
|
||||||
|
assertEquals(4, queryResult.getJSONArray("records").getJSONObject(1).getInt("primaryKey"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private TestOutput testCli(String... args)
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
if(VERBOSE)
|
||||||
|
{
|
||||||
|
System.out.println("> " + CLI_NAME + (args == null ? "" : " " + StringUtils.join(" ", Arrays.stream(args).toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
qPicoCliImplementation.runCli(CLI_NAME, args, new PrintStream(outputStream, true), new PrintStream(errorStream, true));
|
||||||
|
|
||||||
|
String output = outputStream.toString(StandardCharsets.UTF_8);
|
||||||
|
String error = errorStream.toString(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if(VERBOSE)
|
||||||
|
{
|
||||||
|
System.out.println(output);
|
||||||
|
System.err.println(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestOutput testOutput = new TestOutput(output, error);
|
||||||
|
return (testOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static class TestOutput
|
||||||
|
{
|
||||||
|
private String output;
|
||||||
|
private String[] outputLines;
|
||||||
|
private String error;
|
||||||
|
private String[] errorLines;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public TestOutput(String output, String error)
|
||||||
|
{
|
||||||
|
this.output = output;
|
||||||
|
this.error = error;
|
||||||
|
|
||||||
|
this.outputLines = output.split("\n");
|
||||||
|
this.errorLines = error.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for output
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getOutput()
|
||||||
|
{
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for output
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOutput(String output)
|
||||||
|
{
|
||||||
|
this.output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for outputLines
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String[] getOutputLines()
|
||||||
|
{
|
||||||
|
return outputLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for outputLines
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setOutputLines(String[] outputLines)
|
||||||
|
{
|
||||||
|
this.outputLines = outputLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for error
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getError()
|
||||||
|
{
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for error
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setError(String error)
|
||||||
|
{
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for errorLines
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String[] getErrorLines()
|
||||||
|
{
|
||||||
|
return errorLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for errorLines
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setErrorLines(String[] errorLines)
|
||||||
|
{
|
||||||
|
this.errorLines = errorLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package com.kingsrook.qqq.frontend.picocli;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||||
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import static junit.framework.Assert.assertNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class TestUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void primeTestDatabase() throws Exception
|
||||||
|
{
|
||||||
|
ConnectionManager connectionManager = new ConnectionManager();
|
||||||
|
Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend()));
|
||||||
|
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
|
||||||
|
assertNotNull(primeTestDatabaseSqlStream);
|
||||||
|
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
|
||||||
|
String joinedSQL = String.join("\n", lines);
|
||||||
|
for(String sql : joinedSQL.split(";"))
|
||||||
|
{
|
||||||
|
QueryManager.executeUpdate(connection, sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QInstance defineInstance()
|
||||||
|
{
|
||||||
|
QInstance qInstance = new QInstance();
|
||||||
|
qInstance.addBackend(defineBackend());
|
||||||
|
qInstance.addTable(defineTablePerson());
|
||||||
|
return (qInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QBackendMetaData defineBackend()
|
||||||
|
{
|
||||||
|
return new QBackendMetaData()
|
||||||
|
.withName("default")
|
||||||
|
.withType("rdbms")
|
||||||
|
.withValue("vendor", "h2")
|
||||||
|
.withValue("hostName", "mem")
|
||||||
|
.withValue("databaseName", "test_database")
|
||||||
|
.withValue("username", "sa")
|
||||||
|
.withValue("password", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static QTableMetaData defineTablePerson()
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName("person")
|
||||||
|
.withLabel("Person")
|
||||||
|
.withBackendName(defineBackend().getName())
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date"))
|
||||||
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date"))
|
||||||
|
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name"))
|
||||||
|
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name"))
|
||||||
|
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
|
||||||
|
.withField(new QFieldMetaData("email", QFieldType.STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
src/test/resources/prime-test-database.sql
Normal file
18
src/test/resources/prime-test-database.sql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
DROP TABLE IF EXISTS person;
|
||||||
|
CREATE TABLE person
|
||||||
|
(
|
||||||
|
id SERIAL,
|
||||||
|
create_date TIMESTAMP DEFAULT now(),
|
||||||
|
modify_date TIMESTAMP DEFAULT now(),
|
||||||
|
|
||||||
|
first_name VARCHAR(80) NOT NULL,
|
||||||
|
last_name VARCHAR(80) NOT NULL,
|
||||||
|
birth_date DATE,
|
||||||
|
email VARCHAR(250) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com');
|
||||||
|
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com');
|
||||||
|
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com');
|
||||||
|
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', '1990-01-01', 'tsamples@mmltholdings.com');
|
||||||
|
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');
|
Reference in New Issue
Block a user