├── .github ├── watch.png ├── include_hierarchy_example.png └── workflows │ └── main.yml ├── beancount.dic ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── jbeancount ├── src │ ├── main │ │ └── java │ │ │ └── nl │ │ │ └── jrdie │ │ │ └── beancount │ │ │ ├── language │ │ │ ├── TagOrLink.java │ │ │ ├── MetadataLine.java │ │ │ ├── CostCompValue.java │ │ │ ├── Tag.java │ │ │ ├── Link.java │ │ │ ├── CompoundExpression.java │ │ │ ├── MetadataValue.java │ │ │ ├── PragmaNode.java │ │ │ ├── Flag.java │ │ │ ├── WithComment.java │ │ │ ├── ArithmeticExpression.java │ │ │ ├── JournalDeclaration.java │ │ │ ├── ScalarValue.java │ │ │ ├── DirectiveNode.java │ │ │ ├── LinkAndTagContainer.java │ │ │ ├── NilValue.java │ │ │ ├── PlusExpression.java │ │ │ ├── NegationExpression.java │ │ │ ├── TxnFlag.java │ │ │ ├── AdditionExpression.java │ │ │ ├── DivisionExpression.java │ │ │ ├── ParenthesisedExpression.java │ │ │ ├── SubtractionExpression.java │ │ │ ├── BooleanValue.java │ │ │ ├── MetadataKey.java │ │ │ ├── MultiplicationExpression.java │ │ │ ├── TagValue.java │ │ │ ├── LinkValue.java │ │ │ ├── Account.java │ │ │ ├── StringValue.java │ │ │ ├── NumberValue.java │ │ │ ├── DateValue.java │ │ │ ├── Commodity.java │ │ │ ├── PopTagPragma.java │ │ │ ├── PushTagPragma.java │ │ │ ├── ConstantExpression.java │ │ │ ├── Node.java │ │ │ ├── UnaryCompoundExpression.java │ │ │ ├── tools │ │ │ │ ├── internal │ │ │ │ │ ├── AstNodeAdapter.java │ │ │ │ │ └── NodeChildrenContainer.java │ │ │ │ ├── AstTransformer.java │ │ │ │ ├── NodeUtil.java │ │ │ │ ├── NestJournalNodeInIncludePragmaTransformer.java │ │ │ │ ├── NodeVisitor.java │ │ │ │ └── NodeVisitorStub.java │ │ │ ├── AbstractUnaryArithmeticExpression.java │ │ │ ├── MetadataItem.java │ │ │ ├── Eol.java │ │ │ ├── Metadata.java │ │ │ ├── CostSpec.java │ │ │ ├── CompoundAmount.java │ │ │ ├── AbstractPragmaNode.java │ │ │ ├── BinaryCompoundExpression.java │ │ │ ├── SourceLocation.java │ │ │ ├── Comment.java │ │ │ ├── PriceAnnotation.java │ │ │ ├── Amount.java │ │ │ ├── AbstractNode.java │ │ │ ├── AbstractBinaryArithmeticExpression.java │ │ │ ├── SymbolFlag.java │ │ │ ├── OptionPragma.java │ │ │ ├── PluginPragma.java │ │ │ ├── IncludePragma.java │ │ │ ├── CloseDirective.java │ │ │ ├── CommodityDirective.java │ │ │ ├── Journal.java │ │ │ ├── QueryDirective.java │ │ │ ├── PriceDirective.java │ │ │ ├── DocumentDirective.java │ │ │ ├── EventDirective.java │ │ │ ├── BalanceDirective.java │ │ │ ├── NoteDirective.java │ │ │ ├── PadDirective.java │ │ │ ├── CustomDirective.java │ │ │ ├── OpenDirective.java │ │ │ ├── TransactionDirective.java │ │ │ ├── AbstractDirectiveNode.java │ │ │ └── Posting.java │ │ │ ├── parser │ │ │ ├── BeancountParserOptions.java │ │ │ ├── CharStreamFunction.java │ │ │ ├── InvalidSyntaxException.java │ │ │ ├── AntlrHelper.java │ │ │ ├── BeancountUtil.java │ │ │ └── BeancountParser.java │ │ │ ├── annotation │ │ │ └── Beta.java │ │ │ ├── BeancountInvalidStateException.java │ │ │ ├── util │ │ │ ├── Assert.java │ │ │ └── ImmutableKit.java │ │ │ ├── io │ │ │ ├── BeancountPrinter.java │ │ │ └── BeancountIOException.java │ │ │ ├── BeancountException.java │ │ │ ├── construe │ │ │ ├── BeancountConstrueStrategy.java │ │ │ ├── AsyncConstrueStrategy.java │ │ │ └── SyncConstrueStrategy.java │ │ │ └── Beancount.java │ └── test │ │ └── java │ │ └── nl │ │ └── jrdie │ │ └── beancount │ │ ├── testing │ │ ├── ExpectedToken.java │ │ └── TestUtil.java │ │ ├── parser │ │ ├── StringParseTest.java │ │ └── NumberParseTest.java │ │ └── lexer │ │ ├── StringLexTest.java │ │ └── NumberLexTest.java └── build.gradle.kts ├── test-dont-push └── build.gradle.kts ├── jbeancount-cli ├── src │ └── main │ │ └── java │ │ └── nl │ │ └── jrdie │ │ └── beancount │ │ └── cli │ │ ├── internal │ │ ├── include │ │ │ └── IncludePair.java │ │ └── transformations │ │ │ ├── SortTransactions.java │ │ │ └── FlattenJournal.java │ │ ├── commands │ │ ├── jordie │ │ │ ├── JordieCommand.java │ │ │ ├── JordieMoveToDesc.java │ │ │ └── FixStuffCommand.java │ │ ├── InternalCommand.java │ │ ├── mixin │ │ │ └── SingleOutput.java │ │ ├── CheckJournal.java │ │ ├── FormatJournal.java │ │ ├── SortJournal.java │ │ ├── LexCommand.java │ │ ├── MergeJournal.java │ │ └── IncludeTreeCommand.java │ │ ├── picocli │ │ ├── PathConverter.java │ │ └── BeancountExecutionExceptionHandler.java │ │ └── BeancountCli.java └── build.gradle.kts ├── settings.gradle.kts ├── gradlew.bat └── gradlew /.github/watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jord1e/jbeancount/HEAD/.github/watch.png -------------------------------------------------------------------------------- /beancount.dic: -------------------------------------------------------------------------------- 1 | Beancount 2 | beancount 3 | jbeancount 4 | jrdie 5 | buildtools 6 | Aproject 7 | diffplug 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jord1e/jbeancount/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.github/include_hierarchy_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jord1e/jbeancount/HEAD/.github/include_hierarchy_example.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /*.iml 3 | 4 | /.gradle/ 5 | /build/ 6 | /*/build/ 7 | 8 | /test-dont-push/* 9 | !/test-dont-push/build.gradle.kts 10 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/TagOrLink.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface TagOrLink permits Link, Tag {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/BeancountParserOptions.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | public final class BeancountParserOptions {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/annotation/Beta.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.annotation; 2 | 3 | public @interface Beta { 4 | 5 | String value() default ""; 6 | } 7 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/BeancountInvalidStateException.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount; 2 | 3 | public class BeancountInvalidStateException extends RuntimeException {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/testing/ExpectedToken.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.testing; 2 | 3 | public record ExpectedToken(String name, String text, int line, int col) {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/MetadataLine.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface MetadataLine permits Comment, Link, MetadataItem, Tag {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CostCompValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface CostCompValue permits CompoundAmount, DateValue, StringValue {} 4 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Tag.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface Tag extends TagOrLink, MetadataLine permits TagValue { 4 | 5 | String tag(); 6 | } 7 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Link.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface Link extends TagOrLink, MetadataLine permits LinkValue { 4 | 5 | String link(); 6 | } 7 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CompoundExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface CompoundExpression 4 | permits BinaryCompoundExpression, UnaryCompoundExpression {} 5 | -------------------------------------------------------------------------------- /test-dont-push/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | dependencies { 6 | implementation(project(":jbeancount")) 7 | } 8 | 9 | application { 10 | mainClass.set("nl.jrdie.beancount.test.Main") 11 | } 12 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/internal/include/IncludePair.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.internal.include; 2 | 3 | import java.nio.file.Path; 4 | 5 | public record IncludePair(Path fromJournal, Path toJournal) {} 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/MetadataValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface MetadataValue permits Amount, ScalarValue { 4 | 5 | default boolean empty() { 6 | return false; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "beancount-parser" 9 | 10 | include("jbeancount") 11 | include("jbeancount-cli") 12 | include("test-dont-push") 13 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/util/Assert.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.util; 2 | 3 | public final class Assert { 4 | private Assert() {} 5 | 6 | public static void shouldNeverHappen() { 7 | throw new RuntimeException("Should never happen"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PragmaNode.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface PragmaNode, B extends Node.Builder> 4 | extends Node, JournalDeclaration, WithComment permits AbstractPragmaNode {} 5 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Flag.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface Flag permits SymbolFlag, TxnFlag { 4 | 5 | String flag(); 6 | 7 | boolean star(); 8 | 9 | boolean exclamationMark(); 10 | 11 | boolean txn(); 12 | } 13 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/WithComment.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | public sealed interface WithComment permits DirectiveNode, Posting, PragmaNode { 6 | 7 | @Nullable 8 | Comment comment(); 9 | } 10 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/ArithmeticExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface ArithmeticExpression extends ScalarValue 4 | permits ConstantExpression, 5 | AbstractBinaryArithmeticExpression, 6 | AbstractUnaryArithmeticExpression {} 7 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/CharStreamFunction.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import java.io.IOException; 4 | import org.antlr.v4.runtime.CharStream; 5 | 6 | @FunctionalInterface 7 | public interface CharStreamFunction { 8 | 9 | CharStream apply(T t) throws IOException; 10 | } 11 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/JournalDeclaration.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface JournalDeclaration, B extends Node.Builder> 4 | extends Node 5 | permits AbstractDirectiveNode, AbstractPragmaNode, Comment, DirectiveNode, Eol, PragmaNode {} 6 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/io/BeancountPrinter.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.io; 2 | 3 | import nl.jrdie.beancount.language.Journal; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface BeancountPrinter { 7 | 8 | @NotNull 9 | String print(@NotNull Journal journal) throws BeancountIOException; 10 | } 11 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/io/BeancountIOException.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.io; 2 | 3 | import nl.jrdie.beancount.BeancountException; 4 | 5 | public class BeancountIOException extends BeancountException { 6 | 7 | public BeancountIOException() {} 8 | 9 | public BeancountIOException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/ScalarValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public sealed interface ScalarValue extends MetadataValue 4 | permits Account, 5 | ArithmeticExpression, 6 | BooleanValue, 7 | Commodity, 8 | DateValue, 9 | LinkValue, 10 | NilValue, 11 | StringValue, 12 | TagValue {} 13 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/BeancountException.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount; 2 | 3 | public class BeancountException extends RuntimeException { 4 | public BeancountException() {} 5 | 6 | public BeancountException(String message) { 7 | super(message); 8 | } 9 | 10 | public BeancountException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/construe/BeancountConstrueStrategy.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.construe; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.function.Supplier; 5 | import nl.jrdie.beancount.language.Journal; 6 | 7 | public interface BeancountConstrueStrategy { 8 | 9 | CompletableFuture construe(Supplier journalSupplier); 10 | } 11 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/DirectiveNode.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.time.LocalDate; 4 | 5 | public sealed interface DirectiveNode, B extends Node.Builder> 6 | extends Node, JournalDeclaration, LinkAndTagContainer, WithComment 7 | permits AbstractDirectiveNode { 8 | 9 | LocalDate date(); 10 | 11 | Metadata metadata(); 12 | } 13 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/jordie/JordieCommand.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands.jordie; 2 | 3 | import picocli.CommandLine.Command; 4 | import picocli.CommandLine.ScopeType; 5 | 6 | @Command( 7 | name = "jordie", 8 | description = "Some special tools for myself", 9 | subcommands = {FixStuffCommand.class, JordieMoveToDesc.class}, 10 | scope = ScopeType.INHERIT) 11 | public class JordieCommand {} 12 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/LinkAndTagContainer.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import nl.jrdie.beancount.annotation.Beta; 6 | 7 | public sealed interface LinkAndTagContainer permits AbstractDirectiveNode, DirectiveNode { 8 | 9 | // Ordered 10 | @Beta("Naming uncertain") 11 | List tagsAndLinks(); 12 | 13 | Collection links(); 14 | 15 | Collection tags(); 16 | } 17 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/construe/AsyncConstrueStrategy.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.construe; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.function.Supplier; 5 | import nl.jrdie.beancount.language.Journal; 6 | 7 | public class AsyncConstrueStrategy implements BeancountConstrueStrategy { 8 | @Override 9 | public CompletableFuture construe(Supplier journalSupplier) { 10 | return CompletableFuture.supplyAsync(journalSupplier); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/InternalCommand.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import nl.jrdie.beancount.cli.commands.jordie.JordieCommand; 4 | import picocli.CommandLine; 5 | import picocli.CommandLine.Command; 6 | 7 | @Command( 8 | name = "internal", 9 | description = "Internal commands to debug the utility and library", 10 | subcommands = {LexCommand.class, JordieCommand.class}, 11 | scope = CommandLine.ScopeType.INHERIT) 12 | public class InternalCommand {} 13 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/NilValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class NilValue implements ScalarValue { 4 | 5 | private NilValue() {} 6 | 7 | @Override 8 | public boolean empty() { 9 | return true; 10 | } 11 | 12 | public static Builder newNilValue() { 13 | return new Builder(); 14 | } 15 | 16 | public static final class Builder { 17 | private Builder() {} 18 | 19 | public NilValue build() { 20 | return new NilValue(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/mixin/SingleOutput.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands.mixin; 2 | 3 | import java.nio.file.Path; 4 | import picocli.CommandLine.Option; 5 | 6 | public class SingleOutput { 7 | 8 | @Option( 9 | names = {"-o", "--output"}, 10 | description = "The output file. When this is not specified stdout will be used") 11 | private Path output; 12 | 13 | public Path outputFile() { 14 | return output; 15 | } 16 | 17 | public boolean hasOutput() { 18 | return output != null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jbeancount-cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | id("java") 4 | id("org.graalvm.buildtools.native") version "0.9.4" 5 | } 6 | 7 | dependencies { 8 | implementation(project(":jbeancount")) 9 | implementation("info.picocli:picocli:4.6.3") 10 | annotationProcessor("info.picocli:picocli-codegen:4.6.3") 11 | } 12 | 13 | tasks { 14 | compileJava { 15 | options.compilerArgs.add("-Aproject=${project.group}/${project.name}") 16 | } 17 | } 18 | 19 | application { 20 | mainClass.set("nl.jrdie.beancount.cli.BeancountCli") 21 | } 22 | 23 | nativeBuild { 24 | imageName.set("jbeancount") 25 | } 26 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/picocli/PathConverter.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.picocli; 2 | 3 | import java.nio.file.FileSystems; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | import picocli.CommandLine; 7 | 8 | public class PathConverter implements CommandLine.ITypeConverter { 9 | @Override 10 | public Path convert(String value) throws Exception { 11 | String sep = FileSystems.getDefault().getSeparator(); 12 | if (value != null && !value.contains(sep) && !value.isEmpty() && value.charAt(0) != '.') { 13 | value = "." + sep + value; 14 | } 15 | return Paths.get(value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/construe/SyncConstrueStrategy.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.construe; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.function.Supplier; 5 | import nl.jrdie.beancount.language.Journal; 6 | 7 | public class SyncConstrueStrategy implements BeancountConstrueStrategy { 8 | @Override 9 | public CompletableFuture construe(Supplier journalSupplier) { 10 | try { 11 | final Journal journal = journalSupplier.get(); 12 | return CompletableFuture.completedFuture(journal); 13 | } catch (Exception e) { // TODO Should this be Throwable? 14 | return CompletableFuture.failedFuture(e); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PlusExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class PlusExpression extends AbstractUnaryArithmeticExpression { 4 | 5 | private PlusExpression(ArithmeticExpression expression) { 6 | super(expression); 7 | } 8 | 9 | public static Builder newPlusExpression() { 10 | return new Builder(); 11 | } 12 | 13 | public static final class Builder 14 | extends AbstractUnaryArithmeticExpression.Builder { 15 | 16 | private Builder() { 17 | super(); 18 | } 19 | 20 | @Override 21 | public PlusExpression build() { 22 | return new PlusExpression(expression); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/NegationExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class NegationExpression extends AbstractUnaryArithmeticExpression { 4 | 5 | private NegationExpression(ArithmeticExpression expression) { 6 | super(expression); 7 | } 8 | 9 | public static Builder newNegationExpression() { 10 | return new Builder(); 11 | } 12 | 13 | public static final class Builder 14 | extends AbstractUnaryArithmeticExpression.Builder { 15 | 16 | private Builder() { 17 | super(); 18 | } 19 | 20 | @Override 21 | public NegationExpression build() { 22 | return new NegationExpression(expression); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/TxnFlag.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class TxnFlag implements Flag { 4 | 5 | private TxnFlag() {} 6 | 7 | @Override 8 | public String flag() { 9 | return "txn"; 10 | } 11 | 12 | @Override 13 | public boolean star() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public boolean exclamationMark() { 19 | return false; 20 | } 21 | 22 | @Override 23 | public boolean txn() { 24 | return true; 25 | } 26 | 27 | public static Builder newTxnFlag() { 28 | return new Builder(); 29 | } 30 | 31 | public static final class Builder { 32 | private Builder() {} 33 | 34 | public TxnFlag build() { 35 | return new TxnFlag(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/util/ImmutableKit.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.function.Function; 8 | 9 | public final class ImmutableKit { 10 | 11 | private ImmutableKit() {} 12 | 13 | public static List emptyList() { 14 | return Collections.emptyList(); 15 | } 16 | 17 | public static List map( 18 | Collection collection, Function mapper) { 19 | // TODO Optimise 20 | List list = new ArrayList<>(); 21 | for (T t : collection) { 22 | R r = mapper.apply(t); 23 | list.add(r); 24 | } 25 | return Collections.unmodifiableList(list); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/CheckJournal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.nio.file.Path; 4 | import java.util.concurrent.Callable; 5 | import nl.jrdie.beancount.Beancount; 6 | import nl.jrdie.beancount.language.Journal; 7 | import picocli.CommandLine.Command; 8 | import picocli.CommandLine.Parameters; 9 | 10 | @Command(name = "check", description = "Checks the beancount file for errors") 11 | public class CheckJournal implements Callable { 12 | 13 | @Parameters(index = "0", description = "The Beancount file") 14 | private Path file; 15 | 16 | @Override 17 | public Integer call() { 18 | Beancount beancount = Beancount.newBeancount().build(); 19 | Journal journal = beancount.createJournalSync(file); 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/InvalidSyntaxException.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import java.util.Objects; 4 | import nl.jrdie.beancount.language.SourceLocation; 5 | 6 | public class InvalidSyntaxException extends RuntimeException { 7 | 8 | private final SourceLocation sourceLocation; 9 | private final String preview; 10 | 11 | public InvalidSyntaxException(SourceLocation sourceLocation, String message, String preview) { 12 | super(message); 13 | this.sourceLocation = Objects.requireNonNull(sourceLocation, "sourceLocation"); 14 | this.preview = Objects.requireNonNull(preview, "preview"); 15 | } 16 | 17 | public SourceLocation getSourceLocation() { 18 | return sourceLocation; 19 | } 20 | 21 | public String getPreview() { 22 | return preview; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AdditionExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class AdditionExpression extends AbstractBinaryArithmeticExpression { 4 | 5 | private AdditionExpression( 6 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 7 | super(leftExpression, rightExpression); 8 | } 9 | 10 | public static Builder newAdditionExpression() { 11 | return new Builder(); 12 | } 13 | 14 | public static final class Builder 15 | extends AbstractBinaryArithmeticExpression.Builder { 16 | 17 | private Builder() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public AdditionExpression build() { 23 | return new AdditionExpression(leftExpression, rightExpression); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/DivisionExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class DivisionExpression extends AbstractBinaryArithmeticExpression { 4 | 5 | private DivisionExpression( 6 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 7 | super(leftExpression, rightExpression); 8 | } 9 | 10 | public static Builder newDivisionExpression() { 11 | return new Builder(); 12 | } 13 | 14 | public static final class Builder 15 | extends AbstractBinaryArithmeticExpression.Builder { 16 | 17 | private Builder() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public DivisionExpression build() { 23 | return new DivisionExpression(leftExpression, rightExpression); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/parser/StringParseTest.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import static nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer.STRING; 4 | import static nl.jrdie.beancount.testing.TestUtil.assertToLanguage; 5 | import static nl.jrdie.beancount.testing.TestUtil.t; 6 | 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.ValueSource; 9 | 10 | public class StringParseTest { 11 | 12 | @ParameterizedTest 13 | @ValueSource( 14 | strings = {"", "1", "a", "1b1", "c2c", "\\\"a\\\"", "\\\"", "\\n", "\\t\\n\\r\\f\\b"}) 15 | public void assertToLanguageStringParses(String theString) { 16 | final String tokenText = '"' + theString + '"'; 17 | assertToLanguage(antlr -> antlr.parseStringToken(t(STRING, tokenText))).isEqualTo(theString); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/ParenthesisedExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import nl.jrdie.beancount.annotation.Beta; 4 | 5 | @Beta("Naming uncertain") 6 | public final class ParenthesisedExpression extends AbstractUnaryArithmeticExpression { 7 | 8 | private ParenthesisedExpression(ArithmeticExpression expression) { 9 | super(expression); 10 | } 11 | 12 | public static Builder newParenthesisedExpression() { 13 | return new Builder(); 14 | } 15 | 16 | public static final class Builder 17 | extends AbstractUnaryArithmeticExpression.Builder { 18 | 19 | private Builder() { 20 | super(); 21 | } 22 | 23 | @Override 24 | public ParenthesisedExpression build() { 25 | return new ParenthesisedExpression(expression); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/SubtractionExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class SubtractionExpression extends AbstractBinaryArithmeticExpression { 4 | 5 | private SubtractionExpression( 6 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 7 | super(leftExpression, rightExpression); 8 | } 9 | 10 | public static Builder newSubtractionExpression() { 11 | return new Builder(); 12 | } 13 | 14 | public static final class Builder 15 | extends AbstractBinaryArithmeticExpression.Builder { 16 | 17 | private Builder() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public SubtractionExpression build() { 23 | return new SubtractionExpression(leftExpression, rightExpression); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/BooleanValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class BooleanValue implements ScalarValue { 4 | 5 | private final boolean value; 6 | 7 | private BooleanValue(boolean value) { 8 | this.value = value; 9 | } 10 | 11 | public boolean value() { 12 | return value; 13 | } 14 | 15 | public static Builder newBooleanValue() { 16 | return new Builder(); 17 | } 18 | 19 | public static final class Builder { 20 | private boolean value; 21 | 22 | private Builder() {} 23 | 24 | public BooleanValue build() { 25 | return new BooleanValue(value); 26 | } 27 | 28 | public boolean value() { 29 | return value; 30 | } 31 | 32 | public Builder value(boolean value) { 33 | this.value = value; 34 | return this; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/MetadataKey.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class MetadataKey { 6 | 7 | private final String key; 8 | 9 | private MetadataKey(String key) { 10 | this.key = Objects.requireNonNull(key, "key"); 11 | } 12 | 13 | public String key() { 14 | return key; 15 | } 16 | 17 | public static Builder newMetadataKey() { 18 | return new Builder(); 19 | } 20 | 21 | public static final class Builder { 22 | private String key; 23 | 24 | private Builder() {} 25 | 26 | public MetadataKey build() { 27 | return new MetadataKey(key); 28 | } 29 | 30 | public String key() { 31 | return key; 32 | } 33 | 34 | public Builder key(String key) { 35 | this.key = key; 36 | return this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/MultiplicationExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class MultiplicationExpression extends AbstractBinaryArithmeticExpression { 4 | 5 | private MultiplicationExpression( 6 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 7 | super(leftExpression, rightExpression); 8 | } 9 | 10 | public static Builder newMultiplicationExpression() { 11 | return new Builder(); 12 | } 13 | 14 | public static final class Builder 15 | extends AbstractBinaryArithmeticExpression.Builder { 16 | 17 | private Builder() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public MultiplicationExpression build() { 23 | return new MultiplicationExpression(leftExpression, rightExpression); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/TagValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class TagValue implements Tag, ScalarValue { 6 | 7 | private final String tag; 8 | 9 | private TagValue(String tag) { 10 | this.tag = Objects.requireNonNull(tag, "tag"); 11 | } 12 | 13 | @Override 14 | public String tag() { 15 | return tag; 16 | } 17 | 18 | public static Builder newTagValue() { 19 | return new Builder(); 20 | } 21 | 22 | public static final class Builder { 23 | private String tag; 24 | 25 | private Builder() {} 26 | 27 | public TagValue build() { 28 | return new TagValue(tag); 29 | } 30 | 31 | public String tag() { 32 | return tag; 33 | } 34 | 35 | public Builder tag(String tag) { 36 | this.tag = tag; 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/LinkValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class LinkValue implements Link, ScalarValue { 6 | 7 | private final String link; 8 | 9 | private LinkValue(String link) { 10 | this.link = Objects.requireNonNull(link, "link"); 11 | } 12 | 13 | @Override 14 | public String link() { 15 | return link; 16 | } 17 | 18 | public static Builder newLinkValue() { 19 | return new Builder(); 20 | } 21 | 22 | public static final class Builder { 23 | private String link; 24 | 25 | private Builder() {} 26 | 27 | public LinkValue build() { 28 | return new LinkValue(link); 29 | } 30 | 31 | public String link() { 32 | return link; 33 | } 34 | 35 | public Builder link(String link) { 36 | this.link = link; 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Account.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class Account implements ScalarValue { 6 | 7 | private final String account; 8 | 9 | private Account(String account) { 10 | this.account = Objects.requireNonNull(account, "account"); 11 | } 12 | 13 | public String account() { 14 | return account; 15 | } 16 | 17 | public static Builder newAccount() { 18 | return new Builder(); 19 | } 20 | 21 | public static final class Builder { 22 | private String account; 23 | 24 | private Builder() {} 25 | 26 | public Account build() { 27 | return new Account(account); 28 | } 29 | 30 | public String account() { 31 | return account; 32 | } 33 | 34 | public Builder account(String account) { 35 | this.account = account; 36 | return this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/StringValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class StringValue implements CostCompValue, ScalarValue { 6 | 7 | private final String value; 8 | 9 | private StringValue(String value) { 10 | this.value = Objects.requireNonNull(value, "value"); 11 | } 12 | 13 | public String value() { 14 | return value; 15 | } 16 | 17 | public static Builder newStringValue() { 18 | return new Builder(); 19 | } 20 | 21 | public static final class Builder { 22 | private String value; 23 | 24 | private Builder() {} 25 | 26 | public StringValue build() { 27 | return new StringValue(value); 28 | } 29 | 30 | public String value() { 31 | return value; 32 | } 33 | 34 | public Builder value(String value) { 35 | this.value = value; 36 | return this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/NumberValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Objects; 5 | 6 | public final class NumberValue { 7 | 8 | private final BigDecimal value; 9 | 10 | private NumberValue(BigDecimal value) { 11 | this.value = Objects.requireNonNull(value, "value"); 12 | } 13 | 14 | public BigDecimal value() { 15 | return value; 16 | } 17 | 18 | public static Builder newNumberValue() { 19 | return new Builder(); 20 | } 21 | 22 | public static final class Builder { 23 | private BigDecimal value; 24 | 25 | private Builder() {} 26 | 27 | public NumberValue build() { 28 | return new NumberValue(value); 29 | } 30 | 31 | public BigDecimal value() { 32 | return value; 33 | } 34 | 35 | public Builder value(BigDecimal value) { 36 | this.value = value; 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/DateValue.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Objects; 5 | 6 | public final class DateValue implements CostCompValue, ScalarValue { 7 | 8 | private final LocalDate date; 9 | 10 | private DateValue(LocalDate date) { 11 | this.date = Objects.requireNonNull(date, "date"); 12 | } 13 | 14 | public LocalDate date() { 15 | return date; 16 | } 17 | 18 | public static Builder newDateValue() { 19 | return new Builder(); 20 | } 21 | 22 | public static final class Builder { 23 | private LocalDate date; 24 | 25 | private Builder() {} 26 | 27 | public DateValue build() { 28 | return new DateValue(date); 29 | } 30 | 31 | public LocalDate date() { 32 | return date; 33 | } 34 | 35 | public Builder date(LocalDate date) { 36 | this.date = date; 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Commodity.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class Commodity implements ScalarValue { 6 | 7 | private final String commodity; 8 | 9 | private Commodity(String commodity) { 10 | this.commodity = Objects.requireNonNull(commodity, "commodity"); 11 | } 12 | 13 | public String commodity() { 14 | return commodity; 15 | } 16 | 17 | public static Builder newCommodity() { 18 | return new Builder(); 19 | } 20 | 21 | public static final class Builder { 22 | private String commodity; 23 | 24 | private Builder() {} 25 | 26 | public Commodity build() { 27 | return new Commodity(commodity); 28 | } 29 | 30 | public String commodity() { 31 | return commodity; 32 | } 33 | 34 | public Builder commodity(String commodity) { 35 | this.commodity = commodity; 36 | return this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PopTagPragma.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.function.Consumer; 6 | import nl.jrdie.beancount.language.tools.NodeVisitor; 7 | 8 | public final class PopTagPragma extends AbstractPragmaNode { 9 | PopTagPragma(SourceLocation sourceLocation, Comment comment) { 10 | super(sourceLocation, comment); 11 | } 12 | 13 | @Override 14 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 15 | return null; 16 | } 17 | 18 | @Override 19 | public PopTagPragma transform(Consumer builderConsumer) { 20 | return null; 21 | } 22 | 23 | public static final class Builder extends AbstractPragmaNode.Builder { 24 | @Override 25 | public PopTagPragma build() { 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PushTagPragma.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.function.Consumer; 6 | import nl.jrdie.beancount.language.tools.NodeVisitor; 7 | 8 | public final class PushTagPragma extends AbstractPragmaNode { 9 | PushTagPragma(SourceLocation sourceLocation, Comment comment) { 10 | super(sourceLocation, comment); 11 | } 12 | 13 | @Override 14 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 15 | return null; 16 | } 17 | 18 | @Override 19 | public PushTagPragma transform(Consumer builderConsumer) { 20 | return null; 21 | } 22 | 23 | public static final class Builder extends AbstractPragmaNode.Builder { 24 | @Override 25 | public PushTagPragma build() { 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/ConstantExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Objects; 5 | 6 | public final class ConstantExpression implements ArithmeticExpression { 7 | 8 | private final BigDecimal value; 9 | 10 | private ConstantExpression(BigDecimal value) { 11 | this.value = Objects.requireNonNull(value, "value"); 12 | } 13 | 14 | public BigDecimal value() { 15 | return value; 16 | } 17 | 18 | public static Builder newConstantExpression() { 19 | return new Builder(); 20 | } 21 | 22 | public static final class Builder { 23 | private BigDecimal value; 24 | 25 | private Builder() {} 26 | 27 | public ConstantExpression build() { 28 | return new ConstantExpression(value); 29 | } 30 | 31 | public BigDecimal value() { 32 | return value; 33 | } 34 | 35 | public Builder value(BigDecimal value) { 36 | this.value = value; 37 | return this; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Node.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.function.Consumer; 6 | import nl.jrdie.beancount.language.tools.NodeVisitor; 7 | import nl.jrdie.beancount.language.tools.internal.NodeChildrenContainer; 8 | 9 | public sealed interface Node, B extends Node.Builder> 10 | permits AbstractDirectiveNode, 11 | AbstractNode, 12 | AbstractPragmaNode, 13 | DirectiveNode, 14 | JournalDeclaration, 15 | PragmaNode { 16 | 17 | SourceLocation sourceLocation(); 18 | 19 | TraversalControl accept(TraverserContext> context, NodeVisitor visitor); 20 | 21 | T transform(Consumer builderConsumer); 22 | 23 | NodeChildrenContainer getNamedChildren(); 24 | 25 | T withNewChildren(NodeChildrenContainer newChildren); 26 | 27 | interface Builder, B extends Node.Builder> { 28 | T build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/lexer/StringLexTest.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.lexer; 2 | 3 | import static nl.jrdie.beancount.parser.antlr.BeancountAntlrParser.STRING; 4 | import static nl.jrdie.beancount.testing.TestUtil.assertLexedTokensEquals; 5 | import static nl.jrdie.beancount.testing.TestUtil.et; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class StringLexTest { 10 | 11 | @Test 12 | public void emptyString() { 13 | assertLexedTokensEquals("\"\"", et(STRING, "\"\"", 1, 0)); 14 | } 15 | 16 | @Test 17 | public void someText() { 18 | assertLexedTokensEquals("\"abc123\"", et(STRING, "\"abc123\"", 1, 0)); 19 | } 20 | 21 | @Test 22 | public void textWithNewlines() { 23 | assertLexedTokensEquals("\"a\nb\"", et(STRING, "\"a\nb\"", 1, 0)); 24 | } 25 | 26 | @Test 27 | public void escapeOneQuote() { 28 | assertLexedTokensEquals("\"a\\\"b\"", et(STRING, "\"a\\\"b\"", 1, 0)); 29 | } 30 | 31 | @Test 32 | public void escapeTwoQuotes() { 33 | assertLexedTokensEquals("\"a\\\"c\\\"b\"", et(STRING, "\"a\\\"c\\\"b\"", 1, 0)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/UnaryCompoundExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class UnaryCompoundExpression implements CompoundExpression { 6 | private final ArithmeticExpression expression; 7 | 8 | public UnaryCompoundExpression(ArithmeticExpression expression) { 9 | this.expression = Objects.requireNonNull(expression, "expression"); 10 | } 11 | 12 | public ArithmeticExpression expression() { 13 | return expression; 14 | } 15 | 16 | public static Builder newUnaryCompoundExpression() { 17 | return new Builder(); 18 | } 19 | 20 | public static final class Builder { 21 | private ArithmeticExpression expression; 22 | 23 | private Builder() {} 24 | 25 | public UnaryCompoundExpression build() { 26 | return new UnaryCompoundExpression(expression); 27 | } 28 | 29 | public ArithmeticExpression expression() { 30 | return expression; 31 | } 32 | 33 | public Builder expression(ArithmeticExpression expression) { 34 | this.expression = expression; 35 | return this; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/parser/NumberParseTest.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import static nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer.NUMBER; 4 | import static nl.jrdie.beancount.testing.TestUtil.assertToLanguage; 5 | import static nl.jrdie.beancount.testing.TestUtil.t; 6 | 7 | import java.math.BigDecimal; 8 | import org.junit.jupiter.params.ParameterizedTest; 9 | import org.junit.jupiter.params.provider.CsvSource; 10 | 11 | public class NumberParseTest { 12 | 13 | @ParameterizedTest 14 | @CsvSource( 15 | textBlock = 16 | """ 17 | '1,,,,4.5', '14.5' 18 | '1,2,3,4.5', '1234.5' 19 | '123,45.00', '12345.00' 20 | '123.00', '123.00' 21 | '123.0', '123.0' 22 | '123,00', '12300' 23 | '123.', '123' 24 | '123', '123' 25 | '1,2.3', '12.3' 26 | """) 27 | public void assertToLanguageNumberParses(String numberString, String targetValue) { 28 | assertToLanguage(antlr -> antlr.parseNumberToken(t(NUMBER, numberString))) 29 | .isEqualTo(new BigDecimal(targetValue)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/internal/AstNodeAdapter.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools.internal; 2 | 3 | import graphql.util.NodeAdapter; 4 | import graphql.util.NodeLocation; 5 | import java.util.List; 6 | import java.util.Map; 7 | import nl.jrdie.beancount.language.Node; 8 | import nl.jrdie.beancount.language.tools.NodeUtil; 9 | 10 | public class AstNodeAdapter implements NodeAdapter> { 11 | 12 | public static final AstNodeAdapter AST_NODE_ADAPTER = new AstNodeAdapter(); 13 | 14 | private AstNodeAdapter() {} 15 | 16 | @Override 17 | public Map>> getNamedChildren(Node node) { 18 | return node.getNamedChildren().getChildren(); 19 | } 20 | 21 | @Override 22 | public Node withNewChildren(Node node, Map>> newChildren) { 23 | NodeChildrenContainer nodeChildrenContainer = 24 | NodeChildrenContainer.newNodeChildrenContainer(newChildren).build(); 25 | return node.withNewChildren(nodeChildrenContainer); 26 | } 27 | 28 | @Override 29 | public Node removeChild(Node node, NodeLocation location) { 30 | return NodeUtil.removeChild(node, location); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AbstractUnaryArithmeticExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | abstract sealed class AbstractUnaryArithmeticExpression implements ArithmeticExpression 6 | permits NegationExpression, ParenthesisedExpression, PlusExpression { 7 | private final ArithmeticExpression expression; 8 | 9 | protected AbstractUnaryArithmeticExpression(ArithmeticExpression expression) { 10 | this.expression = Objects.requireNonNull(expression, "expression"); 11 | } 12 | 13 | public ArithmeticExpression expression() { 14 | return expression; 15 | } 16 | 17 | @SuppressWarnings("unchecked") 18 | protected abstract static sealed class Builder< 19 | T extends AbstractUnaryArithmeticExpression, B extends Builder> 20 | permits NegationExpression.Builder, ParenthesisedExpression.Builder, PlusExpression.Builder { 21 | protected ArithmeticExpression expression; 22 | 23 | protected Builder() {} 24 | 25 | public abstract T build(); 26 | 27 | public ArithmeticExpression expression() { 28 | return expression; 29 | } 30 | 31 | public B expression(ArithmeticExpression expression) { 32 | this.expression = expression; 33 | return (B) this; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/MetadataItem.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | public final class MetadataItem implements MetadataLine { 6 | 7 | private final MetadataKey key; 8 | private final MetadataValue value; 9 | 10 | private MetadataItem(MetadataKey key, MetadataValue value) { 11 | this.key = Objects.requireNonNull(key, "key"); 12 | this.value = value; 13 | } 14 | 15 | public MetadataKey key() { 16 | return key; 17 | } 18 | 19 | public MetadataValue value() { 20 | return value; 21 | } 22 | 23 | public static Builder newMetadataItem() { 24 | return new Builder(); 25 | } 26 | 27 | public static final class Builder { 28 | private MetadataKey key; 29 | private MetadataValue value; 30 | 31 | private Builder() {} 32 | 33 | public MetadataItem build() { 34 | return new MetadataItem(key, value); 35 | } 36 | 37 | public MetadataKey key() { 38 | return key; 39 | } 40 | 41 | public Builder key(MetadataKey key) { 42 | this.key = key; 43 | return this; 44 | } 45 | 46 | public MetadataValue value() { 47 | return value; 48 | } 49 | 50 | public Builder value(MetadataValue value) { 51 | this.value = value; 52 | return this; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Eol.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.function.Consumer; 6 | import nl.jrdie.beancount.language.tools.NodeVisitor; 7 | 8 | // TODO - This is only used for double(+) consecutive newlines - should it really be here? 9 | public final class Eol extends AbstractNode 10 | implements JournalDeclaration { 11 | 12 | private Eol(SourceLocation sourceLocation) { 13 | super(sourceLocation); 14 | } 15 | 16 | @Override 17 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 18 | return TraversalControl.CONTINUE; 19 | } 20 | 21 | @Override 22 | public Eol transform(Consumer builderConsumer) { 23 | final Builder b = new Builder(sourceLocation()); 24 | builderConsumer.accept(b); 25 | return b.build(); 26 | } 27 | 28 | public static Builder newEol() { 29 | return new Builder(); 30 | } 31 | 32 | public static final class Builder extends AbstractNode.Builder { 33 | private Builder() {} 34 | 35 | private Builder(SourceLocation sourceLocation) { 36 | super(sourceLocation); 37 | } 38 | 39 | @Override 40 | public Eol build() { 41 | return new Eol(sourceLocation()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Metadata.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | 8 | public final class Metadata { 9 | 10 | private final List metadata; 11 | 12 | private Metadata(List metadata) { 13 | this.metadata = Collections.unmodifiableList(Objects.requireNonNull(metadata, "metadata")); 14 | } 15 | 16 | public List metadata() { 17 | return metadata; 18 | } 19 | 20 | public static Builder newMetadata() { 21 | return new Builder(); 22 | } 23 | 24 | public Metadata transform(Consumer builderConsumer) { 25 | final Builder b = new Builder(metadata); 26 | builderConsumer.accept(b); 27 | return b.build(); 28 | } 29 | 30 | public static final class Builder { 31 | private List metadata; 32 | 33 | private Builder() {} 34 | 35 | private Builder(List metadata) { 36 | this.metadata = metadata; 37 | } 38 | 39 | public Metadata build() { 40 | return new Metadata(metadata); 41 | } 42 | 43 | public List metadata() { 44 | return metadata; 45 | } 46 | 47 | public Builder metadata(List metadata) { 48 | this.metadata = metadata; 49 | return this; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CostSpec.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public final class CostSpec { 7 | 8 | private final List components; 9 | private final boolean doubleBraces; 10 | 11 | private CostSpec(List components, boolean doubleBraces) { 12 | this.components = Objects.requireNonNull(components, "components"); 13 | this.doubleBraces = doubleBraces; 14 | } 15 | 16 | public boolean doubleBraces() { 17 | return doubleBraces; 18 | } 19 | 20 | public List components() { 21 | return components; 22 | } 23 | 24 | public static Builder newCostSpec() { 25 | return new Builder(); 26 | } 27 | 28 | public static final class Builder { 29 | private List components; 30 | private boolean doubleBraces; 31 | 32 | private Builder() {} 33 | 34 | public CostSpec build() { 35 | return new CostSpec(components, doubleBraces); 36 | } 37 | 38 | public List components() { 39 | return components; 40 | } 41 | 42 | public Builder components(List components) { 43 | this.components = components; 44 | return this; 45 | } 46 | 47 | public boolean doubleBraces() { 48 | return doubleBraces; 49 | } 50 | 51 | public Builder doubleBraces(boolean doubleBraces) { 52 | this.doubleBraces = doubleBraces; 53 | return this; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/AstTransformer.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import graphql.util.TraverserVisitor; 6 | import graphql.util.TreeTransformer; 7 | import java.util.Objects; 8 | import nl.jrdie.beancount.language.Node; 9 | import nl.jrdie.beancount.language.tools.internal.AstNodeAdapter; 10 | 11 | public final class AstTransformer { 12 | 13 | private AstTransformer() {} 14 | 15 | public static Node transform(Node root, NodeVisitor nodeVisitor) { 16 | Objects.requireNonNull(root, "root"); 17 | Objects.requireNonNull(nodeVisitor, "nodeVisitor"); 18 | 19 | TraverserVisitor> traverserVisitor = getNodeTraverserVisitor(nodeVisitor); 20 | TreeTransformer> treeTransformer = 21 | new TreeTransformer<>(AstNodeAdapter.AST_NODE_ADAPTER); 22 | return treeTransformer.transform(root, traverserVisitor); 23 | } 24 | 25 | private static TraverserVisitor> getNodeTraverserVisitor(NodeVisitor nodeVisitor) { 26 | return new TraverserVisitor<>() { 27 | @Override 28 | public TraversalControl enter(graphql.util.TraverserContext> context) { 29 | return context.thisNode().accept(context, nodeVisitor); 30 | } 31 | 32 | @Override 33 | public TraversalControl leave(TraverserContext> context) { 34 | return TraversalControl.CONTINUE; 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build CLI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | buildCli: 10 | strategy: 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | - windows-latest 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: gradle/wrapper-validation-action@v1 19 | - uses: graalvm/setup-graalvm@v1 20 | with: 21 | version: '22.1.0' 22 | java-version: '17' 23 | components: 'native-image' 24 | github-token: ${{ secrets.GITHUB_TOKEN }} 25 | - name: Spotless 26 | uses: gradle/gradle-build-action@v2 27 | with: 28 | arguments: spotlessCheck 29 | - name: Test 30 | uses: gradle/gradle-build-action@v2 31 | with: 32 | arguments: test 33 | - name: Build 34 | uses: gradle/gradle-build-action@v2 35 | with: 36 | arguments: :jbeancount-cli:nativeBuild 37 | - name: Upload Ubuntu CLI executable 38 | if: ${{ matrix.os == 'ubuntu-latest'}} 39 | uses: actions/upload-artifact@v3 40 | with: 41 | name: ubuntu-cli-executable 42 | path: jbeancount-cli/build/native/nativeBuild/jbeancount 43 | - name: Upload Windows CLI executable 44 | if: ${{ matrix.os == 'windows-latest'}} 45 | uses: actions/upload-artifact@v3 46 | with: 47 | name: windows-cli-executable 48 | path: jbeancount-cli/build/native/nativeBuild/jbeancount.exe 49 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CompoundAmount.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class CompoundAmount implements CostCompValue { 4 | private final Commodity commodity; 5 | private final CompoundExpression compoundExpression; 6 | 7 | private CompoundAmount(Commodity commodity, CompoundExpression compoundExpression) { 8 | this.commodity = commodity; 9 | this.compoundExpression = compoundExpression; 10 | } 11 | 12 | public Commodity commodity() { 13 | return commodity; 14 | } 15 | 16 | public CompoundExpression compoundExpression() { 17 | return compoundExpression; 18 | } 19 | 20 | public static Builder newCompoundAmount() { 21 | return new Builder(); 22 | } 23 | 24 | public static final class Builder { 25 | private Commodity commodity; 26 | private CompoundExpression compoundExpression; 27 | 28 | private Builder() {} 29 | 30 | public CompoundAmount build() { 31 | return new CompoundAmount(commodity, compoundExpression); 32 | } 33 | 34 | public Commodity commodity() { 35 | return commodity; 36 | } 37 | 38 | public Builder commodity(Commodity commodity) { 39 | this.commodity = commodity; 40 | return this; 41 | } 42 | 43 | public CompoundExpression compoundExpression() { 44 | return compoundExpression; 45 | } 46 | 47 | public Builder compoundExpression(CompoundExpression compoundExpression) { 48 | this.compoundExpression = compoundExpression; 49 | return this; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AbstractPragmaNode.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | abstract sealed class AbstractPragmaNode< 6 | T extends Node, B extends AbstractPragmaNode.Builder> 7 | extends AbstractNode implements Node, PragmaNode, JournalDeclaration 8 | permits IncludePragma, OptionPragma, PluginPragma, PopTagPragma, PushTagPragma { 9 | 10 | private final Comment comment; 11 | 12 | protected AbstractPragmaNode(SourceLocation sourceLocation, Comment comment) { 13 | super(sourceLocation); 14 | this.comment = comment; 15 | } 16 | 17 | @Nullable 18 | @Override 19 | public Comment comment() { 20 | return comment; 21 | } 22 | 23 | abstract static sealed class Builder< 24 | T extends Node, B extends AbstractPragmaNode.Builder> 25 | extends AbstractNode.Builder 26 | permits IncludePragma.Builder, 27 | OptionPragma.Builder, 28 | PluginPragma.Builder, 29 | PopTagPragma.Builder, 30 | PushTagPragma.Builder { 31 | private Comment comment; 32 | 33 | Builder() {} 34 | 35 | Builder(SourceLocation sourceLocation, Comment comment) { 36 | super(sourceLocation); 37 | } 38 | 39 | public Comment comment() { 40 | return comment; 41 | } 42 | 43 | @SuppressWarnings("unchecked") 44 | public B comment(Comment comment) { 45 | this.comment = comment; 46 | return (B) this; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/internal/transformations/SortTransactions.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.internal.transformations; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | import nl.jrdie.beancount.language.Journal; 8 | import nl.jrdie.beancount.language.JournalDeclaration; 9 | import nl.jrdie.beancount.language.TransactionDirective; 10 | 11 | public final class SortTransactions { 12 | 13 | private SortTransactions() {} 14 | 15 | public static Journal sortTransactions(Journal journal) { 16 | final List> declarations = journal.declarations(); 17 | final List> sortedDeclarations = new ArrayList<>(declarations); 18 | sortedDeclarations.sort(new TransactionComparator()); 19 | return journal.transform(builder -> builder.declarations(sortedDeclarations)); 20 | } 21 | 22 | private static final class TransactionComparator implements Comparator> { 23 | @Override 24 | public int compare(JournalDeclaration o1, JournalDeclaration o2) { 25 | if (!(o1 instanceof TransactionDirective) || !(o2 instanceof TransactionDirective)) { 26 | return 0; 27 | } 28 | return Comparator., LocalDate>comparing( 29 | journalDeclaration -> ((TransactionDirective) journalDeclaration).date()) 30 | .thenComparingInt(journalDeclaration -> journalDeclaration.sourceLocation().line()) 31 | .compare(o1, o2); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/BinaryCompoundExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class BinaryCompoundExpression implements CompoundExpression { 4 | private final ArithmeticExpression leftExpression; 5 | private final ArithmeticExpression rightExpression; 6 | 7 | private BinaryCompoundExpression( 8 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 9 | this.leftExpression = leftExpression; 10 | this.rightExpression = rightExpression; 11 | } 12 | 13 | public ArithmeticExpression leftExpression() { 14 | return leftExpression; 15 | } 16 | 17 | public ArithmeticExpression rightExpression() { 18 | return rightExpression; 19 | } 20 | 21 | public static Builder newBinaryCompoundExpression() { 22 | return new Builder(); 23 | } 24 | 25 | public static final class Builder { 26 | private ArithmeticExpression leftExpression; 27 | private ArithmeticExpression rightExpression; 28 | 29 | private Builder() {} 30 | 31 | public BinaryCompoundExpression build() { 32 | return new BinaryCompoundExpression(leftExpression, rightExpression); 33 | } 34 | 35 | public ArithmeticExpression leftExpression() { 36 | return leftExpression; 37 | } 38 | 39 | public Builder leftExpression(ArithmeticExpression leftExpression) { 40 | this.leftExpression = leftExpression; 41 | return this; 42 | } 43 | 44 | public ArithmeticExpression rightExpression() { 45 | return rightExpression; 46 | } 47 | 48 | public Builder rightExpression(ArithmeticExpression rightExpression) { 49 | this.rightExpression = rightExpression; 50 | return this; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/NodeUtil.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools; 2 | 3 | import static graphql.util.FpKit.mergeFirst; 4 | 5 | import graphql.language.NamedNode; 6 | import graphql.util.FpKit; 7 | import graphql.util.NodeLocation; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | import nl.jrdie.beancount.language.Node; 12 | import nl.jrdie.beancount.language.tools.internal.NodeChildrenContainer; 13 | 14 | public class NodeUtil { 15 | 16 | public static > T findNodeByName(List namedNodes, String name) { 17 | for (T namedNode : namedNodes) { 18 | if (Objects.equals(namedNode.getName(), name)) { 19 | return namedNode; 20 | } 21 | } 22 | return null; 23 | } 24 | 25 | public static > Map nodeByName(List nameNode) { 26 | return FpKit.getByName(nameNode, NamedNode::getName, mergeFirst()); 27 | } 28 | 29 | public static void assertNewChildrenAreEmpty(NodeChildrenContainer newChildren) { 30 | if (!newChildren.isEmpty()) { 31 | throw new IllegalArgumentException( 32 | "Cannot pass non-empty newChildren to Node that doesn't hold children"); 33 | } 34 | } 35 | 36 | public static Node removeChild(Node node, NodeLocation childLocationToRemove) { 37 | NodeChildrenContainer namedChildren = node.getNamedChildren(); 38 | NodeChildrenContainer newChildren = 39 | namedChildren.transform( 40 | builder -> 41 | builder.removeChild( 42 | childLocationToRemove.getName(), childLocationToRemove.getIndex())); 43 | return node.withNewChildren(newChildren); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/SourceLocation.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class SourceLocation { 4 | 5 | public static final SourceLocation EMPTY = new SourceLocation(-1, -1, null); 6 | 7 | private final int line; 8 | private final int column; 9 | private final String sourceName; 10 | 11 | private SourceLocation(int line, int column, String sourceName) { 12 | this.line = line; 13 | this.column = column; 14 | this.sourceName = sourceName; 15 | } 16 | 17 | public int line() { 18 | return line; 19 | } 20 | 21 | public int column() { 22 | return column; 23 | } 24 | 25 | public String sourceName() { 26 | return sourceName; 27 | } 28 | 29 | public static SourceLocation of(int line, int column, String sourceName) { 30 | return new SourceLocation(line, column, sourceName); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (o == null || getClass() != o.getClass()) return false; 37 | 38 | SourceLocation that = (SourceLocation) o; 39 | 40 | if (line != that.line) return false; 41 | if (column != that.column) return false; 42 | return sourceName.equals(that.sourceName); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | int result = line; 48 | result = 31 * result + column; 49 | result = 31 * result + sourceName.hashCode(); 50 | return result; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "SourceLocation{" 56 | + "line=" 57 | + line 58 | + ", column=" 59 | + column 60 | + ", sourceName='" 61 | + sourceName 62 | + '\'' 63 | + '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/AntlrHelper.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import java.util.List; 4 | import nl.jrdie.beancount.language.SourceLocation; 5 | import org.antlr.v4.runtime.CharStream; 6 | import org.antlr.v4.runtime.Token; 7 | import org.antlr.v4.runtime.misc.Interval; 8 | 9 | public final class AntlrHelper { 10 | 11 | private AntlrHelper() {} 12 | 13 | public static SourceLocation createSourceLocation(int line, int column, String sourceName) { 14 | return SourceLocation.of(line, column, sourceName); 15 | } 16 | 17 | public static SourceLocation createSourceLocation(Token token, String sourceName) { 18 | return createSourceLocation(token.getLine(), token.getCharPositionInLine(), sourceName); 19 | } 20 | 21 | public static String createPreview( 22 | CharStream charStream, int line, int offendingStart, int offendingEnd) { 23 | StringBuilder sb = new StringBuilder(); 24 | int startLine = line - 3; 25 | int endLine = line + 3; 26 | // TODO Overflow handling 27 | int lineMaxSize = String.valueOf(Math.max(startLine + 1, endLine + 1)).length(); 28 | final String indent = " ".repeat(lineMaxSize + 3 + offendingStart); 29 | List lines = charStream.getText(Interval.of(0, charStream.size())).lines().toList(); 30 | for (int i = 0; i < lines.size(); i++) { 31 | if (i >= startLine && i <= endLine) { 32 | sb.append(String.format("%" + lineMaxSize + "s | ", i + 1)) 33 | .append(lines.get(i)) 34 | .append('\n'); 35 | if (i == line - 1) { 36 | sb.append(indent) 37 | .append("^".repeat(offendingEnd - offendingStart)) 38 | .append(" error location") 39 | .append('\n'); 40 | } 41 | } 42 | } 43 | return sb.toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/NestJournalNodeInIncludePragmaTransformer.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import graphql.util.TreeTransformerUtil; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import nl.jrdie.beancount.language.IncludePragma; 9 | import nl.jrdie.beancount.language.Journal; 10 | import nl.jrdie.beancount.language.Node; 11 | 12 | public final class NestJournalNodeInIncludePragmaTransformer { 13 | 14 | private NestJournalNodeInIncludePragmaTransformer() {} 15 | 16 | public static Journal transform( 17 | Journal journal, Map includePragmaJournalMap) { 18 | if (includePragmaJournalMap.isEmpty()) { 19 | return journal; 20 | } 21 | return (Journal) 22 | AstTransformer.transform(journal, new IncludeJournalTransformer(includePragmaJournalMap)); 23 | } 24 | 25 | private static final class IncludeJournalTransformer extends NodeVisitorStub { 26 | 27 | private final Map pragmaToJournalMapping; 28 | 29 | public IncludeJournalTransformer(Map pragmaToJournalMapping) { 30 | this.pragmaToJournalMapping = 31 | Objects.requireNonNull(pragmaToJournalMapping, "pragmaToJournalMapping"); 32 | } 33 | 34 | @Override 35 | public TraversalControl visitIncludePragma( 36 | IncludePragma ip, TraverserContext> data) { 37 | final Journal journal = pragmaToJournalMapping.get(ip); 38 | if (journal == null) { 39 | return TraversalControl.CONTINUE; 40 | } 41 | final IncludePragma pragmaWithJournal = ip.transform(builder -> builder.journal(journal)); 42 | return TreeTransformerUtil.changeNode(data, pragmaWithJournal); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Comment.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import nl.jrdie.beancount.language.tools.NodeVisitor; 8 | 9 | public final class Comment extends AbstractNode 10 | implements JournalDeclaration, MetadataLine { 11 | private final String comment; 12 | 13 | private Comment(SourceLocation sourceLocation, String comment) { 14 | super(sourceLocation); 15 | this.comment = Objects.requireNonNull(comment, "comment"); 16 | } 17 | 18 | public String comment() { 19 | return comment; 20 | } 21 | 22 | @Override 23 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 24 | return visitor.visitComment(this, context); 25 | } 26 | 27 | @Override 28 | public Comment transform(Consumer builderConsumer) { 29 | final Builder b = new Builder(sourceLocation(), comment); 30 | builderConsumer.accept(b); 31 | return b.build(); 32 | } 33 | 34 | public static Builder newComment() { 35 | return new Builder(); 36 | } 37 | 38 | public static final class Builder extends AbstractNode.Builder { 39 | private String comment; 40 | 41 | private Builder() {} 42 | 43 | private Builder(SourceLocation sourceLocation, String comment) { 44 | super(sourceLocation); 45 | this.comment = comment; 46 | } 47 | 48 | @Override 49 | public Comment build() { 50 | return new Comment(sourceLocation(), comment); 51 | } 52 | 53 | public String comment() { 54 | return comment; 55 | } 56 | 57 | public Builder comment(String comment) { 58 | this.comment = comment; 59 | return this; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/lexer/NumberLexTest.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.lexer; 2 | 3 | import static nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer.COMMA; 4 | import static nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer.NUMBER; 5 | import static nl.jrdie.beancount.testing.TestUtil.assertLexedTokensEquals; 6 | import static nl.jrdie.beancount.testing.TestUtil.et; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | public class NumberLexTest { 13 | 14 | @Test 15 | public void wholeNumber() { 16 | assertLexedTokensEquals("100", et(NUMBER, "100", 1, 0)); 17 | } 18 | 19 | @Test 20 | public void decimalNumber() { 21 | assertLexedTokensEquals("4.123", et(NUMBER, "4.123", 1, 0)); 22 | } 23 | 24 | @Test 25 | public void decimalWithoutContinuation() { 26 | assertLexedTokensEquals("4.", et(NUMBER, "4.", 1, 0)); 27 | } 28 | 29 | @ParameterizedTest 30 | @ValueSource( 31 | strings = { 32 | "1,000", 33 | "1,000,000", 34 | "1,000,000.00", 35 | "1,1", 36 | "1,,2.12", 37 | "1,,,2,,,3", 38 | "123,456,789" 39 | }) 40 | public void validSeparator(String separatedNumber) { 41 | assertLexedTokensEquals(separatedNumber, et(NUMBER, separatedNumber, 1, 0)); 42 | } 43 | 44 | @Test 45 | public void commaBeforeNumberIsNotNumber() { 46 | assertLexedTokensEquals(",1", et(COMMA, ",", 1, 0), et(NUMBER, "1", 1, 1)); 47 | } 48 | 49 | @Test 50 | public void commaAfterNumberIsNotNumber() { 51 | assertLexedTokensEquals("1,", et(NUMBER, "1", 1, 0), et(COMMA, ",", 1, 1)); 52 | } 53 | 54 | @Test 55 | public void commaAfterDotCreatesSeparateTokens() { 56 | assertLexedTokensEquals( 57 | "1.2,3", et(NUMBER, "1.2", 1, 0), et(COMMA, ",", 1, 3), et(NUMBER, "3", 1, 4)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PriceAnnotation.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | public final class PriceAnnotation { 4 | 5 | private final ArithmeticExpression priceExpression; 6 | private final Commodity commodity; 7 | private final boolean totalCost; 8 | 9 | private PriceAnnotation( 10 | ArithmeticExpression priceExpression, Commodity commodity, boolean totalCost) { 11 | this.priceExpression = priceExpression; 12 | this.commodity = commodity; 13 | this.totalCost = totalCost; 14 | } 15 | 16 | public ArithmeticExpression priceExpression() { 17 | return priceExpression; 18 | } 19 | 20 | public Commodity commodity() { 21 | return commodity; 22 | } 23 | 24 | public boolean totalCost() { 25 | return totalCost; 26 | } 27 | 28 | public static Builder newPriceAnnotation() { 29 | return new Builder(); 30 | } 31 | 32 | public static final class Builder { 33 | private ArithmeticExpression priceExpression; 34 | private Commodity commodity; 35 | private boolean totalCost; 36 | 37 | private Builder() {} 38 | 39 | public PriceAnnotation build() { 40 | return new PriceAnnotation(priceExpression, commodity, totalCost); 41 | } 42 | 43 | public ArithmeticExpression priceExpression() { 44 | return priceExpression; 45 | } 46 | 47 | public Builder priceExpression(ArithmeticExpression priceExpression) { 48 | this.priceExpression = priceExpression; 49 | return this; 50 | } 51 | 52 | public Commodity commodity() { 53 | return commodity; 54 | } 55 | 56 | public Builder commodity(Commodity commodity) { 57 | this.commodity = commodity; 58 | return this; 59 | } 60 | 61 | public boolean totalCost() { 62 | return totalCost; 63 | } 64 | 65 | public Builder totalCost(boolean totalCost) { 66 | this.totalCost = totalCost; 67 | return this; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Amount.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Objects; 5 | 6 | public final class Amount implements MetadataValue { 7 | 8 | private final ArithmeticExpression expression; 9 | private final Commodity commodity; 10 | private final BigDecimal tolerance; 11 | 12 | private Amount(ArithmeticExpression expression, Commodity commodity, BigDecimal tolerance) { 13 | this.expression = Objects.requireNonNull(expression, "expression"); 14 | this.commodity = Objects.requireNonNull(commodity, "commodity"); 15 | this.tolerance = tolerance; 16 | } 17 | 18 | public ArithmeticExpression expression() { 19 | return expression; 20 | } 21 | 22 | public Commodity commodity() { 23 | return commodity; 24 | } 25 | 26 | public BigDecimal tolerance() { 27 | return tolerance; 28 | } 29 | 30 | public static Builder newAmount() { 31 | return new Builder(); 32 | } 33 | 34 | public static final class Builder { 35 | private ArithmeticExpression expression; 36 | private Commodity commodity; 37 | private BigDecimal tolerance; 38 | 39 | private Builder() {} 40 | 41 | public Amount build() { 42 | return new Amount(expression, commodity, tolerance); 43 | } 44 | 45 | public ArithmeticExpression expression() { 46 | return expression; 47 | } 48 | 49 | public Builder expression(ArithmeticExpression expression) { 50 | this.expression = expression; 51 | return this; 52 | } 53 | 54 | public Commodity commodity() { 55 | return commodity; 56 | } 57 | 58 | public Builder commodity(Commodity commodity) { 59 | this.commodity = commodity; 60 | return this; 61 | } 62 | 63 | public BigDecimal tolerance() { 64 | return tolerance; 65 | } 66 | 67 | public Builder tolerance(BigDecimal tolerance) { 68 | this.tolerance = tolerance; 69 | return this; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/FormatJournal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.StandardCharsets; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.concurrent.Callable; 8 | import nl.jrdie.beancount.Beancount; 9 | import nl.jrdie.beancount.cli.commands.mixin.SingleOutput; 10 | import nl.jrdie.beancount.cli.internal.transformations.SortTransactions; 11 | import nl.jrdie.beancount.io.SimpleBeancountPrinter; 12 | import nl.jrdie.beancount.language.Journal; 13 | import picocli.CommandLine; 14 | import picocli.CommandLine.Mixin; 15 | import picocli.CommandLine.Option; 16 | import picocli.CommandLine.Parameters; 17 | 18 | @CommandLine.Command( 19 | name = "format", 20 | description = "This command formats a journal (just like bean-format, but better)") 21 | public class FormatJournal implements Callable { 22 | 23 | @Parameters(index = "0", description = "The Beancount file") 24 | private Path file; 25 | 26 | @Option( 27 | names = "--experimental-sort-transactions", 28 | description = "Also sorts the transactions in the file", 29 | defaultValue = "false") 30 | private boolean sortTransactions; 31 | 32 | @Mixin private SingleOutput output; 33 | 34 | @Override 35 | public Integer call() throws IOException { 36 | Beancount beancount = Beancount.newBeancount().build(); 37 | Journal journal = beancount.createJournalSyncWithoutIncludes(file); 38 | if (sortTransactions) { 39 | journal = SortTransactions.sortTransactions(journal); 40 | } 41 | SimpleBeancountPrinter beancountPrinter = SimpleBeancountPrinter.newDefaultPrinter(); 42 | final String journalAsString = beancountPrinter.print(journal); 43 | if (output.hasOutput()) { 44 | Files.writeString(output.outputFile(), journalAsString, StandardCharsets.UTF_8); 45 | } else { 46 | System.out.println(journalAsString); 47 | } 48 | return 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AbstractNode.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | import nl.jrdie.beancount.language.tools.NodeUtil; 5 | import nl.jrdie.beancount.language.tools.internal.NodeChildrenContainer; 6 | 7 | abstract sealed class AbstractNode, B extends AbstractNode.Builder> 8 | implements Node 9 | permits AbstractDirectiveNode, AbstractPragmaNode, Comment, Eol, Journal, Posting { 10 | 11 | private final SourceLocation sourceLocation; 12 | 13 | protected AbstractNode(SourceLocation sourceLocation) { 14 | this.sourceLocation = Objects.requireNonNull(sourceLocation, "sourceLocation"); 15 | } 16 | 17 | @Override 18 | public SourceLocation sourceLocation() { 19 | return sourceLocation; 20 | } 21 | 22 | @Override 23 | public NodeChildrenContainer getNamedChildren() { 24 | return NodeChildrenContainer.newNodeChildrenContainer().build(); 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | @Override 29 | public T withNewChildren(NodeChildrenContainer newChildren) { 30 | NodeUtil.assertNewChildrenAreEmpty(newChildren); 31 | return (T) this; 32 | } 33 | 34 | public abstract static sealed class Builder< 35 | T extends Node, B extends AbstractNode.Builder> 36 | implements Node.Builder 37 | permits AbstractDirectiveNode.Builder, 38 | AbstractPragmaNode.Builder, 39 | Comment.Builder, 40 | Eol.Builder, 41 | Journal.Builder, 42 | Posting.Builder { 43 | Builder() {} 44 | 45 | Builder(SourceLocation sourceLocation) { 46 | this.sourceLocation = sourceLocation; 47 | } 48 | 49 | private SourceLocation sourceLocation; 50 | 51 | public SourceLocation sourceLocation() { 52 | return sourceLocation; 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | public B sourceLocation(SourceLocation sourceLocation) { 57 | this.sourceLocation = sourceLocation; 58 | return (B) this; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AbstractBinaryArithmeticExpression.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Objects; 4 | 5 | abstract sealed class AbstractBinaryArithmeticExpression implements ArithmeticExpression 6 | permits AdditionExpression, 7 | DivisionExpression, 8 | MultiplicationExpression, 9 | SubtractionExpression { 10 | private final ArithmeticExpression leftExpression; 11 | private final ArithmeticExpression rightExpression; 12 | 13 | protected AbstractBinaryArithmeticExpression( 14 | ArithmeticExpression leftExpression, ArithmeticExpression rightExpression) { 15 | this.leftExpression = Objects.requireNonNull(leftExpression, "leftExpression"); 16 | this.rightExpression = Objects.requireNonNull(rightExpression, "rightExpression"); 17 | } 18 | 19 | public ArithmeticExpression leftExpression() { 20 | return leftExpression; 21 | } 22 | 23 | public ArithmeticExpression rightExpression() { 24 | return rightExpression; 25 | } 26 | 27 | @SuppressWarnings("unchecked") 28 | protected abstract static sealed class Builder< 29 | T extends AbstractBinaryArithmeticExpression, B extends Builder> 30 | permits AdditionExpression.Builder, 31 | DivisionExpression.Builder, 32 | MultiplicationExpression.Builder, 33 | SubtractionExpression.Builder { 34 | protected ArithmeticExpression leftExpression; 35 | protected ArithmeticExpression rightExpression; 36 | 37 | protected Builder() {} 38 | 39 | public abstract T build(); 40 | 41 | public ArithmeticExpression leftExpression() { 42 | return leftExpression; 43 | } 44 | 45 | public B leftExpression(ArithmeticExpression leftExpression) { 46 | this.leftExpression = leftExpression; 47 | return (B) this; 48 | } 49 | 50 | public ArithmeticExpression rightExpression() { 51 | return rightExpression; 52 | } 53 | 54 | public B rightExpression(ArithmeticExpression rightExpression) { 55 | this.rightExpression = rightExpression; 56 | return (B) this; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/picocli/BeancountExecutionExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.picocli; 2 | 3 | import java.io.UncheckedIOException; 4 | import java.nio.file.NoSuchFileException; 5 | import java.util.concurrent.CompletionException; 6 | import nl.jrdie.beancount.language.SourceLocation; 7 | import nl.jrdie.beancount.parser.InvalidSyntaxException; 8 | import picocli.CommandLine; 9 | import picocli.CommandLine.IExecutionExceptionHandler; 10 | import picocli.CommandLine.ParseResult; 11 | 12 | public class BeancountExecutionExceptionHandler implements IExecutionExceptionHandler { 13 | @Override 14 | public int handleExecutionException( 15 | final Exception e, final CommandLine commandLine, final ParseResult parseResult) 16 | throws Exception { 17 | Throwable toHandle = e; 18 | if (toHandle instanceof CompletionException) { 19 | toHandle = toHandle.getCause(); 20 | } 21 | if (toHandle instanceof UncheckedIOException) { 22 | toHandle = toHandle.getCause(); 23 | } 24 | if (toHandle instanceof NoSuchFileException noSuchFileException) { 25 | commandLine.getErr().println("File " + noSuchFileException.getFile() + " does not exist"); 26 | } else if (toHandle instanceof InvalidSyntaxException invalidSyntaxException) { 27 | final SourceLocation sourceLocation = invalidSyntaxException.getSourceLocation(); 28 | commandLine 29 | .getErr() 30 | .println( 31 | "Invalid syntax on line " 32 | + sourceLocation.line() 33 | + ", column " 34 | + sourceLocation.column() 35 | + " in source " 36 | + sourceLocation.sourceName() 37 | + ": " 38 | + invalidSyntaxException.getMessage() 39 | + "\n\n" 40 | + invalidSyntaxException.getPreview()); 41 | } else { 42 | e.printStackTrace(); 43 | } 44 | return commandLine.getExitCodeExceptionMapper() != null 45 | ? commandLine.getExitCodeExceptionMapper().getExitCode(e) 46 | : commandLine.getCommandSpec().exitCodeOnExecutionException(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/SymbolFlag.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import nl.jrdie.beancount.annotation.Beta; 8 | 9 | public final class SymbolFlag implements Flag { 10 | 11 | private final Type type; 12 | 13 | private SymbolFlag(Type type) { 14 | this.type = Objects.requireNonNull(type, "type"); 15 | } 16 | 17 | @Override 18 | public String flag() { 19 | return type.symbol(); 20 | } 21 | 22 | @Override 23 | public boolean star() { 24 | return type == Type.ASTERISK; 25 | } 26 | 27 | @Override 28 | public boolean exclamationMark() { 29 | return type == Type.EXCLAMATION_MARK; 30 | } 31 | 32 | @Override 33 | public boolean txn() { 34 | return false; 35 | } 36 | 37 | public static Builder newSymbolFlag() { 38 | return new Builder(); 39 | } 40 | 41 | public static final class Builder { 42 | private Type type; 43 | 44 | private Builder() {} 45 | 46 | public SymbolFlag build() { 47 | return new SymbolFlag(type); 48 | } 49 | 50 | public Type type() { 51 | return type; 52 | } 53 | 54 | public Builder type(Type type) { 55 | this.type = type; 56 | return this; 57 | } 58 | } 59 | 60 | @Beta 61 | public enum Type { 62 | ASTERISK("*"), 63 | HASH("#"), 64 | EXCLAMATION_MARK("!"), 65 | AMPERSAND("&"), 66 | QUESTION_MARK("?"), 67 | PERCENT("%"); 68 | 69 | private static final Map TYPE_MAP; 70 | 71 | static { 72 | final Map typeMap = new HashMap<>(); 73 | for (Type type : Type.values()) { 74 | typeMap.put(type.symbol(), type); 75 | } 76 | TYPE_MAP = Collections.unmodifiableMap(typeMap); 77 | } 78 | 79 | @Beta 80 | public static Type ofSymbol(String symbol) { 81 | return TYPE_MAP.get(symbol); 82 | } 83 | 84 | private final String symbol; 85 | 86 | Type(String symbol) { 87 | this.symbol = Objects.requireNonNull(symbol, "symbol"); 88 | } 89 | 90 | public String symbol() { 91 | return symbol; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/BeancountCli.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli; 2 | 3 | import java.nio.file.Path; 4 | import nl.jrdie.beancount.cli.commands.CheckJournal; 5 | import nl.jrdie.beancount.cli.commands.FormatJournal; 6 | import nl.jrdie.beancount.cli.commands.IncludeTreeCommand; 7 | import nl.jrdie.beancount.cli.commands.InternalCommand; 8 | import nl.jrdie.beancount.cli.commands.MergeJournal; 9 | import nl.jrdie.beancount.cli.commands.SortJournal; 10 | import nl.jrdie.beancount.cli.commands.jordie.JordieCommand; 11 | import nl.jrdie.beancount.cli.picocli.BeancountExecutionExceptionHandler; 12 | import nl.jrdie.beancount.cli.picocli.PathConverter; 13 | import picocli.CommandLine; 14 | import picocli.CommandLine.Command; 15 | import picocli.CommandLine.Help; 16 | 17 | @Command( 18 | name = "jbeancount", 19 | mixinStandardHelpOptions = true, 20 | version = { 21 | "JBeancount %1$s", 22 | "Java ${java.version} by ${java.vendor} (${java.vm.name}, ${java.vm.version})" 23 | }, 24 | description = "Extension utilities for the Beancount plain text accounting tool", 25 | subcommands = { 26 | JordieCommand.class, 27 | MergeJournal.class, 28 | FormatJournal.class, 29 | CheckJournal.class, 30 | IncludeTreeCommand.class, 31 | InternalCommand.class, 32 | SortJournal.class 33 | }, 34 | scope = CommandLine.ScopeType.INHERIT) 35 | public final class BeancountCli { 36 | 37 | private static final String VERSION = "prerelease"; 38 | 39 | private BeancountCli() {} 40 | 41 | public static void main(String... args) { 42 | @SuppressWarnings("InstantiationOfUtilityClass") 43 | final CommandLine commandLine = new CommandLine(new BeancountCli()); 44 | commandLine.registerConverter(Path.class, new PathConverter()); 45 | commandLine.setExecutionExceptionHandler(new BeancountExecutionExceptionHandler()); 46 | 47 | // To parse version flag, maybe implement different behaviour in the future 48 | commandLine.parseArgs(args); 49 | if (commandLine.isVersionHelpRequested()) { 50 | commandLine.printVersionHelp(System.out, Help.Ansi.AUTO, BeancountCli.VERSION); 51 | return; 52 | } 53 | 54 | // Otherwise, execute the command 55 | final int exitCode = commandLine.execute(args); 56 | System.exit(exitCode); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/OptionPragma.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import nl.jrdie.beancount.language.tools.NodeVisitor; 8 | 9 | public final class OptionPragma extends AbstractPragmaNode { 10 | 11 | private final String name; 12 | private final String value; 13 | 14 | private OptionPragma(SourceLocation sourceLocation, String name, String value, Comment comment) { 15 | super(sourceLocation, comment); 16 | this.name = Objects.requireNonNull(name, "name"); 17 | this.value = Objects.requireNonNull(value, "value"); 18 | } 19 | 20 | public String name() { 21 | return name; 22 | } 23 | 24 | public String value() { 25 | return value; 26 | } 27 | 28 | public static Builder newOptionPragma() { 29 | return new Builder(); 30 | } 31 | 32 | @Override 33 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 34 | return visitor.visitOptionPragma(this, context); 35 | } 36 | 37 | @Override 38 | public OptionPragma transform(Consumer builderConsumer) { 39 | final Builder b = new Builder(sourceLocation(), name, value, comment()); 40 | builderConsumer.accept(b); 41 | return b.build(); 42 | } 43 | 44 | public static final class Builder extends AbstractPragmaNode.Builder { 45 | private String name; 46 | private String value; 47 | 48 | private Builder() {} 49 | 50 | private Builder(SourceLocation sourceLocation, String name, String value, Comment comment) { 51 | super(sourceLocation, comment); 52 | this.name = name; 53 | this.value = value; 54 | } 55 | 56 | @Override 57 | public OptionPragma build() { 58 | return new OptionPragma(sourceLocation(), name, value, comment()); 59 | } 60 | 61 | public String name() { 62 | return name; 63 | } 64 | 65 | public Builder name(String name) { 66 | this.name = name; 67 | return this; 68 | } 69 | 70 | public String value() { 71 | return value; 72 | } 73 | 74 | public Builder value(String value) { 75 | this.value = value; 76 | return this; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PluginPragma.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import nl.jrdie.beancount.language.tools.NodeVisitor; 8 | 9 | public final class PluginPragma extends AbstractPragmaNode { 10 | private final String name; 11 | private final String config; 12 | 13 | private PluginPragma(SourceLocation sourceLocation, String name, String config, Comment comment) { 14 | super(sourceLocation, comment); 15 | this.name = Objects.requireNonNull(name, "name"); 16 | this.config = config; 17 | } 18 | 19 | public String name() { 20 | return name; 21 | } 22 | 23 | public String config() { 24 | return config; 25 | } 26 | 27 | public static PluginPragma.Builder newPluginPragma() { 28 | return new PluginPragma.Builder(); 29 | } 30 | 31 | @Override 32 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 33 | return visitor.visitPluginPragma(this, context); 34 | } 35 | 36 | @Override 37 | public PluginPragma transform(Consumer builderConsumer) { 38 | Builder b = new Builder(sourceLocation(), name, config, comment()); 39 | builderConsumer.accept(b); 40 | return b.build(); 41 | } 42 | 43 | public static final class Builder extends AbstractPragmaNode.Builder { 44 | private String name; 45 | private String config; 46 | 47 | private Builder() {} 48 | 49 | private Builder(SourceLocation sourceLocation, String name, String config, Comment comment) { 50 | super(sourceLocation, comment); 51 | this.name = name; 52 | this.config = config; 53 | } 54 | 55 | @Override 56 | public PluginPragma build() { 57 | return new PluginPragma(sourceLocation(), name, config, comment()); 58 | } 59 | 60 | public String name() { 61 | return name; 62 | } 63 | 64 | public Builder name(String name) { 65 | this.name = name; 66 | return this; 67 | } 68 | 69 | public String config() { 70 | return config; 71 | } 72 | 73 | public Builder config(String config) { 74 | this.config = config; 75 | return this; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/SortJournal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.concurrent.Callable; 7 | import nl.jrdie.beancount.Beancount; 8 | import nl.jrdie.beancount.cli.commands.mixin.SingleOutput; 9 | import nl.jrdie.beancount.cli.internal.transformations.SortTransactions; 10 | import nl.jrdie.beancount.io.SimpleBeancountPrinter; 11 | import nl.jrdie.beancount.language.CustomDirective; 12 | import nl.jrdie.beancount.language.Journal; 13 | import nl.jrdie.beancount.language.JournalDeclaration; 14 | import nl.jrdie.beancount.language.StringValue; 15 | import nl.jrdie.beancount.language.TransactionDirective; 16 | import picocli.CommandLine.Command; 17 | import picocli.CommandLine.Mixin; 18 | import picocli.CommandLine.Parameters; 19 | 20 | @Command(name = "sort", description = "This command sorts the transactions in a Beancount file") 21 | public class SortJournal implements Callable { 22 | 23 | @Parameters(index = "0", description = "The Beancount file") 24 | private Path file; 25 | 26 | @Mixin private SingleOutput output; 27 | 28 | @Override 29 | public Integer call() throws Exception { 30 | Beancount beancount = Beancount.newBeancount().build(); 31 | Journal journal = beancount.createJournalSyncWithoutIncludes(file); 32 | for (JournalDeclaration declaration : journal.declarations()) { 33 | if (!(declaration instanceof TransactionDirective) 34 | && !(declaration instanceof CustomDirective cd 35 | && "fava-option".equals(cd.name()) 36 | && cd.values().size() == 2 37 | && ((StringValue) cd.values().get(0)).value().equals("insert-entry"))) { 38 | throw new Exception( 39 | "The sort command can currently only handle files with transaction directives, and Fava custom insert-entry directives"); 40 | } 41 | } 42 | journal = SortTransactions.sortTransactions(journal); 43 | SimpleBeancountPrinter beancountPrinter = SimpleBeancountPrinter.newDefaultPrinter(); 44 | final String journalAsString = beancountPrinter.print(journal); 45 | if (output.hasOutput()) { 46 | Files.writeString(output.outputFile(), journalAsString, StandardCharsets.UTF_8); 47 | } else { 48 | System.out.println(journalAsString); 49 | } 50 | return 0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/IncludePragma.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import nl.jrdie.beancount.language.tools.NodeVisitor; 8 | 9 | public final class IncludePragma extends AbstractPragmaNode { 10 | private final String filename; 11 | private final Journal journal; 12 | 13 | private IncludePragma( 14 | SourceLocation sourceLocation, String filename, Journal journal, Comment comment) { 15 | super(sourceLocation, comment); 16 | this.filename = Objects.requireNonNull(filename, "filename"); 17 | this.journal = journal; 18 | } 19 | 20 | public String filename() { 21 | return filename; 22 | } 23 | 24 | public Journal journal() { 25 | return journal; 26 | } 27 | 28 | @Override 29 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 30 | return visitor.visitIncludePragma(this, context); 31 | } 32 | 33 | @Override 34 | public IncludePragma transform(Consumer builderConsumer) { 35 | Builder b = new Builder(sourceLocation(), filename, journal, comment()); 36 | builderConsumer.accept(b); 37 | return b.build(); 38 | } 39 | 40 | public static Builder newIncludePragma() { 41 | return new Builder(); 42 | } 43 | 44 | public static final class Builder extends AbstractPragmaNode.Builder { 45 | private String filename; 46 | private Journal journal; 47 | 48 | private Builder() {} 49 | 50 | private Builder( 51 | SourceLocation sourceLocation, String filename, Journal journal, Comment comment) { 52 | super(sourceLocation, comment); 53 | this.filename = filename; 54 | this.journal = journal; 55 | } 56 | 57 | @Override 58 | public IncludePragma build() { 59 | return new IncludePragma(sourceLocation(), filename, journal, comment()); 60 | } 61 | 62 | public String filename() { 63 | return filename; 64 | } 65 | 66 | public Builder filename(String filename) { 67 | this.filename = filename; 68 | return this; 69 | } 70 | 71 | public Journal journal() { 72 | return journal; 73 | } 74 | 75 | public Builder journal(Journal journal) { 76 | this.journal = journal; 77 | return this; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CloseDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class CloseDirective 12 | extends AbstractDirectiveNode { 13 | private final Account account; 14 | 15 | private CloseDirective( 16 | SourceLocation sourceLocation, 17 | LocalDate date, 18 | List tagsAndLinks, 19 | Account account, 20 | Metadata metadata, 21 | Comment comment) { 22 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 23 | this.account = Objects.requireNonNull(account, "account"); 24 | } 25 | 26 | public Account account() { 27 | return account; 28 | } 29 | 30 | public static Builder newCloseDirective() { 31 | return new Builder(); 32 | } 33 | 34 | @Override 35 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 36 | return visitor.visitCloseDirective(this, context); 37 | } 38 | 39 | @Override 40 | public CloseDirective transform(Consumer builderConsumer) { 41 | Builder b = 42 | new Builder(sourceLocation(), date(), tagsAndLinks(), metadata(), account, comment()); 43 | builderConsumer.accept(b); 44 | return b.build(); 45 | } 46 | 47 | public static final class Builder extends AbstractDirectiveNode.Builder { 48 | private Account account; 49 | 50 | private Builder() {} 51 | 52 | public Builder( 53 | SourceLocation sourceLocation, 54 | LocalDate date, 55 | List tagsAndLinks, 56 | Metadata metadata, 57 | Account account, 58 | Comment comment) { 59 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 60 | this.account = account; 61 | } 62 | 63 | @Override 64 | public CloseDirective build() { 65 | return new CloseDirective( 66 | sourceLocation(), date(), tagsAndLinks(), account, metadata(), comment()); 67 | } 68 | 69 | public Account account() { 70 | return account; 71 | } 72 | 73 | public Builder account(Account account) { 74 | this.account = account; 75 | return this; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jbeancount/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("antlr") 4 | id("com.diffplug.spotless") 5 | id("com.github.johnrengelman.shadow") version "7.1.2" 6 | } 7 | 8 | dependencies { 9 | antlr("org.antlr:antlr4:4.10.1") 10 | implementation("org.antlr:antlr4-runtime:4.10.1") 11 | api("com.graphql-java:graphql-java:18.2") 12 | // implementation("com.yuvalshavit:antlr-denter:1.1") 13 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") 14 | testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1") 15 | testImplementation("org.assertj:assertj-core:3.23.1") 16 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") 17 | } 18 | 19 | tasks { 20 | shadowJar { 21 | minimize { 22 | exclude(dependency("org.antlr:antlr4-runtime:4.*")) 23 | } 24 | relocate("graphql.util", "nl.jrdie.beancount.internal.s.graphql.util") { 25 | val transformerClasses: Set = setOf( 26 | "Breadcrumb", 27 | "DefaultTraverserContext", 28 | "FpKit", 29 | "NodeAdapter", 30 | "NodeLocation", 31 | "NodeMultiZipper", 32 | "NodeZipper", 33 | "TraversalControl", 34 | "Traverser", 35 | "TraverserContext", 36 | "TraverserResult", 37 | "TraverserState", 38 | "TraverserVisitor", 39 | "TraverserVisitorStub", 40 | "TreeTransformer", 41 | "TreeTransformerUtil" 42 | ) 43 | transformerClasses.forEach { include("graphql.util.${it}") } 44 | } 45 | relocate("com.yuvalshavit.antlr4", "nl.jrdie.beancount.internal.s.altered.com.yuvalshavit.antlr4") 46 | relocate("org.antlr.v4.runtime", "nl.jrdie.beancount.internal.s.org.antlr.v4.runtime") 47 | val excludeFiles: Set = setOf( 48 | "LICENSE", 49 | "GraphqlCommon.g4", 50 | "GraphqlSDL.g4", 51 | "LICENSE.md", 52 | "GraphqlOperation.g4", 53 | "Graphql.g4", 54 | "META-INF/maven/" 55 | ) 56 | excludeFiles.forEach { exclude("/${it}") } 57 | // exclude("Graphql.g4") 58 | } 59 | } 60 | 61 | tasks.getByName("test") { 62 | useJUnitPlatform() 63 | } 64 | 65 | spotless { 66 | antlr4 { 67 | antlr4Formatter("1.2.1") 68 | target("src/*/antlr/**/*.g4") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CommodityDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class CommodityDirective 12 | extends AbstractDirectiveNode { 13 | private final Commodity commodity; 14 | 15 | private CommodityDirective( 16 | SourceLocation sourceLocation, 17 | LocalDate date, 18 | List tagsAndLinks, 19 | Commodity commodity, 20 | Metadata metadata, 21 | Comment comment) { 22 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 23 | this.commodity = Objects.requireNonNull(commodity, "commodity"); 24 | } 25 | 26 | public Commodity commodity() { 27 | return commodity; 28 | } 29 | 30 | public static Builder newCommodityDirective() { 31 | return new Builder(); 32 | } 33 | 34 | @Override 35 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 36 | return visitor.visitCommodityDirective(this, context); 37 | } 38 | 39 | @Override 40 | public CommodityDirective transform(Consumer builderConsumer) { 41 | final Builder b = 42 | new Builder(sourceLocation(), date(), tagsAndLinks(), metadata(), commodity, comment()); 43 | builderConsumer.accept(b); 44 | return b.build(); 45 | } 46 | 47 | public static final class Builder 48 | extends AbstractDirectiveNode.Builder { 49 | private Commodity commodity; 50 | 51 | private Builder() {} 52 | 53 | private Builder( 54 | SourceLocation sourceLocation, 55 | LocalDate date, 56 | List tagsAndLinks, 57 | Metadata metadata, 58 | Commodity commodity, 59 | Comment comment) { 60 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 61 | this.commodity = commodity; 62 | } 63 | 64 | public CommodityDirective build() { 65 | return new CommodityDirective( 66 | sourceLocation(), date(), tagsAndLinks(), commodity, metadata(), comment()); 67 | } 68 | 69 | public Commodity commodity() { 70 | return commodity; 71 | } 72 | 73 | public Builder commodity(Commodity commodity) { 74 | this.commodity = commodity; 75 | return this; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Journal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | import nl.jrdie.beancount.language.tools.internal.NodeChildrenContainer; 11 | 12 | public final class Journal extends AbstractNode { 13 | 14 | public static final String CHILDREN_DECLARATIONS = "declarations"; 15 | 16 | private final List> declarations; 17 | 18 | private Journal(SourceLocation sourceLocation, List> declarations) { 19 | super(sourceLocation); 20 | this.declarations = 21 | Collections.unmodifiableList(Objects.requireNonNull(declarations, "declarations")); 22 | } 23 | 24 | public List> declarations() { 25 | return declarations; 26 | } 27 | 28 | public static Builder newJournal() { 29 | return new Builder(); 30 | } 31 | 32 | @Override 33 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 34 | return visitor.visitJournal(this, context); 35 | } 36 | 37 | @Override 38 | public Journal transform(Consumer builderConsumer) { 39 | final Builder b = new Builder(sourceLocation(), declarations); 40 | builderConsumer.accept(b); 41 | return b.build(); 42 | } 43 | 44 | @Override 45 | public NodeChildrenContainer getNamedChildren() { 46 | return NodeChildrenContainer.newNodeChildrenContainer() 47 | .children(CHILDREN_DECLARATIONS, declarations) 48 | .build(); 49 | } 50 | 51 | @Override 52 | public Journal withNewChildren(NodeChildrenContainer newChildren) { 53 | return transform( 54 | builder -> builder.declarations(newChildren.getChildren(CHILDREN_DECLARATIONS))); 55 | } 56 | 57 | public static final class Builder extends AbstractNode.Builder { 58 | private List> declarations; 59 | 60 | private Builder() {} 61 | 62 | private Builder(SourceLocation sourceLocation, List> declarations) { 63 | super(sourceLocation); 64 | this.declarations = declarations; 65 | } 66 | 67 | @Override 68 | public Journal build() { 69 | return new Journal(sourceLocation(), declarations); 70 | } 71 | 72 | public List> declarations() { 73 | return declarations; 74 | } 75 | 76 | public Builder declarations(List> declarations) { 77 | this.declarations = declarations; 78 | return this; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/internal/transformations/FlattenJournal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.internal.transformations; 2 | 3 | import graphql.Assert; 4 | import graphql.util.TraversalControl; 5 | import graphql.util.TraverserContext; 6 | import graphql.util.TreeTransformerUtil; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import nl.jrdie.beancount.language.IncludePragma; 10 | import nl.jrdie.beancount.language.Journal; 11 | import nl.jrdie.beancount.language.JournalDeclaration; 12 | import nl.jrdie.beancount.language.Node; 13 | import nl.jrdie.beancount.language.tools.AstTransformer; 14 | import nl.jrdie.beancount.language.tools.NodeVisitor; 15 | import nl.jrdie.beancount.language.tools.NodeVisitorStub; 16 | 17 | public final class FlattenJournal { 18 | 19 | private FlattenJournal() {} 20 | 21 | public static Journal flattenJournal( 22 | Journal journal, boolean recursively, boolean keepIncludePragmas) { 23 | final NodeVisitor nodeVisitor = 24 | new SingleRootJournalMultiUseFlatteningNodeVisitor(!keepIncludePragmas, recursively); 25 | return (Journal) AstTransformer.transform(journal, nodeVisitor); 26 | } 27 | 28 | private static final class SingleRootJournalMultiUseFlatteningNodeVisitor 29 | extends NodeVisitorStub { 30 | 31 | private final Set resolvedPaths; 32 | private final boolean removeIncludePragma; 33 | private final boolean isRecursive; 34 | 35 | private SingleRootJournalMultiUseFlatteningNodeVisitor( 36 | boolean removeIncludePragma, boolean isRecursive) { 37 | this.removeIncludePragma = removeIncludePragma; 38 | this.isRecursive = isRecursive; 39 | this.resolvedPaths = new HashSet<>(); 40 | } 41 | 42 | @Override 43 | public TraversalControl visitIncludePragma( 44 | IncludePragma ip, TraverserContext> data) { 45 | if (resolvedPaths.contains(ip.filename())) { 46 | if (removeIncludePragma || !isRecursive) { 47 | Assert.assertShouldNeverHappen(); 48 | return TraversalControl.QUIT; 49 | } else { 50 | return TraversalControl.CONTINUE; 51 | } 52 | } 53 | if (removeIncludePragma) { 54 | TreeTransformerUtil.deleteNode(data); 55 | } 56 | final Journal journal = ip.journal(); 57 | if (journal != null) { 58 | resolvedPaths.add(ip.filename()); 59 | for (JournalDeclaration declaration : journal.declarations()) { 60 | final TraversalControl traversalControl = 61 | TreeTransformerUtil.insertAfter(data, declaration); 62 | if (traversalControl != TraversalControl.CONTINUE) { 63 | return traversalControl; 64 | } 65 | } 66 | } 67 | return TraversalControl.CONTINUE; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/QueryDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class QueryDirective 12 | extends AbstractDirectiveNode { 13 | private final String name; 14 | private final String sql; 15 | 16 | public QueryDirective( 17 | SourceLocation sourceLocation, 18 | LocalDate date, 19 | List tagsAndLinks, 20 | String name, 21 | String sql, 22 | Metadata metadata, 23 | Comment comment) { 24 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 25 | this.name = Objects.requireNonNull(name, "name"); 26 | this.sql = Objects.requireNonNull(sql, "sql"); 27 | } 28 | 29 | public String name() { 30 | return name; 31 | } 32 | 33 | public String sql() { 34 | return sql; 35 | } 36 | 37 | public static Builder newQueryDirective() { 38 | return new Builder(); 39 | } 40 | 41 | @Override 42 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 43 | return visitor.visitQueryDirective(this, context); 44 | } 45 | 46 | @Override 47 | public QueryDirective transform(Consumer builderConsumer) { 48 | final Builder b = 49 | new Builder(sourceLocation(), date(), tagsAndLinks(), metadata(), name, sql, comment()); 50 | builderConsumer.accept(b); 51 | return b.build(); 52 | } 53 | 54 | public static final class Builder extends AbstractDirectiveNode.Builder { 55 | private String name; 56 | private String sql; 57 | 58 | private Builder() {} 59 | 60 | private Builder( 61 | SourceLocation sourceLocation, 62 | LocalDate date, 63 | List tagsAndLinks, 64 | Metadata metadata, 65 | String name, 66 | String sql, 67 | Comment comment) { 68 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 69 | this.name = name; 70 | this.sql = sql; 71 | } 72 | 73 | @Override 74 | public QueryDirective build() { 75 | return new QueryDirective( 76 | sourceLocation(), date(), tagsAndLinks(), name, sql, metadata(), comment()); 77 | } 78 | 79 | public String name() { 80 | return name; 81 | } 82 | 83 | public Builder name(String name) { 84 | this.name = name; 85 | return this; 86 | } 87 | 88 | public String sql() { 89 | return sql; 90 | } 91 | 92 | public Builder sql(String sql) { 93 | this.sql = sql; 94 | return this; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PriceDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | import nl.jrdie.beancount.language.tools.NodeVisitor; 9 | 10 | public final class PriceDirective 11 | extends AbstractDirectiveNode { 12 | private final Commodity commodity; 13 | private final Amount price; 14 | 15 | private PriceDirective( 16 | SourceLocation sourceLocation, 17 | LocalDate date, 18 | List tagsAndLinks, 19 | Commodity commodity, 20 | Amount price, 21 | Metadata metadata, 22 | Comment comment) { 23 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 24 | this.commodity = commodity; 25 | this.price = price; 26 | } 27 | 28 | public Commodity commodity() { 29 | return commodity; 30 | } 31 | 32 | public Amount price() { 33 | return price; 34 | } 35 | 36 | public static Builder newPriceDirective() { 37 | return new Builder(); 38 | } 39 | 40 | @Override 41 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 42 | return visitor.visitPriceDirective(this, context); 43 | } 44 | 45 | @Override 46 | public PriceDirective transform(Consumer builderConsumer) { 47 | final Builder b = 48 | new Builder( 49 | sourceLocation(), date(), tagsAndLinks(), metadata(), commodity, price, comment()); 50 | builderConsumer.accept(b); 51 | return b.build(); 52 | } 53 | 54 | public static final class Builder extends AbstractDirectiveNode.Builder { 55 | private Commodity commodity; 56 | private Amount price; 57 | 58 | private Builder() {} 59 | 60 | private Builder( 61 | SourceLocation sourceLocation, 62 | LocalDate date, 63 | List tagsAndLinks, 64 | Metadata metadata, 65 | Commodity commodity, 66 | Amount price, 67 | Comment comment) { 68 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 69 | this.commodity = commodity; 70 | this.price = price; 71 | } 72 | 73 | @Override 74 | public PriceDirective build() { 75 | return new PriceDirective( 76 | sourceLocation(), date(), tagsAndLinks(), commodity, price, metadata(), comment()); 77 | } 78 | 79 | public Commodity commodity() { 80 | return commodity; 81 | } 82 | 83 | public Builder commodity(Commodity commodity) { 84 | this.commodity = commodity; 85 | return this; 86 | } 87 | 88 | public Amount price() { 89 | return price; 90 | } 91 | 92 | public Builder price(Amount price) { 93 | this.price = price; 94 | return this; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/DocumentDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class DocumentDirective 12 | extends AbstractDirectiveNode { 13 | private final String filename; 14 | private final Account account; 15 | 16 | private DocumentDirective( 17 | SourceLocation sourceLocation, 18 | LocalDate date, 19 | List tagsAndLinks, 20 | String filename, 21 | Account account, 22 | Metadata metadata, 23 | Comment comment) { 24 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 25 | this.filename = Objects.requireNonNull(filename, "filename"); 26 | this.account = Objects.requireNonNull(account, "account"); 27 | } 28 | 29 | public String filename() { 30 | return filename; 31 | } 32 | 33 | public Account account() { 34 | return account; 35 | } 36 | 37 | public static Builder newDocumentDirective() { 38 | return new Builder(); 39 | } 40 | 41 | @Override 42 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 43 | return visitor.visitDocumentDirective(this, context); 44 | } 45 | 46 | @Override 47 | public DocumentDirective transform(Consumer builderConsumer) { 48 | final Builder b = 49 | new Builder( 50 | sourceLocation(), date(), tagsAndLinks(), metadata(), filename, account, comment()); 51 | builderConsumer.accept(b); 52 | return b.build(); 53 | } 54 | 55 | public static final class Builder 56 | extends AbstractDirectiveNode.Builder { 57 | private String filename; 58 | private Account account; 59 | 60 | private Builder() {} 61 | 62 | private Builder( 63 | SourceLocation sourceLocation, 64 | LocalDate date, 65 | List tagsAndLinks, 66 | Metadata metadata, 67 | String filename, 68 | Account account, 69 | Comment comment) { 70 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 71 | this.filename = filename; 72 | this.account = account; 73 | } 74 | 75 | public DocumentDirective build() { 76 | return new DocumentDirective( 77 | sourceLocation(), date(), tagsAndLinks(), filename, account, metadata(), comment()); 78 | } 79 | 80 | public String filename() { 81 | return filename; 82 | } 83 | 84 | public Builder filename(String filename) { 85 | this.filename = filename; 86 | return this; 87 | } 88 | 89 | public Account account() { 90 | return account; 91 | } 92 | 93 | public Builder account(Account account) { 94 | this.account = account; 95 | return this; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/NodeVisitor.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import nl.jrdie.beancount.language.BalanceDirective; 6 | import nl.jrdie.beancount.language.CloseDirective; 7 | import nl.jrdie.beancount.language.Comment; 8 | import nl.jrdie.beancount.language.CommodityDirective; 9 | import nl.jrdie.beancount.language.CustomDirective; 10 | import nl.jrdie.beancount.language.DocumentDirective; 11 | import nl.jrdie.beancount.language.EventDirective; 12 | import nl.jrdie.beancount.language.IncludePragma; 13 | import nl.jrdie.beancount.language.Journal; 14 | import nl.jrdie.beancount.language.Node; 15 | import nl.jrdie.beancount.language.NoteDirective; 16 | import nl.jrdie.beancount.language.OpenDirective; 17 | import nl.jrdie.beancount.language.OptionPragma; 18 | import nl.jrdie.beancount.language.PadDirective; 19 | import nl.jrdie.beancount.language.PluginPragma; 20 | import nl.jrdie.beancount.language.Posting; 21 | import nl.jrdie.beancount.language.PriceDirective; 22 | import nl.jrdie.beancount.language.QueryDirective; 23 | import nl.jrdie.beancount.language.TransactionDirective; 24 | 25 | public interface NodeVisitor { 26 | 27 | TraversalControl visitIncludePragma(IncludePragma ip, TraverserContext> data); 28 | 29 | TraversalControl visitPluginPragma(PluginPragma pp, TraverserContext> data); 30 | 31 | TraversalControl visitOptionPragma(OptionPragma op, TraverserContext> data); 32 | 33 | TraversalControl visitCloseDirective(CloseDirective cd, TraverserContext> data); 34 | 35 | TraversalControl visitBalanceDirective(BalanceDirective bd, TraverserContext> data); 36 | 37 | TraversalControl visitCommodityDirective( 38 | CommodityDirective cd, TraverserContext> data); 39 | 40 | TraversalControl visitCustomDirective(CustomDirective cd, TraverserContext> data); 41 | 42 | TraversalControl visitDocumentDirective(DocumentDirective dd, TraverserContext> data); 43 | 44 | TraversalControl visitEventDirective(EventDirective ed, TraverserContext> data); 45 | 46 | TraversalControl visitNoteDirective(NoteDirective nd, TraverserContext> data); 47 | 48 | TraversalControl visitOpenDirective(OpenDirective od, TraverserContext> data); 49 | 50 | TraversalControl visitQueryDirective(QueryDirective qd, TraverserContext> data); 51 | 52 | TraversalControl visitTransactionDirective( 53 | TransactionDirective td, TraverserContext> data); 54 | 55 | TraversalControl visitPriceDirective(PriceDirective pd, TraverserContext> data); 56 | 57 | TraversalControl visitPadDirective(PadDirective pd, TraverserContext> data); 58 | 59 | TraversalControl visitJournal(Journal journal, TraverserContext> data); 60 | 61 | TraversalControl visitComment(Comment comment, TraverserContext> data); 62 | 63 | TraversalControl visitPosting(Posting posting, TraverserContext> data); 64 | } 65 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/EventDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class EventDirective 12 | extends AbstractDirectiveNode { 13 | 14 | private final String type; 15 | private final String description; 16 | 17 | private EventDirective( 18 | SourceLocation sourceLocation, 19 | LocalDate date, 20 | List tagsAndLinks, 21 | String type, 22 | String description, 23 | Metadata metadata, 24 | Comment comment) { 25 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 26 | this.type = Objects.requireNonNull(type, "type"); 27 | this.description = Objects.requireNonNull(description, "description"); 28 | } 29 | 30 | public String type() { 31 | return type; 32 | } 33 | 34 | public String description() { 35 | return description; 36 | } 37 | 38 | public static EventDirective.Builder newEventDirective() { 39 | return new EventDirective.Builder(); 40 | } 41 | 42 | @Override 43 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 44 | return visitor.visitEventDirective(this, context); 45 | } 46 | 47 | @Override 48 | public EventDirective transform(Consumer builderConsumer) { 49 | final Builder b = 50 | new Builder( 51 | sourceLocation(), date(), tagsAndLinks(), metadata(), type, description, comment()); 52 | builderConsumer.accept(b); 53 | return b.build(); 54 | } 55 | 56 | public static final class Builder extends AbstractDirectiveNode.Builder { 57 | private String type; 58 | private String description; 59 | 60 | private Builder() {} 61 | 62 | private Builder( 63 | SourceLocation sourceLocation, 64 | LocalDate date, 65 | List tagsAndLinks, 66 | Metadata metadata, 67 | String type, 68 | String description, 69 | Comment comment) { 70 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 71 | this.type = type; 72 | this.description = description; 73 | } 74 | 75 | @Override 76 | public EventDirective build() { 77 | return new EventDirective( 78 | sourceLocation(), date(), tagsAndLinks(), type, description, metadata(), comment()); 79 | } 80 | 81 | public String type() { 82 | return type; 83 | } 84 | 85 | public Builder type(String type) { 86 | this.type = type; 87 | return this; 88 | } 89 | 90 | public String description() { 91 | return description; 92 | } 93 | 94 | public Builder description(String description) { 95 | this.description = description; 96 | return this; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/BalanceDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class BalanceDirective 12 | extends AbstractDirectiveNode { 13 | private final Account account; 14 | private final Amount amount; 15 | 16 | private BalanceDirective( 17 | SourceLocation sourceLocation, 18 | LocalDate date, 19 | List tagsAndLinks, 20 | Account account, 21 | Amount amount, 22 | Metadata metadata, 23 | Comment comment) { 24 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 25 | this.account = Objects.requireNonNull(account, "account"); 26 | this.amount = Objects.requireNonNull(amount, "amount"); 27 | } 28 | 29 | public Account account() { 30 | return account; 31 | } 32 | 33 | public Amount amount() { 34 | return amount; 35 | } 36 | 37 | public static Builder newBalanceDirective() { 38 | return new Builder(); 39 | } 40 | 41 | @Override 42 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 43 | return visitor.visitBalanceDirective(this, context); 44 | } 45 | 46 | @Override 47 | public BalanceDirective transform(Consumer builderConsumer) { 48 | Builder builder = 49 | new Builder( 50 | sourceLocation(), date(), tagsAndLinks(), metadata(), account, amount, comment()); 51 | builderConsumer.accept(builder); 52 | return builder.build(); 53 | } 54 | 55 | public static final class Builder 56 | extends AbstractDirectiveNode.Builder { 57 | private Account account; 58 | private Amount amount; 59 | 60 | private Builder() { 61 | super(); 62 | } 63 | 64 | private Builder( 65 | SourceLocation sourceLocation, 66 | LocalDate date, 67 | List tagsAndLinks, 68 | Metadata metadata, 69 | Account account, 70 | Amount amount, 71 | Comment comment) { 72 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 73 | this.account = account; 74 | this.amount = amount; 75 | } 76 | 77 | @Override 78 | public BalanceDirective build() { 79 | return new BalanceDirective( 80 | sourceLocation(), date(), tagsAndLinks(), account, amount, metadata(), comment()); 81 | } 82 | 83 | public Account account() { 84 | return account; 85 | } 86 | 87 | public Builder account(Account account) { 88 | this.account = account; 89 | return this; 90 | } 91 | 92 | public Amount amount() { 93 | return amount; 94 | } 95 | 96 | public Builder amount(Amount amount) { 97 | this.amount = amount; 98 | return this; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/jordie/JordieMoveToDesc.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands.jordie; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import graphql.util.TreeTransformerUtil; 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.concurrent.Callable; 10 | import nl.jrdie.beancount.Beancount; 11 | import nl.jrdie.beancount.cli.commands.mixin.SingleOutput; 12 | import nl.jrdie.beancount.io.SimpleBeancountPrinter; 13 | import nl.jrdie.beancount.language.Journal; 14 | import nl.jrdie.beancount.language.Node; 15 | import nl.jrdie.beancount.language.TransactionDirective; 16 | import nl.jrdie.beancount.language.tools.AstTransformer; 17 | import nl.jrdie.beancount.language.tools.NodeVisitorStub; 18 | import nl.jrdie.beancount.parser.BeancountUtil; 19 | import picocli.CommandLine.Command; 20 | import picocli.CommandLine.Mixin; 21 | import picocli.CommandLine.Parameters; 22 | import picocli.CommandLine.ScopeType; 23 | 24 | @Command(name = "movetodesc", scope = ScopeType.LOCAL) 25 | public class JordieMoveToDesc implements Callable { 26 | 27 | @Parameters(index = "0", description = "The Beancount file") 28 | private Path file; 29 | 30 | @Mixin private SingleOutput output; 31 | 32 | @Override 33 | public Integer call() throws Exception { 34 | Beancount beancount = Beancount.newBeancount().build(); 35 | Journal journal = beancount.createJournalSyncWithoutIncludes(file); 36 | journal = 37 | (Journal) 38 | AstTransformer.transform( 39 | journal, 40 | new NodeVisitorStub() { 41 | @Override 42 | public TraversalControl visitTransactionDirective( 43 | TransactionDirective td, TraverserContext> data) { 44 | if (!BeancountUtil.hasMetadataWithKey(td.metadata(), "desc")) { 45 | TransactionDirective newtd = 46 | td.transform( 47 | transactionDirectiveBuilder -> 48 | transactionDirectiveBuilder 49 | .narration(null) 50 | .metadata( 51 | BeancountUtil.addMetadataAtStart( 52 | transactionDirectiveBuilder.metadata(), 53 | BeancountUtil.newMetadataItem( 54 | "desc", td.narration())))); 55 | return TreeTransformerUtil.changeNode(data, newtd); 56 | } 57 | return TraversalControl.CONTINUE; 58 | } 59 | }); 60 | 61 | SimpleBeancountPrinter beancountPrinter = SimpleBeancountPrinter.newDefaultPrinter(); 62 | final String journalAsString = beancountPrinter.print(journal); 63 | if (output.hasOutput()) { 64 | Files.writeString(output.outputFile(), journalAsString, StandardCharsets.UTF_8); 65 | } else { 66 | System.out.println(journalAsString); 67 | } 68 | return 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/NoteDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class NoteDirective 12 | extends AbstractDirectiveNode { 13 | 14 | private final Account account; 15 | private final String note; 16 | 17 | private NoteDirective( 18 | SourceLocation sourceLocation, 19 | LocalDate date, 20 | List tagsAndLinks, 21 | Account account, 22 | String note, 23 | Metadata metadata, 24 | Comment comment) { 25 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 26 | this.account = Objects.requireNonNull(account, "account"); 27 | this.note = Objects.requireNonNull(note, "comment"); 28 | } 29 | 30 | /** 31 | * The account this note corresponds to. 32 | * 33 | * @return The {@link Account} this note belongs to, never null 34 | */ 35 | public Account account() { 36 | return account; 37 | } 38 | 39 | /** 40 | * The text of the note. 41 | * 42 | * @return The text of the note, never null 43 | */ 44 | public String note() { 45 | return note; 46 | } 47 | 48 | public static NoteDirective.Builder newNoteDirective() { 49 | return new NoteDirective.Builder(); 50 | } 51 | 52 | @Override 53 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 54 | return visitor.visitNoteDirective(this, context); 55 | } 56 | 57 | @Override 58 | public NoteDirective transform(Consumer builderConsumer) { 59 | final Builder b = 60 | new Builder(sourceLocation(), date(), tagsAndLinks(), metadata(), note, account, comment()); 61 | builderConsumer.accept(b); 62 | return b.build(); 63 | } 64 | 65 | public static final class Builder extends AbstractDirectiveNode.Builder { 66 | private String note; 67 | private Account account; 68 | 69 | private Builder() {} 70 | 71 | private Builder( 72 | SourceLocation sourceLocation, 73 | LocalDate date, 74 | List tagsAndLinks, 75 | Metadata metadata, 76 | String note, 77 | Account account, 78 | Comment comment) { 79 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 80 | this.note = note; 81 | this.account = account; 82 | } 83 | 84 | @Override 85 | public NoteDirective build() { 86 | return new NoteDirective( 87 | sourceLocation(), date(), tagsAndLinks(), account, note, metadata(), comment()); 88 | } 89 | 90 | public String note() { 91 | return note; 92 | } 93 | 94 | public Builder note(String note) { 95 | this.note = note; 96 | return this; 97 | } 98 | 99 | public Account account() { 100 | return account; 101 | } 102 | 103 | public Builder account(Account account) { 104 | this.account = account; 105 | return this; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /jbeancount/src/test/java/nl/jrdie/beancount/testing/TestUtil.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.testing; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatCode; 5 | 6 | import java.io.StringReader; 7 | import java.util.List; 8 | import java.util.function.Function; 9 | import nl.jrdie.beancount.language.Journal; 10 | import nl.jrdie.beancount.parser.BeancountAntlrToLanguage; 11 | import nl.jrdie.beancount.parser.BeancountParser; 12 | import nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer; 13 | import nl.jrdie.beancount.parser.antlr.BeancountAntlrParser; 14 | import org.antlr.v4.runtime.CharStream; 15 | import org.antlr.v4.runtime.CharStreams; 16 | import org.antlr.v4.runtime.CommonToken; 17 | import org.antlr.v4.runtime.Token; 18 | import org.assertj.core.api.AbstractThrowableAssert; 19 | import org.assertj.core.api.ObjectAssert; 20 | 21 | public final class TestUtil { 22 | 23 | private TestUtil() {} 24 | 25 | public static Journal parse(String journalString) { 26 | return BeancountParser.newParser().parseJournal(new StringReader(journalString)); 27 | } 28 | 29 | public static List lex(String tokenizationString) { 30 | CharStream cs = CharStreams.fromString(tokenizationString); 31 | BeancountAntlrLexer lexer = new BeancountAntlrLexer(cs); 32 | return lexer.getAllTokens(); 33 | } 34 | 35 | public static void assertLexedTokensEquals(String tokenizationString, Integer... types) { 36 | assertThat(lex(tokenizationString)).map(Token::getType).containsExactly(types); 37 | } 38 | 39 | public static void assertLexedTokensEquals(String tokenizationString, ExpectedToken... types) { 40 | assertThat(lex(tokenizationString)) 41 | .filteredOn(token -> token.getType() != BeancountAntlrParser.EOL) // Hmmm 42 | .map( 43 | token -> 44 | new ExpectedToken( 45 | BeancountAntlrLexer.VOCABULARY.getSymbolicName(token.getType()), 46 | token.getText(), 47 | token.getLine(), 48 | token.getCharPositionInLine())) 49 | .containsExactly(types); 50 | } 51 | 52 | public static ExpectedToken et(int type, String text, int line, int col) { 53 | return new ExpectedToken(BeancountAntlrLexer.VOCABULARY.getSymbolicName(type), text, line, col); 54 | } 55 | 56 | public static Token t(int type, String text) { 57 | return new CommonToken(type, text); 58 | } 59 | 60 | public static void assertLexicalFailure(String tokenizationString) { 61 | CharStream cs = CharStreams.fromString(tokenizationString); 62 | BeancountAntlrLexer lexer = new BeancountAntlrLexer(cs); 63 | assertThat(lexer.getAllTokens()).isEmpty(); 64 | } 65 | 66 | public static ObjectAssert assertToLanguage( 67 | Function toLanguageFunction) { 68 | return assertThat( 69 | toLanguageFunction.apply( 70 | new BeancountAntlrToLanguage(null, "TestUtil.assertToLanguage(Function)"))); 71 | } 72 | 73 | public static AbstractThrowableAssert assertToLanguageThrows( 74 | Function toLanguageFunction) { 75 | return assertThatCode( 76 | () -> 77 | toLanguageFunction.apply( 78 | new BeancountAntlrToLanguage(null, "TestUtil.assertToLanguageThrows(Function)"))); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/PadDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class PadDirective extends AbstractDirectiveNode { 12 | private final Account sourceAccount; 13 | private final Account targetAccount; 14 | 15 | private PadDirective( 16 | SourceLocation sourceLocation, 17 | LocalDate date, 18 | List tagsAndLinks, 19 | Account sourceAccount, 20 | Account targetAccount, 21 | Metadata metadata, 22 | Comment comment) { 23 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 24 | this.sourceAccount = Objects.requireNonNull(sourceAccount, "sourceAccount"); 25 | this.targetAccount = Objects.requireNonNull(targetAccount, "targetAccount"); 26 | } 27 | 28 | public Account sourceAccount() { 29 | return sourceAccount; 30 | } 31 | 32 | public Account targetAccount() { 33 | return targetAccount; 34 | } 35 | 36 | public static Builder newPadDirective() { 37 | return new Builder(); 38 | } 39 | 40 | @Override 41 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 42 | return visitor.visitPadDirective(this, context); 43 | } 44 | 45 | @Override 46 | public PadDirective transform(Consumer builderConsumer) { 47 | final Builder b = 48 | new Builder( 49 | sourceLocation(), 50 | date(), 51 | tagsAndLinks(), 52 | metadata(), 53 | sourceAccount, 54 | targetAccount, 55 | comment()); 56 | builderConsumer.accept(b); 57 | return b.build(); 58 | } 59 | 60 | public static final class Builder extends AbstractDirectiveNode.Builder { 61 | private Account sourceAccount; 62 | private Account targetAccount; 63 | 64 | private Builder() {} 65 | 66 | private Builder( 67 | SourceLocation sourceLocation, 68 | LocalDate date, 69 | List tagsAndLinks, 70 | Metadata metadata, 71 | Account sourceAccount, 72 | Account targetAccount, 73 | Comment comment) { 74 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 75 | this.sourceAccount = sourceAccount; 76 | this.targetAccount = targetAccount; 77 | } 78 | 79 | @Override 80 | public PadDirective build() { 81 | return new PadDirective( 82 | sourceLocation(), 83 | date(), 84 | tagsAndLinks(), 85 | sourceAccount, 86 | targetAccount, 87 | metadata(), 88 | comment()); 89 | } 90 | 91 | public Account sourceAccount() { 92 | return sourceAccount; 93 | } 94 | 95 | public Builder sourceAccount(Account sourceAccount) { 96 | this.sourceAccount = sourceAccount; 97 | return this; 98 | } 99 | 100 | public Account targetAccount() { 101 | return targetAccount; 102 | } 103 | 104 | public Builder targetAccount(Account targetAccount) { 105 | this.targetAccount = targetAccount; 106 | return this; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/CustomDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import java.util.function.Consumer; 10 | import nl.jrdie.beancount.language.tools.NodeVisitor; 11 | import nl.jrdie.beancount.util.ImmutableKit; 12 | 13 | public final class CustomDirective 14 | extends AbstractDirectiveNode { 15 | 16 | private final String name; 17 | private final List values; 18 | 19 | private CustomDirective( 20 | SourceLocation sourceLocation, 21 | LocalDate date, 22 | String name, 23 | List values, 24 | Metadata metadata, 25 | Comment comment) { 26 | super(sourceLocation, date, ImmutableKit.emptyList(), metadata, comment); 27 | this.name = Objects.requireNonNull(name, "name"); 28 | this.values = Objects.requireNonNull(values, "values"); 29 | } 30 | 31 | @Override 32 | public List tagsAndLinks() { 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | @Override 37 | public Collection links() { 38 | throw new UnsupportedOperationException(); 39 | } 40 | 41 | @Override 42 | public Collection tags() { 43 | throw new UnsupportedOperationException(); 44 | } 45 | 46 | public String name() { 47 | return name; 48 | } 49 | 50 | public List values() { 51 | return values; 52 | } 53 | 54 | public static Builder newCustomDirective() { 55 | return new Builder(); 56 | } 57 | 58 | @Override 59 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 60 | return visitor.visitCustomDirective(this, context); 61 | } 62 | 63 | @Override 64 | public CustomDirective transform(Consumer builderConsumer) { 65 | final Builder b = 66 | new Builder(sourceLocation(), date(), tagsAndLinks(), metadata(), name, values, comment()); 67 | builderConsumer.accept(b); 68 | return b.build(); 69 | } 70 | 71 | public static final class Builder 72 | extends AbstractDirectiveNode.Builder { 73 | private String name; 74 | private List values; 75 | 76 | private Builder() {} 77 | 78 | private Builder( 79 | SourceLocation sourceLocation, 80 | LocalDate date, 81 | List tagsAndLinks, 82 | Metadata metadata, 83 | String name, 84 | List values, 85 | Comment comment) { 86 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 87 | this.name = name; 88 | this.values = values; 89 | } 90 | 91 | @Override 92 | public CustomDirective build() { 93 | return new CustomDirective(sourceLocation(), date(), name, values, metadata(), comment()); 94 | } 95 | 96 | public String name() { 97 | return name; 98 | } 99 | 100 | public Builder name(String name) { 101 | this.name = name; 102 | return this; 103 | } 104 | 105 | public List values() { 106 | return values; 107 | } 108 | 109 | public Builder values(List values) { 110 | this.values = values; 111 | return this; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/LexCommand.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.nio.file.Path; 5 | import java.util.List; 6 | import java.util.concurrent.Callable; 7 | import nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer; 8 | import org.antlr.v4.runtime.CharStream; 9 | import org.antlr.v4.runtime.CharStreams; 10 | import org.antlr.v4.runtime.Token; 11 | import picocli.CommandLine.Command; 12 | import picocli.CommandLine.Option; 13 | import picocli.CommandLine.Parameters; 14 | 15 | @Command(name = "lex", description = "This command dumps the lexer output") 16 | public class LexCommand implements Callable { 17 | 18 | @Parameters(index = "0", description = "The Beancount file") 19 | private Path file; 20 | 21 | @Option( 22 | names = "--no-header", 23 | description = "Do not display the descriptive header", 24 | defaultValue = "false") 25 | private boolean noHeader; 26 | 27 | @Override 28 | public Integer call() throws Exception { 29 | final CharStream charStream = CharStreams.fromPath(file, StandardCharsets.UTF_8); 30 | final BeancountAntlrLexer lexer = new BeancountAntlrLexer(charStream); 31 | final List tokens = lexer.getAllTokens(); 32 | final StringBuilder sb = new StringBuilder(); 33 | int maxLineNumLength = 0; 34 | int maxColNumLength = 0; 35 | int maxTokenNameLength = 0; 36 | int maxSymbolNameLength = 0; 37 | for (Token token : tokens) { 38 | maxTokenNameLength = Math.max(tokenName(token).length(), maxTokenNameLength); 39 | maxSymbolNameLength = Math.max(symbolicName(token).length(), maxSymbolNameLength); 40 | maxLineNumLength = Math.max(String.valueOf(token.getLine()).length(), maxLineNumLength); 41 | maxColNumLength = 42 | Math.max(String.valueOf(token.getCharPositionInLine()).length(), maxColNumLength); 43 | } 44 | if (!noHeader) { 45 | maxTokenNameLength = Math.max("Class".length(), maxTokenNameLength); 46 | maxSymbolNameLength = Math.max("Symbol".length(), maxSymbolNameLength); 47 | maxLineNumLength = Math.max("Line".length(), maxLineNumLength); 48 | maxColNumLength = Math.max("Col".length(), maxColNumLength); 49 | } 50 | final String format = 51 | "%" 52 | + maxTokenNameLength 53 | + "s %" 54 | + maxSymbolNameLength 55 | + "s %" 56 | + maxLineNumLength 57 | + "s:%-" 58 | + maxColNumLength 59 | + "s \"%s\"\n"; 60 | if (!noHeader) { 61 | sb.append(String.format(format, "Class", "Symbol", "Line", "Col", "Text")); 62 | } 63 | for (Token token : tokens) { 64 | final String tokenName = tokenName(token); 65 | sb.append( 66 | String.format( 67 | format, 68 | tokenName, 69 | symbolicName(token), 70 | token.getLine(), 71 | token.getCharPositionInLine(), 72 | token.getText().replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r"))); 73 | } 74 | System.out.println(sb); 75 | return 0; 76 | } 77 | 78 | private static String symbolicName(Token token) { 79 | final String symbolicName = BeancountAntlrLexer.VOCABULARY.getSymbolicName(token.getType()); 80 | return symbolicName == null ? String.valueOf(token.getType()) : symbolicName; 81 | } 82 | 83 | private static String tokenName(Token token) { 84 | return switch (token.getClass().getSimpleName()) { 85 | case "FakeCommentBeforeEolToken" -> "CommentBefEol"; 86 | case "FakeEolAfterCommentToken" -> "EolAftComment"; 87 | default -> token.getClass().getSimpleName(); 88 | }; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/MergeJournal.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.StandardCharsets; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.concurrent.Callable; 10 | import nl.jrdie.beancount.Beancount; 11 | import nl.jrdie.beancount.BeancountInvalidStateException; 12 | import nl.jrdie.beancount.cli.commands.mixin.SingleOutput; 13 | import nl.jrdie.beancount.cli.internal.transformations.FlattenJournal; 14 | import nl.jrdie.beancount.io.SimpleBeancountPrinter; 15 | import nl.jrdie.beancount.language.Journal; 16 | import nl.jrdie.beancount.language.JournalDeclaration; 17 | import nl.jrdie.beancount.language.SourceLocation; 18 | import nl.jrdie.beancount.util.ImmutableKit; 19 | import picocli.CommandLine.Command; 20 | import picocli.CommandLine.Mixin; 21 | import picocli.CommandLine.Option; 22 | import picocli.CommandLine.Parameters; 23 | 24 | @Command( 25 | name = "merge", 26 | description = 27 | "This command (recursively) merges all journals included using include pragmas, and creates one composite journal") 28 | public class MergeJournal implements Callable { 29 | 30 | @Parameters( 31 | index = "0", 32 | arity = "1..*", 33 | description = "The Beancount file(s) at the root(s) of your inclusion tree(s)") 34 | private List files; 35 | 36 | @Option( 37 | names = "-r", 38 | description = 39 | "Recursively aggregate (also flattens include pragmas within included journals)", 40 | defaultValue = "false") 41 | private boolean recurse; 42 | 43 | @Option( 44 | names = "--keep-include-pragmas", 45 | description = 46 | "Keep the include pragmas present in the composite journal (just before the contents of said journal)", 47 | defaultValue = "false") 48 | private boolean keepIncludePragmas; 49 | 50 | @Mixin private SingleOutput output; 51 | 52 | @Override 53 | public Integer call() throws IOException { 54 | Beancount beancount = Beancount.newBeancount().build(); 55 | if (files.isEmpty()) { 56 | throw new BeancountInvalidStateException(); 57 | } 58 | final Journal masterJournal = 59 | files.stream() 60 | .map(beancount::createJournalSync) 61 | .reduce( 62 | Journal.newJournal() 63 | .declarations(ImmutableKit.emptyList()) 64 | .sourceLocation(SourceLocation.EMPTY) 65 | .build(), 66 | (journal, journal2) -> 67 | FlattenJournal.flattenJournal(journal, recurse, keepIncludePragmas) 68 | .transform( 69 | builder -> { 70 | List> combinedDeclarations = 71 | new ArrayList<>(builder.declarations()); 72 | combinedDeclarations.addAll( 73 | FlattenJournal.flattenJournal( 74 | journal2, recurse, keepIncludePragmas) 75 | .declarations()); 76 | builder.declarations(combinedDeclarations); 77 | })); 78 | SimpleBeancountPrinter beancountPrinter = SimpleBeancountPrinter.newDefaultPrinter(); 79 | final String journalAsString = beancountPrinter.print(masterJournal); 80 | if (output.hasOutput()) { 81 | Files.writeString(output.outputFile(), journalAsString, StandardCharsets.UTF_8); 82 | } else { 83 | System.out.println(journalAsString); 84 | } 85 | return 0; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/jordie/FixStuffCommand.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands.jordie; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import graphql.util.TreeTransformerUtil; 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.concurrent.Callable; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | import nl.jrdie.beancount.Beancount; 13 | import nl.jrdie.beancount.cli.commands.mixin.SingleOutput; 14 | import nl.jrdie.beancount.io.SimpleBeancountPrinter; 15 | import nl.jrdie.beancount.language.Journal; 16 | import nl.jrdie.beancount.language.Node; 17 | import nl.jrdie.beancount.language.TransactionDirective; 18 | import nl.jrdie.beancount.language.tools.AstTransformer; 19 | import nl.jrdie.beancount.language.tools.NodeVisitorStub; 20 | import nl.jrdie.beancount.parser.BeancountUtil; 21 | import picocli.CommandLine.Command; 22 | import picocli.CommandLine.Mixin; 23 | import picocli.CommandLine.Parameters; 24 | import picocli.CommandLine.ScopeType; 25 | 26 | @Command(name = "fixjordie", scope = ScopeType.LOCAL) 27 | public class FixStuffCommand implements Callable { 28 | 29 | @Parameters(index = "0", description = "The Beancount file") 30 | private Path file; 31 | 32 | @Mixin private SingleOutput output; 33 | 34 | @Override 35 | public Integer call() throws Exception { 36 | Beancount beancount = Beancount.newBeancount().build(); 37 | Journal journal = beancount.createJournalSyncWithoutIncludes(file); 38 | journal = 39 | (Journal) 40 | AstTransformer.transform( 41 | journal, 42 | new NodeVisitorStub() { 43 | @Override 44 | public TraversalControl visitTransactionDirective( 45 | TransactionDirective td, TraverserContext> data) { 46 | final String regex = 47 | "^Betaalautomaat ([0-9][0-9]?:[0-9][0-9]?) pasnr\\. [0-9][0-9][0-9]$"; 48 | if (td.narration().matches(regex) 49 | && !BeancountUtil.hasMetadataWithKey(td.metadata(), "time") 50 | && !BeancountUtil.hasMetadataWithKey(td.metadata(), "desc")) { 51 | Matcher matcher = Pattern.compile(regex).matcher(td.narration()); 52 | matcher.matches(); 53 | TransactionDirective newtd = 54 | td.transform( 55 | transactionDirectiveBuilder -> 56 | transactionDirectiveBuilder 57 | .narration(null) 58 | .metadata( 59 | BeancountUtil.addMetadataAtStart( 60 | transactionDirectiveBuilder.metadata(), 61 | BeancountUtil.newMetadataItem( 62 | "time", matcher.toMatchResult().group(1)), 63 | BeancountUtil.newMetadataItem( 64 | "desc", td.narration())))); 65 | return TreeTransformerUtil.changeNode(data, newtd); 66 | } 67 | return TraversalControl.CONTINUE; 68 | } 69 | }); 70 | 71 | SimpleBeancountPrinter beancountPrinter = SimpleBeancountPrinter.newDefaultPrinter(); 72 | final String journalAsString = beancountPrinter.print(journal); 73 | if (output.hasOutput()) { 74 | Files.writeString(output.outputFile(), journalAsString, StandardCharsets.UTF_8); 75 | } else { 76 | System.out.println(journalAsString); 77 | } 78 | return 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/internal/NodeChildrenContainer.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools.internal; 2 | 3 | import static graphql.Assert.assertNotNull; 4 | import static graphql.collect.ImmutableKit.emptyList; 5 | 6 | import graphql.PublicApi; 7 | import java.util.ArrayList; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.function.Consumer; 12 | import nl.jrdie.beancount.language.Node; 13 | 14 | @PublicApi 15 | public class NodeChildrenContainer { 16 | 17 | private final Map>> children = new LinkedHashMap<>(); 18 | 19 | private NodeChildrenContainer(Map>> children) { 20 | this.children.putAll(assertNotNull(children)); 21 | } 22 | 23 | @SuppressWarnings("unchecked") 24 | public > List getChildren(String key) { 25 | return (List) children.getOrDefault(key, emptyList()); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public > T getChildOrNull(String key) { 30 | List> result = children.getOrDefault(key, emptyList()); 31 | if (result.size() > 1) { 32 | throw new IllegalStateException("children " + key + " is not a single value"); 33 | } 34 | return result.size() > 0 ? (T) result.get(0) : null; 35 | } 36 | 37 | public Map>> getChildren() { 38 | return new LinkedHashMap<>(children); 39 | } 40 | 41 | public static Builder newNodeChildrenContainer() { 42 | return new Builder(); 43 | } 44 | 45 | public static Builder newNodeChildrenContainer( 46 | Map>> childrenMap) { 47 | return new Builder().children(childrenMap); 48 | } 49 | 50 | public static Builder newNodeChildrenContainer(NodeChildrenContainer existing) { 51 | return new Builder(existing); 52 | } 53 | 54 | public NodeChildrenContainer transform(Consumer builderConsumer) { 55 | Builder builder = new Builder(this); 56 | builderConsumer.accept(builder); 57 | return builder.build(); 58 | } 59 | 60 | public boolean isEmpty() { 61 | return this.children.isEmpty(); 62 | } 63 | 64 | public static class Builder { 65 | private final Map>> children = new LinkedHashMap<>(); 66 | 67 | private Builder() {} 68 | 69 | private Builder(NodeChildrenContainer other) { 70 | this.children.putAll(other.children); 71 | } 72 | 73 | public Builder child(String key, Node child) { 74 | // we allow null here to make the actual nodes easier 75 | if (child == null) { 76 | return this; 77 | } 78 | children.computeIfAbsent(key, (k) -> new ArrayList<>()); 79 | children.get(key).add(child); 80 | return this; 81 | } 82 | 83 | public Builder children(String key, List> children) { 84 | this.children.computeIfAbsent(key, (k) -> new ArrayList<>()); 85 | this.children.get(key).addAll(children); 86 | return this; 87 | } 88 | 89 | @SuppressWarnings("unchecked") 90 | public Builder children(Map>> children) { 91 | this.children.clear(); 92 | this.children.putAll((Map>>) children); 93 | return this; 94 | } 95 | 96 | public Builder replaceChild(String key, int index, Node newChild) { 97 | assertNotNull(newChild); 98 | this.children.get(key).set(index, newChild); 99 | return this; 100 | } 101 | 102 | public Builder removeChild(String key, int index) { 103 | this.children.get(key).remove(index); 104 | return this; 105 | } 106 | 107 | public NodeChildrenContainer build() { 108 | return new NodeChildrenContainer(this.children); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/OpenDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.annotation.Beta; 10 | import nl.jrdie.beancount.language.tools.NodeVisitor; 11 | 12 | public final class OpenDirective 13 | extends AbstractDirectiveNode { 14 | private final Account account; 15 | private final List commodities; 16 | @Beta private final String bookingMethod; 17 | 18 | private OpenDirective( 19 | SourceLocation sourceLocation, 20 | LocalDate date, 21 | List tagsAndLinks, 22 | Account account, 23 | List commodities, 24 | @Beta String bookingMethod, 25 | Metadata metadata, 26 | Comment comment) { 27 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 28 | this.account = Objects.requireNonNull(account, "account"); 29 | this.commodities = Objects.requireNonNull(commodities, "commodities"); 30 | this.bookingMethod = bookingMethod; 31 | } 32 | 33 | public Account account() { 34 | return account; 35 | } 36 | 37 | public List commodities() { 38 | return commodities; 39 | } 40 | 41 | @Beta 42 | public String bookingMethod() { 43 | return bookingMethod; 44 | } 45 | 46 | public static Builder newOpenDirective() { 47 | return new Builder(); 48 | } 49 | 50 | @Override 51 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 52 | return visitor.visitOpenDirective(this, context); 53 | } 54 | 55 | @Override 56 | public OpenDirective transform(Consumer builderConsumer) { 57 | final Builder b = 58 | new Builder( 59 | sourceLocation(), 60 | date(), 61 | tagsAndLinks(), 62 | metadata(), 63 | account, 64 | commodities, 65 | bookingMethod, 66 | comment()); 67 | builderConsumer.accept(b); 68 | return b.build(); 69 | } 70 | 71 | public static final class Builder extends AbstractDirectiveNode.Builder { 72 | private Account account; 73 | private List commodities; 74 | @Beta private String bookingMethod; 75 | 76 | private Builder() {} 77 | 78 | private Builder( 79 | SourceLocation sourceLocation, 80 | LocalDate date, 81 | List tagsAndLinks, 82 | Metadata metadata, 83 | Account account, 84 | List commodities, 85 | String bookingMethod, 86 | Comment comment) { 87 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 88 | this.account = account; 89 | this.commodities = commodities; 90 | this.bookingMethod = bookingMethod; 91 | } 92 | 93 | @Override 94 | public OpenDirective build() { 95 | return new OpenDirective( 96 | sourceLocation(), 97 | date(), 98 | tagsAndLinks(), 99 | account, 100 | commodities, 101 | bookingMethod, 102 | metadata(), 103 | comment()); 104 | } 105 | 106 | public Account account() { 107 | return account; 108 | } 109 | 110 | public Builder account(Account account) { 111 | this.account = account; 112 | return this; 113 | } 114 | 115 | public List commodities() { 116 | return commodities; 117 | } 118 | 119 | public Builder commodities(List commodities) { 120 | this.commodities = commodities; 121 | return this; 122 | } 123 | 124 | @Beta 125 | public String bookingMethod() { 126 | return bookingMethod; 127 | } 128 | 129 | @Beta 130 | public Builder bookingMethod(String bookingMethod) { 131 | this.bookingMethod = bookingMethod; 132 | return this; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/Beancount.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount; 2 | 3 | import java.nio.file.Path; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.concurrent.CompletableFuture; 9 | import nl.jrdie.beancount.annotation.Beta; 10 | import nl.jrdie.beancount.construe.BeancountConstrueStrategy; 11 | import nl.jrdie.beancount.construe.SyncConstrueStrategy; 12 | import nl.jrdie.beancount.language.IncludePragma; 13 | import nl.jrdie.beancount.language.Journal; 14 | import nl.jrdie.beancount.language.JournalDeclaration; 15 | import nl.jrdie.beancount.language.tools.NestJournalNodeInIncludePragmaTransformer; 16 | import nl.jrdie.beancount.parser.BeancountParser; 17 | 18 | public final class Beancount { 19 | 20 | private final BeancountConstrueStrategy construeStrategy; 21 | 22 | private Beancount() { 23 | this.construeStrategy = new SyncConstrueStrategy(); 24 | } 25 | 26 | public Journal createJournalSync(Path path) { 27 | return createJournal(path).join(); 28 | } 29 | 30 | public CompletableFuture createJournal(Path path) { 31 | return createJournal(path, BeancountParser.newParser(), true); 32 | } 33 | 34 | @Beta 35 | public Journal createJournalSyncWithoutIncludes(Path path) { 36 | return createJournalWithoutIncludes(path).join(); 37 | } 38 | 39 | @Beta 40 | public CompletableFuture createJournalWithoutIncludes(Path path) { 41 | return createJournal(path, BeancountParser.newParser(), false); 42 | } 43 | 44 | private CompletableFuture createJournal( 45 | Path path, BeancountParser beancountParser, boolean resolveIncludePragmas) { 46 | final CompletableFuture rootJournal = 47 | construeStrategy.construe(() -> beancountParser.parseJournal(path)); 48 | if (!resolveIncludePragmas) { 49 | return rootJournal; 50 | } 51 | return rootJournal.thenCompose( 52 | journal -> 53 | resolveIncludePragmas(path, journal, beancountParser) 54 | .thenApply( 55 | map -> NestJournalNodeInIncludePragmaTransformer.transform(journal, map))); 56 | } 57 | 58 | private CompletableFuture> resolveIncludePragmas( 59 | Path theJournalPath, Journal theJournal, BeancountParser beancountParser) { 60 | List includePragmas = new ArrayList<>(); 61 | for (JournalDeclaration declaration : theJournal.declarations()) { 62 | if (declaration instanceof IncludePragma includePragma) { 63 | includePragmas.add(includePragma); 64 | } 65 | } 66 | // TODO: new CompletableFuture<>[includePragmas.size()] not seen as an error by IDEA, report on 67 | // tracker 68 | @SuppressWarnings("unchecked") 69 | CompletableFuture[] includes = 70 | (CompletableFuture[]) new CompletableFuture[includePragmas.size()]; 71 | for (int i = 0; i < includePragmas.size(); i++) { 72 | IncludePragma includePragma = includePragmas.get(i); 73 | Path includePath = theJournalPath.getParent().resolve(includePragma.filename()); 74 | includes[i] = createJournal(includePath, beancountParser, true); 75 | } 76 | 77 | CompletableFuture> result = new CompletableFuture<>(); 78 | 79 | CompletableFuture.allOf(includes) 80 | .whenComplete( 81 | (nil, t) -> { 82 | if (t != null) { 83 | result.completeExceptionally(t); 84 | return; 85 | } 86 | Map resolved = new HashMap<>(); 87 | for (int i = 0; i < includePragmas.size(); i++) { 88 | resolved.put(includePragmas.get(i), includes[i].join()); 89 | } 90 | result.complete(resolved); 91 | }); 92 | 93 | return result; 94 | } 95 | 96 | public static Builder newBeancount() { 97 | return new Builder(); 98 | } 99 | 100 | public static class Builder { 101 | private Builder() {} 102 | 103 | public Beancount build() { 104 | return new Beancount(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/BeancountUtil.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import java.util.function.Predicate; 8 | import java.util.stream.Stream; 9 | import nl.jrdie.beancount.language.Journal; 10 | import nl.jrdie.beancount.language.JournalDeclaration; 11 | import nl.jrdie.beancount.language.Metadata; 12 | import nl.jrdie.beancount.language.MetadataItem; 13 | import nl.jrdie.beancount.language.MetadataKey; 14 | import nl.jrdie.beancount.language.MetadataLine; 15 | import nl.jrdie.beancount.language.MetadataValue; 16 | import nl.jrdie.beancount.language.StringValue; 17 | 18 | public final class BeancountUtil { 19 | 20 | private BeancountUtil() {} 21 | 22 | public static > List findDeclarationsOfType( 23 | Journal journal, Class type) { 24 | return journal.declarations().stream().filter(type::isInstance).map(type::cast).toList(); 25 | } 26 | 27 | public static List> findDeclarations( 28 | Journal journal, Predicate> declarationPredicate) { 29 | return journalDeclarationStream(journal, declarationPredicate).toList(); 30 | } 31 | 32 | public static > List findDeclarations( 33 | Journal journal, Class type, Predicate declarationPredicate) { 34 | return journal.declarations().stream() 35 | .mapMulti( 36 | (journalDeclaration, consumer) -> { 37 | if (type.isInstance(journalDeclaration)) { 38 | T t = type.cast(journalDeclaration); 39 | if (declarationPredicate.test(t)) { 40 | consumer.accept(t); 41 | } 42 | } 43 | }) 44 | .toList(); 45 | } 46 | 47 | private static Stream> journalDeclarationStream( 48 | Journal journal, Predicate> declarationPredicate) { 49 | return journal.declarations().stream().filter(declarationPredicate); 50 | } 51 | 52 | public static boolean hasMetadataWithKey(Metadata metadata, String key) { 53 | Objects.requireNonNull(metadata, "metadata"); 54 | Objects.requireNonNull(key, "key"); 55 | return metadata.metadata().stream() 56 | .anyMatch(ml -> ml instanceof MetadataItem mi && mi.key().key().equals(key)); 57 | } 58 | 59 | public static Metadata addMetadataAtStart(Metadata metadata, MetadataLine... linesToAdd) { 60 | return metadata.transform( 61 | builder -> { 62 | List metadataLines = new ArrayList<>(Arrays.asList(linesToAdd)); 63 | metadataLines.addAll(builder.metadata()); 64 | builder.metadata(metadataLines); 65 | }); 66 | } 67 | 68 | public static Metadata addMetadataAtEnd(Metadata metadata, MetadataLine... linesToAdd) { 69 | return metadata.transform( 70 | builder -> { 71 | List metadataLines = builder.metadata(); 72 | metadataLines = new ArrayList<>(metadataLines); 73 | metadataLines.addAll(Arrays.asList(linesToAdd)); 74 | builder.metadata(metadataLines); 75 | }); 76 | } 77 | 78 | public static boolean withNewMetadataLine(Metadata metadata, String key) { 79 | Objects.requireNonNull(metadata, "metadata"); 80 | Objects.requireNonNull(key, "key"); 81 | return metadata.metadata().stream() 82 | .anyMatch(ml -> ml instanceof MetadataItem mi && mi.key().key().equals(key)); 83 | } 84 | 85 | public static MetadataItem newMetadataItem(String key, String value) { 86 | return MetadataItem.newMetadataItem() 87 | .key(MetadataKey.newMetadataKey().key(key).build()) 88 | .value(StringValue.newStringValue().value(value).build()) 89 | .build(); 90 | } 91 | 92 | public static MetadataValue getMetadataValue(Metadata metadata, String key) { 93 | Objects.requireNonNull(metadata, "metadata"); 94 | Objects.requireNonNull(key, "key"); 95 | return metadata.metadata().stream() 96 | .filter(ml -> ml instanceof MetadataItem mi && mi.key().key().equals(key)) 97 | .findFirst() 98 | .map(metadataLine -> ((MetadataItem) metadataLine).value()) 99 | .orElse(null); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/TransactionDirective.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.time.LocalDate; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | import nl.jrdie.beancount.language.tools.NodeVisitor; 10 | 11 | public final class TransactionDirective 12 | extends AbstractDirectiveNode { 13 | private final String payee; 14 | private final String narration; 15 | private final Flag flag; 16 | private final List postings; 17 | 18 | private TransactionDirective( 19 | SourceLocation sourceLocation, 20 | LocalDate date, 21 | List tagsAndLinks, 22 | String payee, 23 | String narration, 24 | Flag flag, 25 | List postings, 26 | Metadata metadata, 27 | Comment comment) { 28 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 29 | this.payee = payee; 30 | this.narration = narration; 31 | this.flag = Objects.requireNonNull(flag, "flag"); 32 | this.postings = Objects.requireNonNull(postings, "postings"); 33 | } 34 | 35 | public String payee() { 36 | return payee; 37 | } 38 | 39 | public String narration() { 40 | return narration; 41 | } 42 | 43 | public Flag flag() { 44 | return flag; 45 | } 46 | 47 | public List postings() { 48 | return postings; 49 | } 50 | 51 | public static Builder newTransactionDirective() { 52 | return new Builder(); 53 | } 54 | 55 | @Override 56 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 57 | return visitor.visitTransactionDirective(this, context); 58 | } 59 | 60 | @Override 61 | public TransactionDirective transform(Consumer builderConsumer) { 62 | final Builder b = 63 | new Builder( 64 | sourceLocation(), 65 | date(), 66 | tagsAndLinks(), 67 | metadata(), 68 | payee, 69 | narration, 70 | flag, 71 | postings, 72 | comment()); 73 | builderConsumer.accept(b); 74 | return b.build(); 75 | } 76 | 77 | public static final class Builder 78 | extends AbstractDirectiveNode.Builder { 79 | private String payee; 80 | private String narration; 81 | private Flag flag; 82 | private List postings; 83 | 84 | private Builder() {} 85 | 86 | private Builder( 87 | SourceLocation sourceLocation, 88 | LocalDate date, 89 | List tagsAndLinks, 90 | Metadata metadata, 91 | String payee, 92 | String narration, 93 | Flag flag, 94 | List postings, 95 | Comment comment) { 96 | super(sourceLocation, date, tagsAndLinks, metadata, comment); 97 | this.payee = payee; 98 | this.narration = narration; 99 | this.flag = flag; 100 | this.postings = postings; 101 | } 102 | 103 | @Override 104 | public TransactionDirective build() { 105 | return new TransactionDirective( 106 | sourceLocation(), 107 | date(), 108 | tagsAndLinks(), 109 | payee, 110 | narration, 111 | flag, 112 | postings, 113 | metadata(), 114 | comment()); 115 | } 116 | 117 | public String payee() { 118 | return payee; 119 | } 120 | 121 | public Builder payee(String payee) { 122 | this.payee = payee; 123 | return this; 124 | } 125 | 126 | public String narration() { 127 | return narration; 128 | } 129 | 130 | public Builder narration(String narration) { 131 | this.narration = narration; 132 | return this; 133 | } 134 | 135 | public Flag flag() { 136 | return flag; 137 | } 138 | 139 | public Builder flag(Flag flag) { 140 | this.flag = flag; 141 | return this; 142 | } 143 | 144 | public List postings() { 145 | return postings; 146 | } 147 | 148 | public Builder postings(List postings) { 149 | this.postings = postings; 150 | return this; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/tools/NodeVisitorStub.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language.tools; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import nl.jrdie.beancount.language.BalanceDirective; 6 | import nl.jrdie.beancount.language.CloseDirective; 7 | import nl.jrdie.beancount.language.Comment; 8 | import nl.jrdie.beancount.language.CommodityDirective; 9 | import nl.jrdie.beancount.language.CustomDirective; 10 | import nl.jrdie.beancount.language.DocumentDirective; 11 | import nl.jrdie.beancount.language.EventDirective; 12 | import nl.jrdie.beancount.language.IncludePragma; 13 | import nl.jrdie.beancount.language.Journal; 14 | import nl.jrdie.beancount.language.Node; 15 | import nl.jrdie.beancount.language.NoteDirective; 16 | import nl.jrdie.beancount.language.OpenDirective; 17 | import nl.jrdie.beancount.language.OptionPragma; 18 | import nl.jrdie.beancount.language.PadDirective; 19 | import nl.jrdie.beancount.language.PluginPragma; 20 | import nl.jrdie.beancount.language.Posting; 21 | import nl.jrdie.beancount.language.PriceDirective; 22 | import nl.jrdie.beancount.language.QueryDirective; 23 | import nl.jrdie.beancount.language.TransactionDirective; 24 | 25 | public class NodeVisitorStub implements NodeVisitor { 26 | @Override 27 | public TraversalControl visitIncludePragma(IncludePragma ip, TraverserContext> data) { 28 | return TraversalControl.CONTINUE; 29 | } 30 | 31 | @Override 32 | public TraversalControl visitPluginPragma(PluginPragma pp, TraverserContext> data) { 33 | return TraversalControl.CONTINUE; 34 | } 35 | 36 | @Override 37 | public TraversalControl visitOptionPragma(OptionPragma op, TraverserContext> data) { 38 | return TraversalControl.CONTINUE; 39 | } 40 | 41 | @Override 42 | public TraversalControl visitCloseDirective( 43 | CloseDirective cd, TraverserContext> data) { 44 | return TraversalControl.CONTINUE; 45 | } 46 | 47 | @Override 48 | public TraversalControl visitBalanceDirective( 49 | BalanceDirective bd, TraverserContext> data) { 50 | return TraversalControl.CONTINUE; 51 | } 52 | 53 | @Override 54 | public TraversalControl visitCommodityDirective( 55 | CommodityDirective cd, TraverserContext> data) { 56 | return TraversalControl.CONTINUE; 57 | } 58 | 59 | @Override 60 | public TraversalControl visitCustomDirective( 61 | CustomDirective cd, TraverserContext> data) { 62 | return TraversalControl.CONTINUE; 63 | } 64 | 65 | @Override 66 | public TraversalControl visitDocumentDirective( 67 | DocumentDirective dd, TraverserContext> data) { 68 | return TraversalControl.CONTINUE; 69 | } 70 | 71 | @Override 72 | public TraversalControl visitEventDirective( 73 | EventDirective ed, TraverserContext> data) { 74 | return TraversalControl.CONTINUE; 75 | } 76 | 77 | @Override 78 | public TraversalControl visitNoteDirective(NoteDirective nd, TraverserContext> data) { 79 | return TraversalControl.CONTINUE; 80 | } 81 | 82 | @Override 83 | public TraversalControl visitOpenDirective(OpenDirective od, TraverserContext> data) { 84 | return TraversalControl.CONTINUE; 85 | } 86 | 87 | @Override 88 | public TraversalControl visitQueryDirective( 89 | QueryDirective qd, TraverserContext> data) { 90 | return TraversalControl.CONTINUE; 91 | } 92 | 93 | @Override 94 | public TraversalControl visitTransactionDirective( 95 | TransactionDirective td, TraverserContext> data) { 96 | return TraversalControl.CONTINUE; 97 | } 98 | 99 | @Override 100 | public TraversalControl visitPriceDirective( 101 | PriceDirective pd, TraverserContext> data) { 102 | return TraversalControl.CONTINUE; 103 | } 104 | 105 | @Override 106 | public TraversalControl visitPadDirective(PadDirective pd, TraverserContext> data) { 107 | return TraversalControl.CONTINUE; 108 | } 109 | 110 | @Override 111 | public TraversalControl visitJournal(Journal journal, TraverserContext> data) { 112 | return TraversalControl.CONTINUE; 113 | } 114 | 115 | @Override 116 | public TraversalControl visitComment(Comment comment, TraverserContext> data) { 117 | return TraversalControl.CONTINUE; 118 | } 119 | 120 | @Override 121 | public TraversalControl visitPosting(Posting posting, TraverserContext> data) { 122 | return TraversalControl.CONTINUE; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/parser/BeancountParser.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.parser; 2 | 3 | import java.io.IOException; 4 | import java.io.Reader; 5 | import java.io.UncheckedIOException; 6 | import java.nio.file.Path; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | import nl.jrdie.beancount.language.Journal; 9 | import nl.jrdie.beancount.language.SourceLocation; 10 | import nl.jrdie.beancount.parser.antlr.BeancountAntlrLexer; 11 | import nl.jrdie.beancount.parser.antlr.BeancountAntlrParser; 12 | import org.antlr.v4.runtime.BaseErrorListener; 13 | import org.antlr.v4.runtime.CharStream; 14 | import org.antlr.v4.runtime.CharStreams; 15 | import org.antlr.v4.runtime.CommonTokenStream; 16 | import org.antlr.v4.runtime.RecognitionException; 17 | import org.antlr.v4.runtime.Recognizer; 18 | 19 | public class BeancountParser { 20 | 21 | private BeancountParser() {} 22 | 23 | public static BeancountParser newParser() { 24 | return new BeancountParser(); 25 | } 26 | 27 | public Journal parseJournal(Path path) { 28 | return parseJournalImpl(path, path.getFileName().toString(), CharStreams::fromPath); 29 | } 30 | 31 | public Journal parseJournal(Reader reader) { 32 | return parseJournalImpl(reader, null, CharStreams::fromReader); 33 | } 34 | 35 | public static AtomicLong ns = new AtomicLong(0); 36 | public static AtomicLong c = new AtomicLong(0); 37 | 38 | private Journal parseJournalImpl(T t, String sourceName, CharStreamFunction func) { 39 | final CharStream charStream; 40 | try { 41 | charStream = func.apply(t); 42 | } catch (IOException e) { 43 | throw new UncheckedIOException(e); 44 | } 45 | 46 | final BeancountAntlrLexer lexer = new BeancountAntlrLexer(charStream); 47 | lexer.removeErrorListeners(); 48 | lexer.addErrorListener( 49 | new BaseErrorListener() { 50 | @Override 51 | public void syntaxError( 52 | Recognizer recognizer, 53 | Object offendingSymbol, 54 | int line, 55 | int charPositionInLine, 56 | String message, 57 | RecognitionException e) { 58 | SourceLocation sourceLocation = 59 | AntlrHelper.createSourceLocation(line, charPositionInLine, sourceName); 60 | throw new InvalidSyntaxException( 61 | sourceLocation, 62 | message, 63 | AntlrHelper.createPreview( 64 | charStream, line, charPositionInLine, charPositionInLine + 1)); 65 | } 66 | }); 67 | 68 | final CommonTokenStream tokens = new CommonTokenStream(lexer); 69 | 70 | final nl.jrdie.beancount.parser.antlr.BeancountAntlrParser antlrParser = 71 | new nl.jrdie.beancount.parser.antlr.BeancountAntlrParser(tokens); 72 | antlrParser.removeErrorListeners(); 73 | antlrParser.addErrorListener( 74 | new BaseErrorListener() { 75 | @Override 76 | public void syntaxError( 77 | Recognizer recognizer, 78 | Object offendingSymbol, 79 | int line, 80 | int charPositionInLine, 81 | String message, 82 | RecognitionException e) { 83 | SourceLocation sourceLocation = 84 | AntlrHelper.createSourceLocation(line, charPositionInLine, sourceName); 85 | throw new InvalidSyntaxException( 86 | sourceLocation, 87 | message, 88 | AntlrHelper.createPreview( 89 | charStream, line, charPositionInLine, charPositionInLine + 1)); 90 | } 91 | }); 92 | 93 | final BeancountAntlrToLanguage toLanguage = new BeancountAntlrToLanguage(tokens, sourceName); 94 | 95 | long a = System.nanoTime(); 96 | final BeancountAntlrParser.JournalContext journalContext = antlrParser.journal(); 97 | ns.addAndGet(System.nanoTime() - a); 98 | c.incrementAndGet(); 99 | 100 | // String tokensDebug = 101 | // tokens.getTokens().stream() 102 | // .mapToInt(Token::getType) 103 | // .mapToObj(BeancountAntlrParser.VOCABULARY::getSymbolicName) 104 | // .collect(Collectors.joining(" ")); 105 | // 106 | // System.out.println(tokensDebug); 107 | 108 | @SuppressWarnings("UnnecessaryLocalVariable") 109 | final Journal journal = toLanguage.createJournal(journalContext); 110 | // ns.addAndGet(System.nanoTime() - a); 111 | // c.incrementAndGet(); 112 | return journal; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /jbeancount-cli/src/main/java/nl/jrdie/beancount/cli/commands/IncludeTreeCommand.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.cli.commands; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.util.HashSet; 6 | import java.util.Iterator; 7 | import java.util.LinkedHashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | import java.util.StringJoiner; 11 | import java.util.concurrent.Callable; 12 | import nl.jrdie.beancount.Beancount; 13 | import nl.jrdie.beancount.cli.internal.include.IncludePair; 14 | import nl.jrdie.beancount.language.IncludePragma; 15 | import nl.jrdie.beancount.language.Journal; 16 | import nl.jrdie.beancount.parser.BeancountUtil; 17 | import nl.jrdie.beancount.util.Assert; 18 | import picocli.CommandLine.Command; 19 | import picocli.CommandLine.Option; 20 | import picocli.CommandLine.Parameters; 21 | 22 | @Command(name = "includes", description = "This command dumps the include pragma mappings") 23 | public class IncludeTreeCommand implements Callable { 24 | 25 | @Parameters(index = "0", description = "The Beancount file") 26 | private Path file; 27 | 28 | @Option( 29 | names = "--experimental-dot", 30 | description = "Output the inclusion mappings as a Graphviz DOT string", 31 | defaultValue = "false") 32 | private boolean useDot; 33 | 34 | @Option( 35 | names = "--experimental-relative", 36 | description = "Outputs relative paths", 37 | defaultValue = "false") 38 | private boolean relative; 39 | 40 | @Override 41 | public Integer call() throws Exception { 42 | Beancount beancount = Beancount.newBeancount().build(); 43 | Journal journal = beancount.createJournalSync(file); 44 | Set includes = new LinkedHashSet<>(); 45 | recurseThroughIncludes(file, journal, includes); 46 | StringBuilder sb = new StringBuilder(); 47 | Set dotDirs = new HashSet<>(); 48 | for (IncludePair includeMapping : includes) { 49 | if (useDot) { 50 | dotDirs.addAll(dotLane(includeMapping.fromJournal(), includeMapping.toJournal(), sb)); 51 | } else { 52 | sb.append(maybeRelativize(includeMapping.fromJournal()).toString()); 53 | sb.append(" -> "); 54 | sb.append(maybeRelativize(includeMapping.toJournal()).toString()); 55 | sb.append('\n'); 56 | } 57 | } 58 | if (useDot) { 59 | StringBuilder dotBuilder = new StringBuilder(); 60 | dotBuilder.append("strict digraph {\n"); 61 | dotBuilder.append(" node [shape=plaintext]\n \""); 62 | dotBuilder.append(file.getFileName().toString()); 63 | dotBuilder.append("\" [shape=septagon]\n"); 64 | if (!dotDirs.isEmpty()) { 65 | for (String dotDir : dotDirs) { 66 | dotBuilder.append(" \""); 67 | dotBuilder.append(dotDir); 68 | dotBuilder.append("\" [shape=box3d]\n"); 69 | } 70 | } 71 | dotBuilder.append(sb); 72 | dotBuilder.append('}'); 73 | sb = dotBuilder; 74 | } 75 | System.out.println(sb); 76 | return 0; 77 | } 78 | 79 | private Set dotLane(Path from, Path to, StringBuilder sb) { 80 | sb.append(" "); 81 | Set directories = new HashSet<>(); 82 | StringJoiner joiner = new StringJoiner(" -> ", "", ""); 83 | joiner.setEmptyValue(""); 84 | for (Iterator iterator = maybeRelativize(from).iterator(); iterator.hasNext(); ) { 85 | Path next = iterator.next(); 86 | if (iterator.hasNext()) { 87 | directories.add(next.getFileName().toString()); 88 | } 89 | joiner.add("\"" + next + "\""); 90 | } 91 | for (Iterator iterator = maybeRelativize(to).iterator(); iterator.hasNext(); ) { 92 | Path next = iterator.next(); 93 | if (iterator.hasNext()) { 94 | directories.add(next.getFileName().toString()); 95 | } 96 | joiner.add("\"" + next + "\""); 97 | } 98 | sb.append(joiner); 99 | sb.append("\n"); 100 | return directories; 101 | } 102 | 103 | private Path maybeRelativize(Path includedPath) { 104 | if (relative) { 105 | return file.getParent().relativize(includedPath); 106 | } else { 107 | return includedPath; 108 | } 109 | } 110 | 111 | private void recurseThroughIncludes( 112 | Path currentPath, Journal currentJournal, Set accumulator) { 113 | Path journalFolder = currentPath.getParent(); 114 | if (!Files.isDirectory(journalFolder)) { 115 | Assert.shouldNeverHappen(); 116 | return; 117 | } 118 | List includePragmas = 119 | BeancountUtil.findDeclarationsOfType(currentJournal, IncludePragma.class); 120 | for (IncludePragma includePragma : includePragmas) { 121 | if (includePragma.journal() == null) { 122 | // TODO Figure out what to do 123 | Assert.shouldNeverHappen(); 124 | return; 125 | } 126 | Path includedFilePath = journalFolder.resolve(includePragma.filename()); 127 | accumulator.add(new IncludePair(currentPath, includedFilePath)); 128 | recurseThroughIncludes(includedFilePath, includePragma.journal(), accumulator); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/AbstractDirectiveNode.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Objects; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | abstract sealed class AbstractDirectiveNode< 12 | T extends Node, B extends AbstractDirectiveNode.Builder> 13 | extends AbstractNode 14 | implements Node, DirectiveNode, JournalDeclaration, LinkAndTagContainer 15 | permits BalanceDirective, 16 | CloseDirective, 17 | CommodityDirective, 18 | CustomDirective, 19 | DocumentDirective, 20 | EventDirective, 21 | NoteDirective, 22 | OpenDirective, 23 | PadDirective, 24 | PriceDirective, 25 | QueryDirective, 26 | TransactionDirective { 27 | 28 | private final LocalDate date; 29 | private final List tagsAndLinks; 30 | private final Metadata metadata; 31 | private final Comment comment; 32 | private Collection links; 33 | private Collection tags; 34 | 35 | AbstractDirectiveNode( 36 | SourceLocation sourceLocation, 37 | LocalDate date, 38 | List tagsAndLinks, 39 | Metadata metadata, 40 | Comment comment) { 41 | super(sourceLocation); 42 | this.date = Objects.requireNonNull(date, "date"); 43 | this.tagsAndLinks = Objects.requireNonNull(tagsAndLinks, "tagsAndLinks"); 44 | this.metadata = Objects.requireNonNull(metadata, "metadata"); 45 | this.comment = comment; 46 | } 47 | 48 | @Override 49 | public LocalDate date() { 50 | return date; 51 | } 52 | 53 | @Override 54 | public Metadata metadata() { 55 | return metadata; 56 | } 57 | 58 | @Override 59 | public List tagsAndLinks() { 60 | return tagsAndLinks; 61 | } 62 | 63 | @Nullable 64 | @Override 65 | public Comment comment() { 66 | return comment; 67 | } 68 | 69 | @Override 70 | public Collection links() { 71 | if (this.links != null) { 72 | return this.links; 73 | } 74 | List links = new ArrayList<>(); 75 | for (TagOrLink i : tagsAndLinks) { 76 | if (i instanceof Link link) { 77 | links.add(link); 78 | } 79 | } 80 | return this.links = Collections.unmodifiableCollection(links); 81 | } 82 | 83 | @Override 84 | public Collection tags() { 85 | if (this.tags != null) { 86 | return this.tags; 87 | } 88 | List tags = new ArrayList<>(); 89 | for (TagOrLink i : tagsAndLinks) { 90 | if (i instanceof Tag tag) { 91 | tags.add(tag); 92 | } 93 | } 94 | return this.tags = Collections.unmodifiableCollection(tags); 95 | } 96 | 97 | abstract static sealed class Builder< 98 | T extends Node, B extends AbstractDirectiveNode.Builder> 99 | extends AbstractNode.Builder 100 | permits BalanceDirective.Builder, 101 | CloseDirective.Builder, 102 | CommodityDirective.Builder, 103 | CustomDirective.Builder, 104 | DocumentDirective.Builder, 105 | EventDirective.Builder, 106 | NoteDirective.Builder, 107 | OpenDirective.Builder, 108 | PadDirective.Builder, 109 | PriceDirective.Builder, 110 | QueryDirective.Builder, 111 | TransactionDirective.Builder { 112 | private LocalDate date; 113 | private List tagsAndLinks; 114 | private Metadata metadata; 115 | private Comment comment; 116 | 117 | Builder() {} 118 | 119 | Builder( 120 | SourceLocation sourceLocation, 121 | LocalDate date, 122 | List tagsAndLinks, 123 | Metadata metadata, 124 | Comment comment) { 125 | super(sourceLocation); 126 | this.date = date; 127 | this.tagsAndLinks = tagsAndLinks; 128 | this.metadata = metadata; 129 | this.comment = comment; 130 | } 131 | 132 | public LocalDate date() { 133 | return date; 134 | } 135 | 136 | @SuppressWarnings("unchecked") 137 | public B date(LocalDate date) { 138 | this.date = date; 139 | return (B) this; 140 | } 141 | 142 | public List tagsAndLinks() { 143 | return tagsAndLinks; 144 | } 145 | 146 | @SuppressWarnings("unchecked") 147 | public B tagsAndLinks(List tagsAndLinks) { 148 | this.tagsAndLinks = tagsAndLinks; 149 | return (B) this; 150 | } 151 | 152 | public Metadata metadata() { 153 | return metadata; 154 | } 155 | 156 | @SuppressWarnings("unchecked") 157 | public B metadata(Metadata metadata) { 158 | this.metadata = metadata; 159 | return (B) this; 160 | } 161 | 162 | public Comment comment() { 163 | return comment; 164 | } 165 | 166 | @SuppressWarnings("unchecked") 167 | public B comment(Comment comment) { 168 | this.comment = comment; 169 | return (B) this; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /jbeancount/src/main/java/nl/jrdie/beancount/language/Posting.java: -------------------------------------------------------------------------------- 1 | package nl.jrdie.beancount.language; 2 | 3 | import graphql.util.TraversalControl; 4 | import graphql.util.TraverserContext; 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | import nl.jrdie.beancount.language.tools.NodeVisitor; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public final class Posting extends AbstractNode implements WithComment { 11 | private final Flag flag; 12 | private final Account account; 13 | private final ArithmeticExpression amountExpression; 14 | private final Commodity commodity; 15 | private final CostSpec costSpec; 16 | private final PriceAnnotation priceAnnotation; 17 | private final Metadata metadata; 18 | private final Comment comment; 19 | 20 | private Posting( 21 | Flag flag, 22 | Account account, 23 | ArithmeticExpression amountExpression, 24 | Commodity commodity, 25 | CostSpec costSpec, 26 | PriceAnnotation priceAnnotation, 27 | Metadata metadata, 28 | SourceLocation sourceLocation, 29 | Comment comment) { 30 | super(sourceLocation); 31 | this.account = comment == null ? Objects.requireNonNull(account, "account") : account; 32 | this.metadata = Objects.requireNonNull(metadata, "metadata"); 33 | this.flag = flag; 34 | this.amountExpression = amountExpression; 35 | this.commodity = commodity; 36 | this.costSpec = costSpec; 37 | this.priceAnnotation = priceAnnotation; 38 | this.comment = comment; 39 | } 40 | 41 | public Flag flag() { 42 | return flag; 43 | } 44 | 45 | public Account account() { 46 | return account; 47 | } 48 | 49 | public ArithmeticExpression amountExpression() { 50 | return amountExpression; 51 | } 52 | 53 | public Commodity commodity() { 54 | return commodity; 55 | } 56 | 57 | public CostSpec costSpec() { 58 | return costSpec; 59 | } 60 | 61 | public PriceAnnotation priceAnnotation() { 62 | return priceAnnotation; 63 | } 64 | 65 | public Metadata metadata() { 66 | return metadata; 67 | } 68 | 69 | public static Builder newPosting() { 70 | return new Builder(); 71 | } 72 | 73 | @Override 74 | public TraversalControl accept(TraverserContext> context, NodeVisitor visitor) { 75 | return visitor.visitPosting(this, context); 76 | } 77 | 78 | @Override 79 | public Posting transform(Consumer builderConsumer) { 80 | final Builder b = 81 | new Builder( 82 | sourceLocation(), 83 | flag, 84 | account, 85 | amountExpression, 86 | commodity, 87 | costSpec, 88 | priceAnnotation, 89 | metadata, 90 | comment); 91 | return null; 92 | } 93 | 94 | @Override 95 | @Nullable 96 | public Comment comment() { 97 | return comment; 98 | } 99 | 100 | public static final class Builder extends AbstractNode.Builder { 101 | private Flag flag; 102 | private Account account; 103 | private ArithmeticExpression amountExpression; 104 | private Commodity commodity; 105 | private CostSpec costSpec; 106 | private PriceAnnotation priceAnnotation; 107 | private Metadata metadata; 108 | private Comment comment; 109 | 110 | private Builder( 111 | SourceLocation sourceLocation, 112 | Flag flag, 113 | Account account, 114 | ArithmeticExpression amountExpression, 115 | Commodity commodity, 116 | CostSpec costSpec, 117 | PriceAnnotation priceAnnotation, 118 | Metadata metadata, 119 | Comment comment) { 120 | super(sourceLocation); 121 | this.flag = flag; 122 | this.account = account; 123 | this.amountExpression = amountExpression; 124 | this.commodity = commodity; 125 | this.costSpec = costSpec; 126 | this.priceAnnotation = priceAnnotation; 127 | this.metadata = metadata; 128 | this.comment = comment; 129 | } 130 | 131 | private Builder() {} 132 | 133 | public Posting build() { 134 | return new Posting( 135 | flag, 136 | account, 137 | amountExpression, 138 | commodity, 139 | costSpec, 140 | priceAnnotation, 141 | metadata, 142 | sourceLocation(), 143 | comment); 144 | } 145 | 146 | public Flag flag() { 147 | return flag; 148 | } 149 | 150 | public Builder flag(Flag flag) { 151 | this.flag = flag; 152 | return this; 153 | } 154 | 155 | public Account account() { 156 | return account; 157 | } 158 | 159 | public Builder account(Account account) { 160 | this.account = account; 161 | return this; 162 | } 163 | 164 | public ArithmeticExpression amountExpression() { 165 | return amountExpression; 166 | } 167 | 168 | public Builder amountExpression(ArithmeticExpression amountExpression) { 169 | this.amountExpression = amountExpression; 170 | return this; 171 | } 172 | 173 | public Commodity commodity() { 174 | return commodity; 175 | } 176 | 177 | public Builder commodity(Commodity commodity) { 178 | this.commodity = commodity; 179 | return this; 180 | } 181 | 182 | public CostSpec costSpec() { 183 | return costSpec; 184 | } 185 | 186 | public Builder costSpec(CostSpec costSpec) { 187 | this.costSpec = costSpec; 188 | return this; 189 | } 190 | 191 | public PriceAnnotation priceAnnotation() { 192 | return priceAnnotation; 193 | } 194 | 195 | public Builder priceAnnotation(PriceAnnotation priceAnnotation) { 196 | this.priceAnnotation = priceAnnotation; 197 | return this; 198 | } 199 | 200 | public Metadata metadata() { 201 | return metadata; 202 | } 203 | 204 | public Builder metadata(Metadata metadata) { 205 | this.metadata = metadata; 206 | return this; 207 | } 208 | 209 | public Comment comment() { 210 | return comment; 211 | } 212 | 213 | public Builder comment(Comment comment) { 214 | this.comment = comment; 215 | return this; 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | --------------------------------------------------------------------------------